fix(documents): collapse timeline to year bars when range > 240 months (#385)
Surfaced during proofshot: the production archive spans 1873 → 2023 (≈1809 month bars). With flex-1 + gap-px on a 1280 px container, every pixel was consumed by gaps and bars rendered at 0 px width — visible as "empty box, no bars". Fix: - Add aggregateToYears(buckets) that sums month counts per year and returns YYYY-keyed entries. - Add selectionBoundaryFrom/To that handle both YYYY and YYYY-MM labels (Jan 1 → Dec 31 for years, first → last day for months). - Component switches to year granularity when the gap-filled month sequence exceeds 240 entries (~20 years), keeping each bar clickable. - Drop the gap-px between bars and add min-w-px so sub-pixel rounding still leaves something visible. 5 new tests cover aggregation, boundary helpers, and the component-level year-mode + click behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,35 @@ export function fillDensityGaps(
|
||||
return sequence.map((month) => ({ month, count: counts.get(month) ?? 0 }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates month-granular buckets into one entry per year. Month strings are
|
||||
* truncated to "YYYY" and counts are summed. Used when the date span is too
|
||||
* long for month-granular bars to render at a clickable size.
|
||||
*/
|
||||
export function aggregateToYears(buckets: MonthBucket[]): MonthBucket[] {
|
||||
const totals = new Map<string, number>();
|
||||
for (const b of buckets) {
|
||||
const year = b.month.slice(0, 4);
|
||||
totals.set(year, (totals.get(year) ?? 0) + b.count);
|
||||
}
|
||||
return Array.from(totals.entries())
|
||||
.map(([year, count]) => ({ month: year, count }))
|
||||
.sort((a, b) => a.month.localeCompare(b.month));
|
||||
}
|
||||
|
||||
/**
|
||||
* Boundary helpers for selection. Accept either "YYYY-MM" (month) or "YYYY"
|
||||
* (year) and return the matching LocalDate string.
|
||||
*/
|
||||
export function selectionBoundaryFrom(label: string): string {
|
||||
return label.length === 4 ? `${label}-01-01` : `${label}-01`;
|
||||
}
|
||||
|
||||
export function selectionBoundaryTo(label: string): string {
|
||||
if (label.length === 4) return `${label}-12-31`;
|
||||
return monthBoundaryTo(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the density data for the timeline widget. Mobile (sm: breakpoint and below)
|
||||
* and calendar view both skip the request entirely — the widget isn't rendered
|
||||
|
||||
Reference in New Issue
Block a user