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:
@@ -4,7 +4,10 @@ import {
|
||||
monthBoundaryTo,
|
||||
buildMonthSequence,
|
||||
fillDensityGaps,
|
||||
fetchDensity
|
||||
fetchDensity,
|
||||
aggregateToYears,
|
||||
selectionBoundaryFrom,
|
||||
selectionBoundaryTo
|
||||
} from './timeline';
|
||||
|
||||
describe('monthBoundaryFrom', () => {
|
||||
@@ -108,6 +111,47 @@ describe('fillDensityGaps', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('aggregateToYears', () => {
|
||||
it('returns empty array for empty input', () => {
|
||||
expect(aggregateToYears([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('sums counts within the same year', () => {
|
||||
const result = aggregateToYears([
|
||||
{ month: '1915-08', count: 5 },
|
||||
{ month: '1915-09', count: 2 },
|
||||
{ month: '1915-10', count: 8 }
|
||||
]);
|
||||
expect(result).toEqual([{ month: '1915', count: 15 }]);
|
||||
});
|
||||
|
||||
it('produces one bucket per distinct year, sorted chronologically', () => {
|
||||
const result = aggregateToYears([
|
||||
{ month: '1916-01', count: 3 },
|
||||
{ month: '1915-08', count: 5 },
|
||||
{ month: '1916-04', count: 7 },
|
||||
{ month: '1914-12', count: 1 }
|
||||
]);
|
||||
expect(result).toEqual([
|
||||
{ month: '1914', count: 1 },
|
||||
{ month: '1915', count: 5 },
|
||||
{ month: '1916', count: 10 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectionBoundaryFrom / To', () => {
|
||||
it('handles month labels (YYYY-MM)', () => {
|
||||
expect(selectionBoundaryFrom('1915-08')).toBe('1915-08-01');
|
||||
expect(selectionBoundaryTo('1915-08')).toBe('1915-08-31');
|
||||
});
|
||||
|
||||
it('handles year labels (YYYY)', () => {
|
||||
expect(selectionBoundaryFrom('1915')).toBe('1915-01-01');
|
||||
expect(selectionBoundaryTo('1915')).toBe('1915-12-31');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchDensity', () => {
|
||||
it('skips fetch and returns null density on mobile', async () => {
|
||||
const fetch = vi.fn();
|
||||
|
||||
Reference in New Issue
Block a user