Skip to content

Commit 1691364

Browse files
committed
extract some logic
1 parent ba9df70 commit 1691364

5 files changed

Lines changed: 392 additions & 399 deletions

File tree

app/composables/useCharts.ts

Lines changed: 14 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ import { parseRepoUrl } from '#shared/utils/git-providers'
1313
import type { PackageMetaResponse } from '#shared/types'
1414
import { encodePackageName } from '#shared/utils/npm'
1515
import { fetchNpmDownloadsRange } from '~/utils/npm/api'
16+
import {
17+
buildDailyEvolution,
18+
buildWeeklyEvolution,
19+
buildMonthlyEvolution,
20+
buildYearlyEvolution,
21+
} from '~/utils/chart-data-buckets'
1622

1723
export type PackumentLikeForTime = {
1824
time?: Record<string, string>
@@ -32,25 +38,6 @@ function startOfUtcMonth(date: Date): Date {
3238
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1))
3339
}
3440

35-
function daysInMonth(year: number, month: number): number {
36-
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate()
37-
}
38-
39-
function daysInYear(year: number): number {
40-
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 366 : 365
41-
}
42-
43-
/**
44-
* Scale up a partial bucket value proportionally.
45-
* @param value - the raw sum for the partial bucket
46-
* @param actualDays - number of days with data in the bucket
47-
* @param totalDays - expected full bucket size in days
48-
*/
49-
export function fillPartialBucket(value: number, actualDays: number, totalDays: number): number {
50-
if (actualDays <= 0 || actualDays >= totalDays) return value
51-
return Math.round((value * totalDays) / actualDays)
52-
}
53-
5441
function startOfUtcYear(date: Date): Date {
5542
return new Date(Date.UTC(date.getUTCFullYear(), 0, 1))
5643
}
@@ -108,164 +95,6 @@ function mergeDailyPoints(points: DailyRawPoint[]): DailyRawPoint[] {
10895
.map(([day, value]) => ({ day, value }))
10996
}
11097

