diff --git a/CHANGELOG.md b/CHANGELOG.md index c29502f1..6f4631a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,6 +144,15 @@ auto-generated per-PR notes; this file is the curated, human-readable history. its) — filed as **#298** rather than deleted or silently fixed here, since restoring it is a distinct rendering fix that needs its own >1560px-viewport regression test. +- **Line/area chart X-axis tick labels no longer rotate into an unreadable + wall of timestamps at a few hundred rows.** Line/area charts still draw a + Chart.js `category` scale (one tick per distinct row — no time scale yet), + so `chartJsConfig` (`src/core/chart-data.ts`) now forces the category axis's + ticks to `autoSkip: true, maxRotation: 0, minRotation: 0` for those two chart + types, letting Chart.js drop enough labels to stay horizontal instead of + rotating every one of them. Bar/hbar/pie category axes are unchanged. A real + Chart.js time scale (natural tick boundaries, gap-aware point placement) is + filed as a follow-up: **#309**. ### Added - **Dashboard v1 contracts, codecs, canonical encoding, and resource limits** diff --git a/src/core/chart-data.ts b/src/core/chart-data.ts index 1a682409..d7b3e6d8 100644 --- a/src/core/chart-data.ts +++ b/src/core/chart-data.ts @@ -725,7 +725,10 @@ interface TooltipContext { interface ChartJsAxis { display: boolean; grid: { color: string; drawBorder: boolean; display: boolean }; - ticks: { color: string; font: Record; callback?: (v: number | string) => string }; + ticks: { + color: string; font: Record; callback?: (v: number | string) => string; + autoSkip?: boolean; maxRotation?: number; minRotation?: number; + }; beginAtZero?: boolean; stacked?: boolean; } @@ -909,7 +912,12 @@ export function chartJsConfig( beginAtZero: style.scale === 'zero' || (style.scale === 'auto' && (horizontal || cfg.type === 'bar')), }; - const catAxis: ChartJsAxis = { display: axesVisible, grid: { ...grid, display: false }, ticks }; + // Line/area charts plot every distinct row as its own category tick (no + // Chart.js time scale — #309), which at a few hundred rows is unreadable + // rotated at Chart.js's default up-to-50°. Force horizontal labels and let + // autoSkip drop enough of them to fit instead. + const catTicks = isLine ? { ...ticks, autoSkip: true, maxRotation: 0, minRotation: 0 } : ticks; + const catAxis: ChartJsAxis = { display: axesVisible, grid: { ...grid, display: false }, ticks: catTicks }; options.scales = horizontal ? { x: valueAxis, y: catAxis } : { x: catAxis, y: valueAxis }; const barsStacked = (horizontal || cfg.type === 'bar') && style.mode === 'stacked'; const areaStacked = isArea && style.stack === 'stacked'; diff --git a/tests/unit/chart-data.test.ts b/tests/unit/chart-data.test.ts index c4e3ba5c..273d07a3 100644 --- a/tests/unit/chart-data.test.ts +++ b/tests/unit/chart-data.test.ts @@ -585,6 +585,18 @@ describe('chartJsConfig', () => { expect(cfg.options.indexAxis).toBe('x'); expect(cfg.options.scales!.y.beginAtZero).toBe(true); }); + it('line/area category-axis ticks force horizontal autoSkip labels (#309); bar keeps Chart.js defaults', () => { + const line = chartJsConfig(cols, rows, { type: 'line', x: 0, y: [1], series: null }, colors); + expect(line.options.scales!.x.ticks).toMatchObject({ autoSkip: true, maxRotation: 0, minRotation: 0 }); + const area = chartJsConfig(cols, rows, { type: 'area', x: 0, y: [1], series: null }, colors); + expect(area.options.scales!.x.ticks).toMatchObject({ autoSkip: true, maxRotation: 0, minRotation: 0 }); + const bar = chartJsConfig(cols, rows, { type: 'bar', x: 0, y: [1], series: null }, colors); + expect(bar.options.scales!.x.ticks.autoSkip).toBeUndefined(); + expect(bar.options.scales!.x.ticks.maxRotation).toBeUndefined(); + // hbar's category axis is y, not x + const hbar = chartJsConfig(cols, rows, { type: 'hbar', x: 0, y: [1], series: null }, colors); + expect(hbar.options.scales!.y.ticks.autoSkip).toBeUndefined(); + }); it('value-axis ticks humanize via callback (number and coercible string)', () => { const cfg = chartJsConfig(cols, rows, { type: 'bar', x: 0, y: [1], series: null }, colors); const cb = cfg.options.scales!.y.ticks.callback!;