@svgrid/grid 3.0.3 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/GridMenus.svelte +44 -2
- package/dist/SvChartMenu.svelte +69 -0
- package/dist/SvChartMenu.svelte.d.ts +11 -0
- package/dist/SvChartPanes.svelte +113 -0
- package/dist/SvChartPanes.svelte.d.ts +20 -0
- package/dist/SvGrid.controller.svelte.d.ts +110 -39
- package/dist/SvGrid.controller.svelte.js +655 -200
- package/dist/SvGrid.css +105 -2
- package/dist/SvGrid.svelte +386 -514
- package/dist/SvGrid.types.d.ts +210 -9
- package/dist/SvGridCellEditor.svelte +17 -0
- package/dist/SvGridChart.svelte +2257 -329
- package/dist/SvGridChart.svelte.d.ts +6 -106
- package/dist/SvGridChart.types.d.ts +338 -0
- package/dist/SvGridChart.types.js +1 -0
- package/dist/SvGridChartBuilder.svelte +323 -0
- package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
- package/dist/SvGridChartPanel.svelte +221 -275
- package/dist/SvGridChartPickers.svelte +417 -0
- package/dist/SvGridChartPickers.svelte.d.ts +70 -0
- package/dist/SvGridChartView.svelte +13 -14
- package/dist/SvModal.svelte +7 -1
- package/dist/SvModal.svelte.d.ts +5 -0
- package/dist/ai.d.ts +40 -8
- package/dist/ai.js +135 -24
- package/dist/aria.d.ts +11 -0
- package/dist/build-api.js +108 -22
- package/dist/cdn/GridMenus-7kbpnnBW.js +635 -0
- package/dist/cdn/GridMenus-BL7ZgQvU.js +636 -0
- package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
- package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
- package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
- package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
- package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
- package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
- package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
- package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
- package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
- package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
- package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
- package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
- package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
- package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
- package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
- package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
- package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
- package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
- package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
- package/dist/cdn/SvModal-CIZWCcad.js +397 -0
- package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
- package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
- package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
- package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
- package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
- package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
- package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
- package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
- package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
- package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
- package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
- package/dist/cdn/dismissable-DAHetSNk.js +44 -0
- package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
- package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
- package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
- package/dist/cdn/{src-D6XvA1Ij.js → src-BKhZ6eXd.js} +10227 -9787
- package/dist/cdn/{src-Bugf5XlI.js → src-V1uu8iE9.js} +6568 -6128
- package/dist/cdn/svgrid.js +26 -15
- package/dist/cdn/svgrid.svelte-external.js +26 -15
- package/dist/chart-axes.d.ts +53 -0
- package/dist/chart-axes.js +351 -0
- package/dist/chart-cartesian.d.ts +88 -0
- package/dist/chart-cartesian.js +1862 -0
- package/dist/chart-decimate.d.ts +51 -0
- package/dist/chart-decimate.js +199 -0
- package/dist/chart-export-pdf.d.ts +47 -0
- package/dist/chart-export-pdf.js +187 -0
- package/dist/chart-export.d.ts +2 -1
- package/dist/chart-export.js +18 -3
- package/dist/chart-financial.d.ts +91 -0
- package/dist/chart-financial.js +175 -0
- package/dist/chart-flow.d.ts +10 -0
- package/dist/chart-flow.js +298 -0
- package/dist/chart-format.d.ts +18 -0
- package/dist/chart-format.js +65 -0
- package/dist/chart-grid.d.ts +8 -0
- package/dist/chart-grid.js +190 -0
- package/dist/chart-hierarchy.d.ts +17 -0
- package/dist/chart-hierarchy.js +146 -0
- package/dist/chart-indicators.d.ts +115 -0
- package/dist/chart-indicators.js +389 -0
- package/dist/chart-messages.d.ts +95 -0
- package/dist/chart-messages.js +92 -0
- package/dist/chart-motion.d.ts +16 -0
- package/dist/chart-motion.js +141 -0
- package/dist/chart-panel-messages.d.ts +265 -0
- package/dist/chart-panel-messages.js +285 -0
- package/dist/chart-pivot.d.ts +87 -0
- package/dist/chart-pivot.js +156 -0
- package/dist/chart-polar.d.ts +25 -0
- package/dist/chart-polar.js +700 -0
- package/dist/chart-samples.d.ts +16 -0
- package/dist/chart-samples.js +153 -0
- package/dist/chart-scale.d.ts +113 -0
- package/dist/chart-scale.js +405 -0
- package/dist/chart-stats.d.ts +111 -0
- package/dist/chart-stats.js +419 -0
- package/dist/chart-stream.d.ts +45 -0
- package/dist/chart-stream.js +91 -0
- package/dist/chart-summary.d.ts +27 -0
- package/dist/chart-summary.js +205 -0
- package/dist/chart-sync.svelte.d.ts +32 -0
- package/dist/chart-sync.svelte.js +23 -0
- package/dist/chart-table.d.ts +35 -0
- package/dist/chart-table.js +165 -0
- package/dist/chart-trend.d.ts +19 -0
- package/dist/chart-trend.js +61 -0
- package/dist/chart-types.d.ts +1456 -0
- package/dist/chart-types.js +6 -0
- package/dist/chart-validate.d.ts +43 -0
- package/dist/chart-validate.js +272 -0
- package/dist/chart-zoom.d.ts +40 -0
- package/dist/chart-zoom.js +145 -0
- package/dist/chart.d.ts +186 -865
- package/dist/chart.js +460 -2204
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +137 -18
- package/dist/column-resize.d.ts +3 -0
- package/dist/column-resize.js +4 -1
- package/dist/columns.js +3 -0
- package/dist/command-context.d.ts +19 -0
- package/dist/command-context.js +116 -0
- package/dist/core.d.ts +7 -0
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.d.ts +2 -8
- package/dist/editing.js +262 -33
- package/dist/fill-patterns.d.ts +0 -5
- package/dist/fill-patterns.js +56 -2
- package/dist/grid-messages.d.ts +10 -3
- package/dist/grid-messages.js +5 -0
- package/dist/history.d.ts +83 -0
- package/dist/history.js +133 -0
- package/dist/index.d.ts +24 -6
- package/dist/index.js +20 -2
- package/dist/keyboard-handlers.js +88 -31
- package/dist/keyboard.d.ts +55 -0
- package/dist/keyboard.js +132 -0
- package/dist/menus.d.ts +3 -1
- package/dist/menus.js +7 -2
- package/dist/merges.d.ts +97 -0
- package/dist/merges.js +147 -0
- package/dist/row-resize.d.ts +3 -0
- package/dist/row-resize.js +14 -0
- package/dist/scroll-sync.d.ts +1 -0
- package/dist/scroll-sync.js +24 -0
- package/dist/selection.d.ts +16 -0
- package/dist/selection.js +108 -16
- package/dist/shortcut-registry.d.ts +145 -0
- package/dist/shortcut-registry.js +44 -0
- package/dist/svgrid-wrapper.types.d.ts +213 -22
- package/dist/virtualization/virtualizer.js +38 -17
- package/package.json +6 -1
- package/src/GridMenus.svelte +44 -2
- package/src/SvChartMenu.svelte +69 -0
- package/src/SvChartPanes.svelte +113 -0
- package/src/SvChartPanes.test.ts +66 -0
- package/src/SvGrid.controller.svelte.ts +629 -172
- package/src/SvGrid.css +105 -2
- package/src/SvGrid.svelte +386 -514
- package/src/SvGrid.types.ts +188 -8
- package/src/SvGridCellEditor.svelte +17 -0
- package/src/SvGridChart.svelte +2257 -329
- package/src/SvGridChart.test.ts +1419 -1
- package/src/SvGridChart.types.ts +336 -0
- package/src/SvGridChartBuilder.svelte +323 -0
- package/src/SvGridChartPanel.svelte +221 -275
- package/src/SvGridChartPickers.svelte +417 -0
- package/src/SvGridChartView.svelte +13 -14
- package/src/SvModal.svelte +7 -1
- package/src/SvModal.test.ts +9 -0
- package/src/ai.test.ts +57 -2
- package/src/ai.ts +166 -22
- package/src/aria.d.ts +11 -0
- package/src/build-api.coverage.test.ts +184 -0
- package/src/build-api.ts +107 -18
- package/src/chart-axes.test.ts +573 -0
- package/src/chart-axes.ts +342 -0
- package/src/chart-cartesian.ts +1863 -0
- package/src/chart-decimate.test.ts +249 -0
- package/src/chart-decimate.ts +186 -0
- package/src/chart-export-pdf.test.ts +91 -0
- package/src/chart-export-pdf.ts +207 -0
- package/src/chart-export.test.ts +14 -0
- package/src/chart-export.ts +13 -3
- package/src/chart-financial.ts +208 -0
- package/src/chart-flow.ts +286 -0
- package/src/chart-format.ts +67 -0
- package/src/chart-grid.ts +179 -0
- package/src/chart-hierarchy.ts +135 -0
- package/src/chart-indicators.test.ts +443 -0
- package/src/chart-indicators.ts +384 -0
- package/src/chart-messages.test.ts +21 -0
- package/src/chart-messages.ts +184 -0
- package/src/chart-motion.test.ts +71 -0
- package/src/chart-motion.ts +148 -0
- package/src/chart-panel-messages.test.ts +29 -0
- package/src/chart-panel-messages.ts +550 -0
- package/src/chart-pivot.test.ts +54 -0
- package/src/chart-pivot.ts +225 -0
- package/src/chart-polar.ts +674 -0
- package/src/chart-samples.ts +106 -0
- package/src/chart-scale.ts +394 -0
- package/src/chart-series-types.test.ts +714 -0
- package/src/chart-stats.ts +409 -0
- package/src/chart-stream.test.ts +75 -0
- package/src/chart-stream.ts +114 -0
- package/src/chart-summary.test.ts +90 -0
- package/src/chart-summary.ts +197 -0
- package/src/chart-sync.svelte.ts +41 -0
- package/src/chart-table.test.ts +76 -0
- package/src/chart-table.ts +178 -0
- package/src/chart-trend.ts +47 -0
- package/src/chart-types.ts +1309 -0
- package/src/chart-validate.test.ts +100 -0
- package/src/chart-validate.ts +247 -0
- package/src/chart-zoom.test.ts +137 -0
- package/src/chart-zoom.ts +124 -0
- package/src/chart.coverage.test.ts +81 -0
- package/src/chart.test.ts +56 -3
- package/src/chart.ts +557 -2784
- package/src/clipboard.test.ts +151 -0
- package/src/clipboard.ts +145 -16
- package/src/column-resize.ts +7 -1
- package/src/columns.ts +2 -0
- package/src/command-context.test.ts +200 -0
- package/src/command-context.ts +137 -0
- package/src/core.ts +7 -0
- package/src/editing.test.ts +281 -3
- package/src/editing.ts +256 -38
- package/src/fill-patterns.test.ts +35 -0
- package/src/fill-patterns.ts +56 -2
- package/src/grid-messages.ts +16 -3
- package/src/history.test.ts +196 -0
- package/src/history.ts +162 -0
- package/src/icon-seam.test.ts +3 -2
- package/src/index.ts +150 -2
- package/src/keyboard-handlers.coverage.test.ts +126 -6
- package/src/keyboard-handlers.ts +91 -30
- package/src/keyboard-shortcuts.seam.test.ts +200 -0
- package/src/keyboard.test.ts +124 -1
- package/src/keyboard.ts +142 -0
- package/src/menus.test.ts +26 -0
- package/src/menus.ts +10 -3
- package/src/merges.test.ts +112 -0
- package/src/merges.ts +168 -0
- package/src/pivot.grid.test.ts +40 -5
- package/src/row-resize.test.ts +44 -0
- package/src/row-resize.ts +17 -0
- package/src/scroll-sync.test.ts +42 -1
- package/src/scroll-sync.ts +25 -0
- package/src/selection.multi-range.test.ts +34 -0
- package/src/selection.test.ts +30 -2
- package/src/selection.ts +111 -19
- package/src/shortcut-registry.test.ts +97 -0
- package/src/shortcut-registry.ts +181 -0
- package/src/svgrid-wrapper.types.ts +184 -13
- package/src/svgrid.behavior.test.ts +135 -0
- package/src/svgrid.charting.test.ts +513 -3
- package/src/svgrid.context-menu.test.ts +31 -0
- package/src/svgrid.new-features.wrapper.test.ts +10 -3
- package/src/virtualization/virtualizer.ts +39 -17
- package/dist/cdn/GridMenus-CHOMnW-c.js +0 -602
- package/dist/cdn/GridMenus-DhdPF700.js +0 -603
- package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
- package/dist/cdn/SvGridChart-1PMSp2LZ.js +0 -1481
- package/dist/cdn/SvGridChart-D02avhyr.js +0 -1480
- package/dist/cdn/SvGridChartPanel-DJnViOv4.js +0 -834
- package/dist/cdn/SvGridChartPanel-vPTNerFZ.js +0 -810
- package/dist/cdn/SvGridChartView-C--HhdUo.js +0 -56
- package/dist/cdn/SvGridChartView-KyyFG-wY.js +0 -55
- package/dist/cdn/chart-DLg3_zTQ.js +0 -1652
|
@@ -399,6 +399,29 @@ describe("buildChart: waterfall", () => {
|
|
|
399
399
|
// a connector line exists between bar tops
|
|
400
400
|
expect(g.lines[0]!.path).toContain("M");
|
|
401
401
|
});
|
|
402
|
+
it("tilts its labels when the widest does not fit its slot, with the angle the renderer needs", () => {
|
|
403
|
+
const cats = ["Revenue", "Cost of sales", "Gross profit", "R&D", "Sales & marketing", "G&A", "Operating income", "Tax", "Net income"];
|
|
404
|
+
const wide = buildChart({ type: "waterfall", categories: cats, series: [{ label: "s", values: cats.map(() => 1) }], width: 1400, height: 300 });
|
|
405
|
+
expect(wide.xLabelRotated).toBe(false);
|
|
406
|
+
const narrow = buildChart({ type: "waterfall", categories: cats, series: [{ label: "s", values: cats.map(() => 1) }], width: 700, height: 300 });
|
|
407
|
+
expect(narrow.xLabelRotated).toBe(true);
|
|
408
|
+
expect(narrow.xLabelAngle).toBe(-40);
|
|
409
|
+
const pinned = buildChart({ type: "waterfall", categories: cats, series: [{ label: "s", values: cats.map(() => 1) }], width: 700, height: 300, xAxis: { labelRotation: 0 } });
|
|
410
|
+
expect(pinned.xLabelRotated).toBe(false);
|
|
411
|
+
});
|
|
412
|
+
it("a total with a value opens the bridge at that level; a total with 0 reads the running sum", () => {
|
|
413
|
+
const g = buildChart({
|
|
414
|
+
type: "waterfall",
|
|
415
|
+
categories: ["Revenue", "Cost", "Gross", "Tax", "Net"],
|
|
416
|
+
series: [{ label: "s", values: [4300, -1840, 0, -180, 0] }],
|
|
417
|
+
waterfallTotals: [true, false, true, false, true],
|
|
418
|
+
});
|
|
419
|
+
expect(g.bars.map((b) => b.value)).toEqual([4300, -1840, 2460, -180, 2280]);
|
|
420
|
+
// The opening bar rises from zero; the cost hangs from its top.
|
|
421
|
+
const [revenue, cost] = g.bars;
|
|
422
|
+
expect(revenue!.y).toBeLessThan(cost!.y + cost!.h);
|
|
423
|
+
expect(cost!.y).toBeCloseTo(revenue!.y, 0);
|
|
424
|
+
});
|
|
402
425
|
it("colors positive/negative/total via waterfallColors overrides", () => {
|
|
403
426
|
const g = buildChart({
|
|
404
427
|
type: "waterfall",
|
|
@@ -437,6 +460,21 @@ describe("buildChart: funnel", () => {
|
|
|
437
460
|
});
|
|
438
461
|
});
|
|
439
462
|
|
|
463
|
+
describe("buildChart: colour legends", () => {
|
|
464
|
+
it("round their steps to the precision the range earns", () => {
|
|
465
|
+
const counts = buildChart({
|
|
466
|
+
type: "calendar", categories: [], series: [],
|
|
467
|
+
calendarValues: [{ date: "2026-01-05", value: 20 }, { date: "2026-01-06", value: 314 }, { date: "2026-01-07", value: 150 }],
|
|
468
|
+
});
|
|
469
|
+
expect(counts.calendarLegend.map((l) => l.label)).toEqual(["20", "94", "167", "241", "314"]);
|
|
470
|
+
const ratios = buildChart({
|
|
471
|
+
type: "heatmap", categories: ["a", "b"],
|
|
472
|
+
series: [{ label: "r1", values: [0.1, 0.5] }, { label: "r2", values: [0.3, 0.9] }],
|
|
473
|
+
});
|
|
474
|
+
expect(ratios.heatmapLegend.map((l) => l.value)).toEqual([0.1, 0.3, 0.5, 0.7, 0.9]);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
440
478
|
describe("buildChart: radar", () => {
|
|
441
479
|
it("one polygon per series + axis spokes + rings", () => {
|
|
442
480
|
const g = buildChart({
|
|
@@ -459,6 +497,27 @@ describe("buildChart: radar", () => {
|
|
|
459
497
|
buildChart({ type: "radar", categories: ["A"], series: [] }).radarAxes,
|
|
460
498
|
).toHaveLength(0);
|
|
461
499
|
});
|
|
500
|
+
it("yAxis.min / max pin the rim, and a value past it sits on the rim", () => {
|
|
501
|
+
const spec = {
|
|
502
|
+
type: "radar" as const,
|
|
503
|
+
categories: ["A", "B", "C", "D"],
|
|
504
|
+
series: [{ label: "s", values: [50, 50, 50, 50] }],
|
|
505
|
+
};
|
|
506
|
+
const free = buildChart(spec);
|
|
507
|
+
const pinned = buildChart({ ...spec, yAxis: { min: 0, max: 100 } });
|
|
508
|
+
const c = pinned.radarCenter!;
|
|
509
|
+
// Unpinned, 50 is the data maximum and sits on the rim; pinned to 100 it sits halfway.
|
|
510
|
+
expect(free.radarSeries[0]!.points[0]!.y).toBeCloseTo(c.cy - c.r, 0);
|
|
511
|
+
expect(pinned.radarSeries[0]!.points[0]!.y).toBeCloseTo(c.cy - c.r / 2, 0);
|
|
512
|
+
expect(pinned.radarRings.at(-1)).toBe(100);
|
|
513
|
+
// Past the rim clamps to it rather than leaving the dial.
|
|
514
|
+
const over = buildChart({ ...spec, yAxis: { min: 0, max: 40 } });
|
|
515
|
+
expect(over.radarSeries[0]!.points[0]!.y).toBeCloseTo(c.cy - c.r, 0);
|
|
516
|
+
// A pinned minimum lifts the floor: the value at the minimum is the centre.
|
|
517
|
+
const floor = buildChart({ ...spec, series: [{ label: "s", values: [20, 60, 20, 60] }], yAxis: { min: 20, max: 60 } });
|
|
518
|
+
expect(floor.radarSeries[0]!.points[0]!.x).toBeCloseTo(c.cx, 0);
|
|
519
|
+
expect(floor.radarSeries[0]!.points[0]!.y).toBeCloseTo(c.cy, 0);
|
|
520
|
+
});
|
|
462
521
|
it("guards against an all-zero max (vMax defaults to 1)", () => {
|
|
463
522
|
const g = buildChart({
|
|
464
523
|
type: "radar",
|
|
@@ -493,6 +552,19 @@ describe("buildChart: calendar heatmap", () => {
|
|
|
493
552
|
expect(blank!.color).toBe("transparent");
|
|
494
553
|
expect(g.calendarMonthTicks.length).toBeGreaterThanOrEqual(1);
|
|
495
554
|
});
|
|
555
|
+
it("labels only the months inside the range: the padding days of the next year get no tick", () => {
|
|
556
|
+
// 2026 ends on a Thursday; the grid pads to Saturday 2 Jan 2027.
|
|
557
|
+
const g = buildChart({
|
|
558
|
+
type: "calendar", categories: [], series: [],
|
|
559
|
+
calendarValues: [{ date: "2026-12-30", value: 1 }],
|
|
560
|
+
calendarStart: "2026-01-01", calendarEnd: "2026-12-31",
|
|
561
|
+
});
|
|
562
|
+
expect(g.calendarMonthTicks).toHaveLength(12);
|
|
563
|
+
expect(g.calendarMonthTicks[0]!.label).toBe(new Date("2026-01-01T00:00:00Z").toLocaleDateString(undefined, { month: "short" }));
|
|
564
|
+
expect(g.calendarMonthTicks[11]!.label).toBe(new Date("2026-12-01T00:00:00Z").toLocaleDateString(undefined, { month: "short" }));
|
|
565
|
+
// The padding cells exist but are blank.
|
|
566
|
+
expect(g.calendarCells.filter((c) => c.date > "2026-12-31").every((c) => !c.defined)).toBe(true);
|
|
567
|
+
});
|
|
496
568
|
it("honours explicit calendarStart even with no values", () => {
|
|
497
569
|
const g = buildChart({
|
|
498
570
|
type: "calendar",
|
|
@@ -628,6 +700,15 @@ describe("buildChart: heatmap", () => {
|
|
|
628
700
|
expect(g.heatmapRowTicks.map((t) => t.label)).toEqual(["North", "South"]);
|
|
629
701
|
expect(g.heatmapColTicks.map((t) => t.label)).toEqual(["Q1", "Q2", "Q3"]);
|
|
630
702
|
expect(g.heatmapLegend).toHaveLength(5);
|
|
703
|
+
// Column labels thin out when the cells get narrower than a label: 24
|
|
704
|
+
// hours in 520px is about 15px a cell, so every fourth one is written.
|
|
705
|
+
const hours = Array.from({ length: 24 }, (_, h) => `${String(h).padStart(2, "0")}:00`);
|
|
706
|
+
const dense = buildChart({ type: "heatmap", categories: hours, series: [{ label: "Mon", values: hours.map((_, i) => i) }] });
|
|
707
|
+
expect(dense.heatmapCells).toHaveLength(24);
|
|
708
|
+
expect(dense.heatmapColTicks.length).toBeLessThan(24);
|
|
709
|
+
expect(dense.heatmapColTicks[0]!.label).toBe("00:00");
|
|
710
|
+
const gap = dense.heatmapColTicks[1]!.x - dense.heatmapColTicks[0]!.x;
|
|
711
|
+
expect(gap).toBeGreaterThanOrEqual(60);
|
|
631
712
|
for (const c of g.heatmapCells) {
|
|
632
713
|
expect(["#0f172a", "#ffffff"]).toContain(c.textColor);
|
|
633
714
|
}
|
package/src/chart.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { buildChart, rowsToChartSpec, niceScale, sliceChartWindow, specToTreemap, specToCalendar, specToSankey, rowsToScatterSpec, rowsToGaugeSpec, boxStats, rowsToBoxSpec, chartScales } from './chart'
|
|
2
|
+
import { buildChart, rowsToChartSpec, niceScale, sliceChartWindow, specToTreemap, specToCalendar, specToSankey, rowsToScatterSpec, rowsToGaugeSpec, boxStats, rowsToBoxSpec, chartScales, layoutDataLabels } from './chart'
|
|
3
3
|
import type { ChartSpec } from './chart'
|
|
4
4
|
|
|
5
5
|
describe('niceScale', () => {
|
|
@@ -116,9 +116,37 @@ describe('buildChart', () => {
|
|
|
116
116
|
expect(g.donut!.total).toBe(10)
|
|
117
117
|
})
|
|
118
118
|
|
|
119
|
-
it('
|
|
120
|
-
const
|
|
119
|
+
it('data labels on a 100% chart read the share, the way the axis does', () => {
|
|
120
|
+
const spec: ChartSpec = {
|
|
121
|
+
type: 'bar',
|
|
122
|
+
stacked100: true,
|
|
123
|
+
categories: ['Q1', 'Q2'],
|
|
124
|
+
series: [
|
|
125
|
+
{ label: 'a', values: [25, 60] },
|
|
126
|
+
{ label: 'b', values: [75, 40] },
|
|
127
|
+
],
|
|
128
|
+
valueFormat: 'currency',
|
|
129
|
+
width: 400,
|
|
130
|
+
height: 200,
|
|
131
|
+
}
|
|
132
|
+
const geo = buildChart(spec)
|
|
133
|
+
const labels = layoutDataLabels(geo, { show: true, placement: 'inside', hideOverlap: false }, (v) => `$${v}`, { stacked: true, share: true })
|
|
134
|
+
expect(labels.map((l) => l.text)).toEqual(['25%', '60%', '75%', '40%'])
|
|
135
|
+
// Without the share flag the raw values stay, formatted the chart's way.
|
|
136
|
+
const raw = layoutDataLabels(geo, { show: true, placement: 'inside', hideOverlap: false }, (v) => `$${v}`, { stacked: true })
|
|
137
|
+
expect(raw.map((l) => l.text)).toEqual(['$25', '$60', '$75', '$40'])
|
|
138
|
+
// A formatter still receives the raw value.
|
|
139
|
+
const custom = layoutDataLabels(geo, { show: true, placement: 'inside', hideOverlap: false, formatter: (v) => `v${v}` }, String, { stacked: true, share: true })
|
|
140
|
+
expect(custom.map((l) => l.text)).toEqual(['v25', 'v60', 'v75', 'v40'])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('rotates x labels when the widest one does not fit its slot, and only then', () => {
|
|
144
|
+
const cats = Array.from({ length: 12 }, (_, i) => `Cat ${i}`)
|
|
145
|
+
const many = buildChart({ type: 'bar', categories: cats, series: [{ label: 's', values: Array(12).fill(1) }] })
|
|
121
146
|
expect(many.xLabelRotated).toBe(true)
|
|
147
|
+
// The same twelve labels on a chart wide enough for them stay upright.
|
|
148
|
+
const wide = buildChart({ type: 'bar', categories: cats, width: 1100, series: [{ label: 's', values: Array(12).fill(1) }] })
|
|
149
|
+
expect(wide.xLabelRotated).toBe(false)
|
|
122
150
|
const few = buildChart({ type: 'bar', categories: ['A', 'B'], series: [{ label: 's', values: [1, 2] }] })
|
|
123
151
|
expect(few.xLabelRotated).toBe(false)
|
|
124
152
|
})
|
|
@@ -317,6 +345,31 @@ describe('rowsToChartSpec', () => {
|
|
|
317
345
|
expect(spec.series[0]!.values).toEqual([100, 50, 50])
|
|
318
346
|
})
|
|
319
347
|
|
|
348
|
+
it('topN folds the tail INTO a category already named "Other" instead of duplicating it', () => {
|
|
349
|
+
// A real "Other" bucket in the data (a browser share's long tail) used
|
|
350
|
+
// to come out beside the folded one: two categories with one name, which
|
|
351
|
+
// is a duplicate key the renderer throws on.
|
|
352
|
+
const many = [
|
|
353
|
+
{ cat: 'A', v: 100, id: 1 },
|
|
354
|
+
{ cat: 'Other', v: 60, id: 2 },
|
|
355
|
+
{ cat: 'B', v: 50, id: 3 },
|
|
356
|
+
{ cat: 'C', v: 30, id: 4 },
|
|
357
|
+
{ cat: 'D', v: 20, id: 5 },
|
|
358
|
+
]
|
|
359
|
+
// "Other" is second by value and would have made the top 3 on its own.
|
|
360
|
+
const spec = rowsToChartSpec(many, { type: 'bar', category: 'cat', value: 'v', topN: 3, idField: 'id' })
|
|
361
|
+
expect(spec.categories).toEqual(['A', 'B', 'Other'])
|
|
362
|
+
expect(spec.series[0]!.values).toEqual([100, 50, 60 + 30 + 20])
|
|
363
|
+
expect(spec.series[0]!.rowIds).toEqual([[1], [3], [2, 4, 5]])
|
|
364
|
+
expect(new Set(spec.categories).size).toBe(spec.categories.length)
|
|
365
|
+
// Below the cut it is folded the same way, and a custom label is honoured.
|
|
366
|
+
const low = rowsToChartSpec(many.map((r) => (r.cat === 'Other' ? { ...r, v: 5 } : r)), { type: 'bar', category: 'cat', value: 'v', topN: 3 })
|
|
367
|
+
expect(low.categories).toEqual(['A', 'B', 'C', 'Other'])
|
|
368
|
+
expect(low.series[0]!.values).toEqual([100, 50, 30, 25])
|
|
369
|
+
const rest = rowsToChartSpec(many, { type: 'bar', category: 'cat', value: 'v', topN: 3, otherLabel: 'Rest' })
|
|
370
|
+
expect(rest.categories).toEqual(['A', 'Other', 'B', 'Rest'])
|
|
371
|
+
})
|
|
372
|
+
|
|
320
373
|
it('stacked100 flag passes through to the spec', () => {
|
|
321
374
|
const spec = rowsToChartSpec(rows, { type: 'bar', category: 'region', value: ['revenue', 'cost'], stacked100: true })
|
|
322
375
|
expect(spec.stacked100).toBe(true)
|