111-
export function buildDailyEvolutionFromDaily(daily: DailyRawPoint[]): DailyDataPoint[] {
112-
return daily
113-
.slice()
114-
.sort((a, b) => a.day.localeCompare(b.day))
115-
.map(item => {
116-
const dayDate = parseIsoDateOnly(item.day)
117-
const timestamp = dayDate.getTime()
118-
119-
return { day: item.day, value: item.value, timestamp }
120-
})
121-
}
122-
123-
export function buildRollingWeeklyEvolutionFromDaily(
124-
daily: DailyRawPoint[],
125-
rangeStartIso: string,
126-
rangeEndIso: string,
127-
): WeeklyDataPoint[] {
128-
const sorted = daily.slice().sort((a, b) => a.day.localeCompare(b.day))
129-
if (sorted.length === 0) return []
130-
131-
const rangeStartDate = parseIsoDateOnly(rangeStartIso)
132-
// Align from last day with actual data (npm has 1-2 day delay, today is incomplete)
133-
const lastNonZero = sorted.findLast(d => d.value > 0)
134-
const effectiveEnd = lastNonZero
135-
? parseIsoDateOnly(lastNonZero.day)
136-
: parseIsoDateOnly(rangeEndIso)
137-
const pickerEnd = parseIsoDateOnly(rangeEndIso)
138-
const rangeEndDate = effectiveEnd.getTime() < pickerEnd.getTime() ? effectiveEnd : pickerEnd
139-
140-
// Build 7-day buckets from END backwards
141-
const groupedByIndex = new Map<number, number>()
142-
143-
for (const item of sorted) {
144-
const itemDate = parseIsoDateOnly(item.day)
145-
const dayOffsetFromEnd = Math.floor((rangeEndDate.getTime() - itemDate.getTime()) / 86400000)
146-
if (dayOffsetFromEnd < 0) continue
147-
148-
const weekIndex = Math.floor(dayOffsetFromEnd / 7)
149-
groupedByIndex.set(weekIndex, (groupedByIndex.get(weekIndex) ?? 0) + item.value)
150-
}
151-
152-
return Array.from(groupedByIndex.entries())
153-
.sort(([a], [b]) => b - a) // reverse: highest index = oldest week
154-
.map(([weekIndex, value]) => {
155-
const weekEndDate = addDays(rangeEndDate, -(weekIndex * 7))
156-
let weekStartDate = addDays(weekEndDate, -6)
157-
158-
// First bucket may be partial — scale up proportionally
159-
if (weekStartDate.getTime() < rangeStartDate.getTime()) {
160-
weekStartDate = rangeStartDate
161-
const actualDays =
162-
Math.floor((weekEndDate.getTime() - rangeStartDate.getTime()) / 86400000) + 1
163-
value = fillPartialBucket(value, actualDays, 7)
164-
}
165-
166-
const weekStartIso = toIsoDateString(weekStartDate)
167-
const weekEndIso = toIsoDateString(weekEndDate)
168-
169-
const timestampStart = weekStartDate.getTime()
170-
const timestampEnd = weekEndDate.getTime()
171-
172-
return {
173-
value,
174-
weekKey: `${weekStartIso}_${weekEndIso}`,
175-
weekStart: weekStartIso,
176-
weekEnd: weekEndIso,
177-
timestampStart,
178-
timestampEnd,
179-
}
180-
})
181-
}
182-
183-
export function buildMonthlyEvolutionFromDaily(
184-
daily: DailyRawPoint[],
185-
rangeStartIso?: string,
186-
rangeEndIso?: string,
187-
): MonthlyDataPoint[] {
188-
const sorted = daily.slice().sort((a, b) => a.day.localeCompare(b.day))
189-
const valuesByMonth = new Map<string, number>()
190-
191-
for (const item of sorted) {
192-
const month = item.day.slice(0, 7)
193-
valuesByMonth.set(month, (valuesByMonth.get(month) ?? 0) + item.value)
194-
}
195-
196-
const entries = Array.from(valuesByMonth.entries()).sort(([a], [b]) => a.localeCompare(b))
197-
198-
return entries.map(([month, value], index) => {
199-
const monthStartDate = parseIsoDateOnly(`${month}-01`)
200-
const [y, m] = month.split('-').map(Number) as [number, number]
201-
const totalDays = daysInMonth(y, m - 1)
202-
203-
// Scale up partial first bucket
204-
if (index === 0 && rangeStartIso) {
205-
const rangeStartDay = Number(rangeStartIso.split('-')[2])
206-
if (rangeStartDay > 1) {
207-
value = fillPartialBucket(value, totalDays - rangeStartDay + 1, totalDays)
208-
}
209-
}
210-
211-
// Scale up partial last bucket
212-
if (index === entries.length - 1 && rangeEndIso) {
213-
const rangeEndDay = Number(rangeEndIso.split('-')[2])
214-
if (rangeEndDay < totalDays) {
215-
value = fillPartialBucket(value, rangeEndDay, totalDays)
216-
}
217-
}
218-
219-
const timestamp = monthStartDate.getTime()
220-
return { month, value, timestamp }
221-
})
222-
}
223-
224-
export function buildYearlyEvolutionFromDaily(
225-
daily: DailyRawPoint[],
226-
rangeStartIso?: string,
227-
rangeEndIso?: string,
228-
): YearlyDataPoint[] {
229-
const sorted = daily.slice().sort((a, b) => a.day.localeCompare(b.day))
230-
const valuesByYear = new Map<string, number>()
231-
232-
for (const item of sorted) {
233-
const year = item.day.slice(0, 4)
234-
valuesByYear.set(year, (valuesByYear.get(year) ?? 0) + item.value)
235-
}
236-
237-
const entries = Array.from(valuesByYear.entries()).sort(([a], [b]) => a.localeCompare(b))
238-
239-
return entries.map(([year, value], index) => {
240-
const y = Number(year)
241-
const totalDays = daysInYear(y)
242-
243-
// Scale up partial first bucket
244-
if (index === 0 && rangeStartIso) {
245-
const rangeStart = parseIsoDateOnly(rangeStartIso)
246-
const yearStart = parseIsoDateOnly(`${year}-01-01`)
247-
const dayOfYear = Math.floor((rangeStart.getTime() - yearStart.getTime()) / 86400000)
248-
if (dayOfYear > 0) {
249-
value = fillPartialBucket(value, totalDays - dayOfYear, totalDays)
250-
}
251-
}
252-
253-
// Scale up partial last bucket
254-
if (index === entries.length - 1 && rangeEndIso) {
255-
const rangeEnd = parseIsoDateOnly(rangeEndIso)
256-
const yearStart = parseIsoDateOnly(`${year}-01-01`)
257-
const actualDays = Math.floor((rangeEnd.getTime() - yearStart.getTime()) / 86400000) + 1
258-
if (actualDays < totalDays) {
259-
value = fillPartialBucket(value, actualDays, totalDays)
260-
}
261-
}
262-
263-
const yearStartDate = parseIsoDateOnly(`${year}-01-01`)
264-
const timestamp = yearStartDate.getTime()
265-
return { year, value, timestamp }
266-
})
267-
}
268-
26998
const npmDailyRangeCache = import.meta.client ? new Map<string, Promise<DailyRawPoint[]>>() : null
27099
const likesEvolutionCache = import.meta.client ? new Map<string, Promise<DailyRawPoint[]>>() : null
271100
const contributorsEvolutionCache = import.meta.client
@@ -552,12 +381,12 @@ export function useCharts() {
552381

553382
const sortedDaily = await fetchDailyRangeChunked(resolvedPackageName, startIso, endIso)
554383

555-
if (resolvedOptions.granularity === 'day') return buildDailyEvolutionFromDaily(sortedDaily)
384+
if (resolvedOptions.granularity === 'day') return buildDailyEvolution(sortedDaily)
556385
if (resolvedOptions.granularity === 'week')
557-
return buildRollingWeeklyEvolutionFromDaily(sortedDaily, startIso, endIso)
386+
return buildWeeklyEvolution(sortedDaily, startIso, endIso)
558387
if (resolvedOptions.granularity === 'month')
559-
return buildMonthlyEvolutionFromDaily(sortedDaily, startIso, endIso)
560-
return buildYearlyEvolutionFromDaily(sortedDaily, startIso, endIso)
388+
return buildMonthlyEvolution(sortedDaily, startIso, endIso)
389+
return buildYearlyEvolution(sortedDaily, startIso, endIso)
561390
}
562391

563392
async function fetchPackageLikesEvolution(
@@ -596,12 +425,12 @@ export function useCharts() {
596425

597426
const filteredDaily = sortedDaily.filter(d => d.day >= startIso && d.day <= endIso)
598427

599-
if (resolvedOptions.granularity === 'day') return buildDailyEvolutionFromDaily(filteredDaily)
428+
if (resolvedOptions.granularity === 'day') return buildDailyEvolution(filteredDaily)
600429
if (resolvedOptions.granularity === 'week')
601-
return buildRollingWeeklyEvolutionFromDaily(filteredDaily, startIso, endIso)
430+
return buildWeeklyEvolution(filteredDaily, startIso, endIso)
602431
if (resolvedOptions.granularity === 'month')
603-
return buildMonthlyEvolutionFromDaily(filteredDaily, startIso, endIso)
604-
return buildYearlyEvolutionFromDaily(filteredDaily, startIso, endIso)
432+
return buildMonthlyEvolution(filteredDaily, startIso, endIso)
433+
return buildYearlyEvolution(filteredDaily, startIso, endIso)
605434
}
606435

607436
async function fetchRepoContributorsEvolution(

0 commit comments

Comments
 (0)