Skip to content
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e0e8fdb
feat: add enlarged downloads chart with filters
graphieros Jan 26, 2026
54bc232
useCharts
graphieros Jan 26, 2026
78bb0c9
fix: attempt to fix component test
graphieros Jan 26, 2026
a6d2037
Merge branch 'main' into main
graphieros Jan 27, 2026
3a30c0a
fix: remove failing component test for POC
graphieros Jan 27, 2026
1299e68
Merge branch 'main' of https://github.com/graphieros/npmx.dev
graphieros Jan 27, 2026
906cddd
feat: improve chart layout
graphieros Jan 27, 2026
58bbca7
feat: restore css overrides in sparkline chart
graphieros Jan 27, 2026
d16357f
feat: override chart zoom selector max width
graphieros Jan 27, 2026
fc0a3d4
Merge remote-tracking branch 'origin/main' into pr-146
danielroe Jan 27, 2026
f90a182
fix: improve accessibility
danielroe Jan 27, 2026
2b6990b
Merge remote-tracking branch 'origin/main' into pr-146
danielroe Jan 27, 2026
cdfbea1
fix: more a11y fixes
danielroe Jan 27, 2026
678a917
chore: fix conflict
graphieros Jan 27, 2026
ab4db68
fix: add missing I18n import
graphieros Jan 27, 2026
2825211
fix: some regressions I caused 🤦
danielroe Jan 27, 2026
ac0b34e
fix: extract strings
danielroe Jan 27, 2026
5a685e1
Merge branch 'pr-146' into graphieros/main
danielroe Jan 27, 2026
4d79605
fix: set glassomorphism on chart tooltip directly in customFormat
graphieros Jan 27, 2026
779e29a
Merge branch 'main' into main
graphieros Jan 27, 2026
fadd9b7
fix: restore default date range for weekly downloads
graphieros Jan 27, 2026
5f615b6
Merge branch 'main' of https://github.com/graphieros/npmx.dev
graphieros Jan 27, 2026
80bdbaa
Merge branch 'main' into main
graphieros Jan 27, 2026
28873a8
fix: clamp `weekEnd`
danielroe Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 36 additions & 49 deletions app/composables/useCharts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,36 +122,6 @@ function mergeDailyPoints(
.map(([day, downloads]) => ({ day, downloads }))
}

function getIsoWeekStartDateFromWeekKey(weekKey: string): Date | null {
const match = /^(\d{4})-W(\d{2})$/.exec(weekKey)
if (!match) return null

const year = Number(match[1])
const week = Number(match[2])

const januaryFourth = new Date(Date.UTC(year, 0, 4))
const januaryFourthIsoDay = januaryFourth.getUTCDay() || 7
const weekOneMonday = new Date(Date.UTC(year, 0, 4 - (januaryFourthIsoDay - 1)))

const weekMonday = new Date(weekOneMonday)
weekMonday.setUTCDate(weekOneMonday.getUTCDate() + (week - 1) * 7)
return weekMonday
}

function toIsoWeekKey(isoDay: string): string {
const date = new Date(`${isoDay}T00:00:00.000Z`)
const isoDayOfWeek = date.getUTCDay() || 7

const thursday = new Date(date)
thursday.setUTCDate(date.getUTCDate() + 4 - isoDayOfWeek)

const isoYear = thursday.getUTCFullYear()
const isoYearStart = new Date(Date.UTC(isoYear, 0, 1))
const weekNumber = Math.ceil(((+thursday - +isoYearStart) / 86400000 + 1) / 7)

return `${isoYear}-W${String(weekNumber).padStart(2, '0')}`
}

function buildDailyEvolutionFromDaily(
daily: Array<{ day: string; downloads: number }>,
): DailyDownloadPoint[] {
Expand All @@ -161,30 +131,44 @@ function buildDailyEvolutionFromDaily(
.map(item => ({ day: item.day, downloads: item.downloads }))
}

function buildWeeklyEvolutionFromDaily(
function buildRollingWeeklyEvolutionFromDaily(
daily: Array<{ day: string; downloads: number }>,
rangeStartIso: string,
rangeEndIso: string,
): WeeklyDownloadPoint[] {
const sorted = daily.slice().sort((a, b) => a.day.localeCompare(b.day))
const downloadsByWeekKey = new Map<string, number>()
const rangeStartDate = parseIsoDateOnly(rangeStartIso)
const rangeEndDate = parseIsoDateOnly(rangeEndIso)

const groupedByIndex = new Map<number, number>()

for (const item of sorted) {
const weekKey = toIsoWeekKey(item.day)
downloadsByWeekKey.set(weekKey, (downloadsByWeekKey.get(weekKey) ?? 0) + item.downloads)
}
const itemDate = parseIsoDateOnly(item.day)
const dayOffset = Math.floor((itemDate.getTime() - rangeStartDate.getTime()) / 86400000)
if (dayOffset < 0) continue

return Array.from(downloadsByWeekKey.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([weekKey, downloads]) => {
const weekStartDate = getIsoWeekStartDateFromWeekKey(weekKey)
if (!weekStartDate) return { weekKey, downloads, weekStart: '-', weekEnd: '-' }
const weekIndex = Math.floor(dayOffset / 7)
groupedByIndex.set(weekIndex, (groupedByIndex.get(weekIndex) ?? 0) + item.downloads)
}

return Array.from(groupedByIndex.entries())
.sort(([a], [b]) => a - b)
.map(([weekIndex, downloads]) => {
const weekStartDate = addDays(rangeStartDate, weekIndex * 7)
const weekEndDate = addDays(weekStartDate, 6)

// Clamp weekEnd to the actual data range end date
const clampedWeekEndDate =
weekEndDate.getTime() > rangeEndDate.getTime() ? rangeEndDate : weekEndDate

const weekStartIso = toIsoDateString(weekStartDate)
const weekEndIso = toIsoDateString(clampedWeekEndDate)

return {
weekKey,
downloads,
weekStart: toIsoDateString(weekStartDate),
weekEnd: toIsoDateString(weekEndDate),
weekKey: `${weekStartIso}_${weekEndIso}`,
weekStart: weekStartIso,
weekEnd: weekEndIso,
}
})
}
Expand Down Expand Up @@ -341,6 +325,9 @@ export function useCharts() {
)
} else if (downloadEvolutionOptions.granularity === 'week') {
const weekCount = downloadEvolutionOptions.weeks ?? 52

// Full rolling weeks ending on `end` (yesterday by default)
// Range length is exactly weekCount * 7 days (inclusive)
start = addDays(end, -(weekCount * 7) + 1)
} else {
start = addDays(end, -30 + 1)
Expand All @@ -362,14 +349,14 @@ export function useCharts() {

const { start, end } = resolveDateRange(resolvedOptions, resolvedCreatedIso)

const sortedDaily = await fetchDailyRangeChunked(
resolvedPackageName,
toIsoDateString(start),
toIsoDateString(end),
)
const startIso = toIsoDateString(start)
const endIso = toIsoDateString(end)

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

if (resolvedOptions.granularity === 'day') return buildDailyEvolutionFromDaily(sortedDaily)
if (resolvedOptions.granularity === 'week') return buildWeeklyEvolutionFromDaily(sortedDaily)
if (resolvedOptions.granularity === 'week')
return buildRollingWeeklyEvolutionFromDaily(sortedDaily, startIso, endIso)
if (resolvedOptions.granularity === 'month') return buildMonthlyEvolutionFromDaily(sortedDaily)
return buildYearlyEvolutionFromDaily(sortedDaily)
}
Expand Down