@svgrid/grid 3.0.2 → 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-DNbz94Ct.js → src-BKhZ6eXd.js} +10227 -9787
- package/dist/cdn/{src-BJ2opd0j.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-DyFA-QxA.js +0 -602
- package/dist/cdn/GridMenus-M2XP9t0_.js +0 -603
- package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
- package/dist/cdn/SvGridChart-DAb9RbRs.js +0 -1480
- package/dist/cdn/SvGridChart-Yl-IXZFO.js +0 -1481
- package/dist/cdn/SvGridChartPanel-DayQpDsR.js +0 -834
- package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +0 -810
- package/dist/cdn/SvGridChartView-CxfakxR0.js +0 -56
- package/dist/cdn/SvGridChartView-Dm_YEx9L.js +0 -55
- package/dist/cdn/chart-CKJRCPOk.js +0 -1652
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { axisScale, niceLogScale, project, errorSpan } from './chart-scale.js';
|
|
2
|
+
/**
|
|
3
|
+
* @internal Which mark a series draws.
|
|
4
|
+
*
|
|
5
|
+
* A series counts as a candle when it says so OR when it carries `ohlc` data,
|
|
6
|
+
* so a spec typed `'candlestick'` can still hold a plain volume bar series
|
|
7
|
+
* beside the prices. Boxes work the same way. Anything unrecognised falls back
|
|
8
|
+
* to `'bar'`, which is what pie and scatter have always relied on.
|
|
9
|
+
*/
|
|
10
|
+
export function kindOf(s, specType) {
|
|
11
|
+
const t = s.type ?? specType;
|
|
12
|
+
if (t === 'candlestick' || t === 'ohlc' || s.ohlc)
|
|
13
|
+
return 'candle';
|
|
14
|
+
if (t === 'boxplot' || s.boxes)
|
|
15
|
+
return 'box';
|
|
16
|
+
if (t === 'line' || t === 'area')
|
|
17
|
+
return t;
|
|
18
|
+
// A stream is a stacked area on a wiggle baseline; the offset is the spec's.
|
|
19
|
+
if (t === 'stream')
|
|
20
|
+
return 'area';
|
|
21
|
+
if (t === 'range-bar' || t === 'range-area' || t === 'lollipop' || t === 'dumbbell')
|
|
22
|
+
return t;
|
|
23
|
+
// A per-series scatter on a category axis: markers with no line.
|
|
24
|
+
if (t === 'scatter' && s.type === 'scatter')
|
|
25
|
+
return 'scatter';
|
|
26
|
+
return 'bar';
|
|
27
|
+
}
|
|
28
|
+
/** @internal Data domain for one axis, honoring stacking of its bar/area series.
|
|
29
|
+
* When `isLog` is true, non-positive values are discarded (log undefined)
|
|
30
|
+
* and the domain is rounded to decade boundaries instead of nice steps. */
|
|
31
|
+
export function axisDomain(list, categories, stacked, extra = [], isLog = false, axis = {}) {
|
|
32
|
+
let dMin = Infinity;
|
|
33
|
+
let dMax = -Infinity;
|
|
34
|
+
const note = (v) => {
|
|
35
|
+
if (!Number.isFinite(v))
|
|
36
|
+
return;
|
|
37
|
+
if (isLog && v <= 0)
|
|
38
|
+
return;
|
|
39
|
+
if (v < dMin)
|
|
40
|
+
dMin = v;
|
|
41
|
+
if (v > dMax)
|
|
42
|
+
dMax = v;
|
|
43
|
+
};
|
|
44
|
+
for (const v of extra)
|
|
45
|
+
note(v);
|
|
46
|
+
const stackable = list.filter((s) => s.kind === 'bar' || s.kind === 'area');
|
|
47
|
+
const lines = list.filter((s) => s.kind === 'line' || s.kind === 'scatter' || s.kind === 'lollipop');
|
|
48
|
+
// Range kinds have no zero baseline: a floating bar from 40 to 60 wants an
|
|
49
|
+
// axis that starts near 40, not at 0, so they are noted but never stacked.
|
|
50
|
+
for (const s of list) {
|
|
51
|
+
if (s.kind !== 'range-bar' && s.kind !== 'range-area' && s.kind !== 'dumbbell')
|
|
52
|
+
continue;
|
|
53
|
+
for (const v of s.values)
|
|
54
|
+
note(v);
|
|
55
|
+
for (const v of s.lowValues ?? [])
|
|
56
|
+
note(v);
|
|
57
|
+
}
|
|
58
|
+
// Totals per stack. Everything shares one stack when `stacked`; otherwise a
|
|
59
|
+
// bar or area series with a `stack` name joins that stack and every other
|
|
60
|
+
// series stands alone, so a lone series contributes its own values. Bars
|
|
61
|
+
// and areas never share a pile even under one name: they are drawn apart.
|
|
62
|
+
const stacks = new Map();
|
|
63
|
+
stackable.forEach((s, k) => {
|
|
64
|
+
const key = s.stack ? `${s.kind}:${s.stack}` : stacked ? s.kind : `#${k}`;
|
|
65
|
+
const list = stacks.get(key);
|
|
66
|
+
if (list)
|
|
67
|
+
list.push(s);
|
|
68
|
+
else
|
|
69
|
+
stacks.set(key, [s]);
|
|
70
|
+
});
|
|
71
|
+
for (const members of stacks.values()) {
|
|
72
|
+
if (members.length === 1) {
|
|
73
|
+
for (const v of members[0].values)
|
|
74
|
+
note(v);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
for (let i = 0; i < categories.length; i += 1) {
|
|
78
|
+
let pos = 0;
|
|
79
|
+
let neg = 0;
|
|
80
|
+
for (const s of members) {
|
|
81
|
+
const v = s.values[i] ?? 0;
|
|
82
|
+
if (v >= 0)
|
|
83
|
+
pos += v;
|
|
84
|
+
else
|
|
85
|
+
neg += v;
|
|
86
|
+
}
|
|
87
|
+
note(pos);
|
|
88
|
+
note(neg);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const s of lines)
|
|
92
|
+
for (const v of s.values)
|
|
93
|
+
note(v);
|
|
94
|
+
// Candles: note the HIGH and the LOW, not `values` (the closes), or every
|
|
95
|
+
// wick clips at the body. Note also that candles are deliberately absent
|
|
96
|
+
// from `stackable`, so the zero-baseline rule below does not fire for them:
|
|
97
|
+
// a price series running 180 to 195 keeps a readable domain instead of being
|
|
98
|
+
// flattened against zero.
|
|
99
|
+
for (const s of list) {
|
|
100
|
+
if (s.kind !== 'candle')
|
|
101
|
+
continue;
|
|
102
|
+
for (const k of s.ohlc ?? []) {
|
|
103
|
+
if (!k)
|
|
104
|
+
continue;
|
|
105
|
+
note(k.h);
|
|
106
|
+
note(k.l);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Boxes: the whisker ends and any outlier, for the same reason - `values`
|
|
110
|
+
// holds the medians, so a domain built from those alone would clip half of
|
|
111
|
+
// every box. Boxes are also absent from `stackable`, so a sample that never
|
|
112
|
+
// goes near zero keeps a readable domain.
|
|
113
|
+
for (const s of list) {
|
|
114
|
+
if (s.kind !== 'box')
|
|
115
|
+
continue;
|
|
116
|
+
for (const b of s.boxes ?? []) {
|
|
117
|
+
if (!b)
|
|
118
|
+
continue;
|
|
119
|
+
note(b.min);
|
|
120
|
+
note(b.max);
|
|
121
|
+
for (const o of b.outliers ?? [])
|
|
122
|
+
note(o);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Error bars extend past their own mark, so a whisker that leaves the plot is
|
|
126
|
+
// the same defect as a clipped candle wick.
|
|
127
|
+
for (const s of list) {
|
|
128
|
+
if (!s.errors)
|
|
129
|
+
continue;
|
|
130
|
+
s.errors.forEach((e, i) => {
|
|
131
|
+
const span = errorSpan(e, s.values[i] ?? 0);
|
|
132
|
+
if (!span)
|
|
133
|
+
return;
|
|
134
|
+
note(span.lo);
|
|
135
|
+
note(span.hi);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (dMin === Infinity) {
|
|
139
|
+
dMin = isLog ? 1 : 0;
|
|
140
|
+
dMax = isLog ? 10 : 1;
|
|
141
|
+
}
|
|
142
|
+
// Bar / area charts read against a zero baseline, so always include 0
|
|
143
|
+
// - but only on linear axes (0 is invalid in log). Lollipops grow from the
|
|
144
|
+
// axis the way bars do.
|
|
145
|
+
if ((stackable.length || list.some((s) => s.kind === 'lollipop')) && !isLog) {
|
|
146
|
+
dMin = Math.min(dMin, 0);
|
|
147
|
+
dMax = Math.max(dMax, 0);
|
|
148
|
+
}
|
|
149
|
+
if (isLog) {
|
|
150
|
+
const ls = niceLogScale(dMin, dMax);
|
|
151
|
+
// A pinned end on a log axis has to be positive to mean anything.
|
|
152
|
+
const lo = axis.min != null && axis.min > 0 ? axis.min : ls.min;
|
|
153
|
+
const hi = axis.max != null && axis.max > lo ? axis.max : ls.max;
|
|
154
|
+
if (lo === ls.min && hi === ls.max)
|
|
155
|
+
return ls;
|
|
156
|
+
return { min: lo, max: hi, step: ls.step, ticks: ls.ticks.filter((t) => t >= lo && t <= hi) };
|
|
157
|
+
}
|
|
158
|
+
return axisScale(dMin, dMax, axis);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Turn a laid-out chart's axes into functions, so a caller can draw its own
|
|
162
|
+
* marks in the same coordinates the built-in ones use.
|
|
163
|
+
*
|
|
164
|
+
* This is the custom-series seam. Rather than a registry of mark types, the
|
|
165
|
+
* chart hands over its geometry and its scales and lets the caller render
|
|
166
|
+
* whatever SVG it likes into the plot - which is the Svelte-shaped answer, and
|
|
167
|
+
* means a custom mark is ordinary markup rather than a plugin.
|
|
168
|
+
*
|
|
169
|
+
* Deriving the scale from `geo.axes` matters: the domain a chart drew against
|
|
170
|
+
* is the NICE-ROUNDED one, stretched to include zero for bar charts and any
|
|
171
|
+
* reference lines. Recomputing it from the data outside would land custom marks
|
|
172
|
+
* a few pixels off the built-in ones, in a way that looks like a rendering bug.
|
|
173
|
+
*
|
|
174
|
+
* Returns `null` for a chart with no cartesian axes (pie, gauge, treemap,
|
|
175
|
+
* sankey, calendar, radar, funnel), where plot coordinates mean nothing.
|
|
176
|
+
*/
|
|
177
|
+
export function chartScales(geo) {
|
|
178
|
+
const a = geo.axes;
|
|
179
|
+
if (!a)
|
|
180
|
+
return null;
|
|
181
|
+
const { x: px, y: py, w: pw, h: ph } = geo.plot;
|
|
182
|
+
const yFor = (dom) => (value) => {
|
|
183
|
+
let t = project(value, dom.min, dom.max, dom.log);
|
|
184
|
+
if (t === null)
|
|
185
|
+
return Number.NaN;
|
|
186
|
+
if (dom.reversed)
|
|
187
|
+
t = 1 - t;
|
|
188
|
+
return py + ph - t * ph;
|
|
189
|
+
};
|
|
190
|
+
const left = yFor(a.y);
|
|
191
|
+
const right = a.y2 ? yFor(a.y2) : left;
|
|
192
|
+
const invFor = (dom) => (y) => {
|
|
193
|
+
let t = ph === 0 ? 0 : (py + ph - y) / ph;
|
|
194
|
+
if (dom.reversed)
|
|
195
|
+
t = 1 - t;
|
|
196
|
+
if (!dom.log)
|
|
197
|
+
return dom.min + t * (dom.max - dom.min);
|
|
198
|
+
const lo = Math.log10(dom.min);
|
|
199
|
+
return 10 ** (lo + t * (Math.log10(dom.max) - lo));
|
|
200
|
+
};
|
|
201
|
+
// A continuous x axis (time / number) places by value; a category axis by
|
|
202
|
+
// index. `xOf(i)` on a continuous axis goes through the stored per-category
|
|
203
|
+
// values, so a custom mark at "category 3" lands where the built-in one did
|
|
204
|
+
// even when the categories are unevenly spaced in time.
|
|
205
|
+
const cx = a.x;
|
|
206
|
+
const span = cx ? cx.max - cx.min || 1 : 1;
|
|
207
|
+
const logSpan = cx?.log ? Math.log10(cx.max) - Math.log10(cx.min) || 1 : 1;
|
|
208
|
+
const xOfNum = (v) => {
|
|
209
|
+
if (!cx || !Number.isFinite(v))
|
|
210
|
+
return Number.NaN;
|
|
211
|
+
if (cx.log && v <= 0)
|
|
212
|
+
return Number.NaN;
|
|
213
|
+
let t = cx.log ? (Math.log10(v) - Math.log10(cx.min)) / logSpan : (v - cx.min) / span;
|
|
214
|
+
if (cx.reversed)
|
|
215
|
+
t = 1 - t;
|
|
216
|
+
return px + t * pw;
|
|
217
|
+
};
|
|
218
|
+
const xOfIndex = (i) => {
|
|
219
|
+
if (cx?.values) {
|
|
220
|
+
const v = cx.values[Math.round(i)];
|
|
221
|
+
if (v != null && Number.isFinite(v))
|
|
222
|
+
return xOfNum(v);
|
|
223
|
+
}
|
|
224
|
+
const t = px + a.slot * i + a.slot / 2;
|
|
225
|
+
return a.xReversed ? px + pw - (t - px) : t;
|
|
226
|
+
};
|
|
227
|
+
const xOfValue = (value) => {
|
|
228
|
+
if (cx) {
|
|
229
|
+
if (value instanceof Date)
|
|
230
|
+
return xOfNum(value.getTime());
|
|
231
|
+
if (typeof value === 'number')
|
|
232
|
+
return xOfNum(value);
|
|
233
|
+
const n = cx.type === 'time' ? Date.parse(value) : Number(value);
|
|
234
|
+
if (Number.isFinite(n))
|
|
235
|
+
return xOfNum(n);
|
|
236
|
+
// A label that did not parse can still be one of the categories.
|
|
237
|
+
const i = geo.xTicks.findIndex((t) => t.label === value);
|
|
238
|
+
return i >= 0 ? geo.xTicks[i].x : Number.NaN;
|
|
239
|
+
}
|
|
240
|
+
if (typeof value === 'number')
|
|
241
|
+
return xOfIndex(value);
|
|
242
|
+
const label = value instanceof Date ? value.toISOString() : value;
|
|
243
|
+
const i = a.labels ? a.labels.indexOf(label) : -1;
|
|
244
|
+
return i >= 0 ? xOfIndex(i) : Number.NaN;
|
|
245
|
+
};
|
|
246
|
+
const xInvertValue = (x) => {
|
|
247
|
+
let t = pw === 0 ? 0 : (x - px) / pw;
|
|
248
|
+
if (cx ? cx.reversed : a.xReversed)
|
|
249
|
+
t = 1 - t;
|
|
250
|
+
if (cx?.log)
|
|
251
|
+
return Math.pow(10, Math.log10(cx.min) + t * logSpan);
|
|
252
|
+
if (cx)
|
|
253
|
+
return cx.min + t * span;
|
|
254
|
+
return Math.max(0, Math.min(a.count - 1, t * a.count - 0.5));
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
xOf: xOfIndex,
|
|
258
|
+
yOf: (value, axis) => (axis === 'right' ? right(value) : left(value)),
|
|
259
|
+
xInvert: (x) => {
|
|
260
|
+
if (cx?.values) {
|
|
261
|
+
// Nearest category by value, which is what a hover on a time axis means.
|
|
262
|
+
const v = xInvertValue(x);
|
|
263
|
+
let best = 0;
|
|
264
|
+
let bestD = Infinity;
|
|
265
|
+
cx.values.forEach((cv, i) => {
|
|
266
|
+
const d = Math.abs(cv - v);
|
|
267
|
+
if (d < bestD) {
|
|
268
|
+
bestD = d;
|
|
269
|
+
best = i;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
return best;
|
|
273
|
+
}
|
|
274
|
+
const raw = Math.floor((x - px) / (a.slot || 1));
|
|
275
|
+
const i = a.xReversed ? a.count - 1 - raw : raw;
|
|
276
|
+
return Math.max(0, Math.min(a.count - 1, i));
|
|
277
|
+
},
|
|
278
|
+
yInvert: (y, axis) => (axis === 'right' && a.y2 ? invFor(a.y2)(y) : invFor(a.y)(y)),
|
|
279
|
+
xOfValue,
|
|
280
|
+
xInvertValue,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* @internal Fold the flat spec shortcuts (`xType`, `yScale`, `xAxisTitle`, ...)
|
|
285
|
+
* into three axis objects with every default filled in, so a layout reads one
|
|
286
|
+
* shape and never has to know which spelling the caller used.
|
|
287
|
+
*/
|
|
288
|
+
export function resolveAxes(spec) {
|
|
289
|
+
const x = spec.xAxis ?? {};
|
|
290
|
+
const y = spec.yAxis ?? {};
|
|
291
|
+
const y2 = spec.y2Axis ?? {};
|
|
292
|
+
const fill = (a, defaults) => ({
|
|
293
|
+
...a,
|
|
294
|
+
type: a.type ?? defaults.type,
|
|
295
|
+
scale: a.scale ?? defaults.scale,
|
|
296
|
+
title: a.title ?? defaults.title,
|
|
297
|
+
gridLines: a.gridLines ?? defaults.gridLines,
|
|
298
|
+
labels: a.labels !== false,
|
|
299
|
+
nice: a.nice !== false,
|
|
300
|
+
reversed: a.reversed === true,
|
|
301
|
+
});
|
|
302
|
+
return {
|
|
303
|
+
x: fill(x, { type: spec.xType ?? 'category', scale: 'linear', title: spec.xAxisTitle, gridLines: false }),
|
|
304
|
+
y: fill(y, { type: 'category', scale: spec.yScale ?? 'linear', title: spec.yAxisTitle, gridLines: true }),
|
|
305
|
+
y2: fill(y2, { type: 'category', scale: spec.y2Scale ?? 'linear', title: spec.y2AxisTitle, gridLines: false }),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/** Vertical room the frame text takes, per line. */
|
|
309
|
+
const TITLE_H = 22;
|
|
310
|
+
const SUBTITLE_H = 16;
|
|
311
|
+
const CAPTION_H = 16;
|
|
312
|
+
/**
|
|
313
|
+
* @internal Place the title, subtitle and caption and measure the room they
|
|
314
|
+
* take. The title and subtitle sit centred above the plot; the caption sits
|
|
315
|
+
* left-aligned under it. A family adds `top` to its top pad and `bottom` to its
|
|
316
|
+
* bottom pad, and the text positions are already absolute.
|
|
317
|
+
*/
|
|
318
|
+
export function chartFrame(spec, width, height) {
|
|
319
|
+
const title = (spec.title ?? '').trim();
|
|
320
|
+
const subtitle = (spec.subtitle ?? '').trim();
|
|
321
|
+
const caption = (spec.caption ?? '').trim();
|
|
322
|
+
let top = 0;
|
|
323
|
+
let titlePos = null;
|
|
324
|
+
let subtitlePos = null;
|
|
325
|
+
if (title) {
|
|
326
|
+
top += TITLE_H;
|
|
327
|
+
titlePos = { x: width / 2, y: top - 6, text: title };
|
|
328
|
+
}
|
|
329
|
+
if (subtitle) {
|
|
330
|
+
top += SUBTITLE_H;
|
|
331
|
+
subtitlePos = { x: width / 2, y: top - 4, text: subtitle };
|
|
332
|
+
}
|
|
333
|
+
if (top)
|
|
334
|
+
top += 4;
|
|
335
|
+
let bottom = 0;
|
|
336
|
+
let captionPos = null;
|
|
337
|
+
if (caption) {
|
|
338
|
+
bottom = CAPTION_H;
|
|
339
|
+
captionPos = { x: 8, y: height - 5, text: caption };
|
|
340
|
+
}
|
|
341
|
+
return { top, bottom, title: titlePos, subtitle: subtitlePos, caption: captionPos };
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* @internal Format one value-axis tick through the axis' own `formatter` or
|
|
345
|
+
* `format`, falling back to the spec's `valueFormat`.
|
|
346
|
+
*/
|
|
347
|
+
export function axisTickLabel(value, index, axis, spec, fmt) {
|
|
348
|
+
if (axis.formatter)
|
|
349
|
+
return axis.formatter(value, index);
|
|
350
|
+
return fmt(value, axis.format ?? spec.valueFormat, spec);
|
|
351
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cartesian layouts: bar / line / area (combo, stacked, dual axis), horizontal
|
|
3
|
+
* bars and scatter. Every mark is positioned in SVG coordinates against the
|
|
4
|
+
* axes chart-axes.ts resolves.
|
|
5
|
+
*/
|
|
6
|
+
import type { ChartDataLabelConfig, ChartGeometry, ChartMarkerShape, ChartSeries, LayoutCtx } from './chart-types.js';
|
|
7
|
+
type Step = NonNullable<ChartSeries['step']>;
|
|
8
|
+
/**
|
|
9
|
+
* Build an SVG path from a list of (x,y) pairs, optionally smoothed via
|
|
10
|
+
* monotone cubic interpolation (preserves local extrema - no overshoots).
|
|
11
|
+
* Breaks the path at `defined === false` gaps unless `connectNulls` is set,
|
|
12
|
+
* and draws steps instead of straight segments when `step` is set.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildLinePath(pts: Array<{
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
defined: boolean;
|
|
18
|
+
}>, smooth: boolean, opts?: {
|
|
19
|
+
connectNulls?: boolean;
|
|
20
|
+
step?: Step;
|
|
21
|
+
}): string;
|
|
22
|
+
/**
|
|
23
|
+
* The baseline a stacked area sits on per category. `'zero'` is the axis;
|
|
24
|
+
* `'silhouette'` centres the total on zero; `'wiggle'` picks the baseline
|
|
25
|
+
* that minimises the weighted change of slope across the layers (Byron and
|
|
26
|
+
* Wattenberg's stream graph), which is what makes a stream read as flowing
|
|
27
|
+
* rather than as a stack. Gaps count as 0 so the stack stays continuous.
|
|
28
|
+
*/
|
|
29
|
+
export declare function streamBaseline(layers: ReadonlyArray<ReadonlyArray<number>>, offset: 'zero' | 'wiggle' | 'silhouette'): number[];
|
|
30
|
+
/**
|
|
31
|
+
* The SVG path for a point marker of `shape` centred on (cx, cy) with
|
|
32
|
+
* half-size `size`. `'circle'` and `'none'` return `''`: circles draw as
|
|
33
|
+
* `<circle>` (which keeps every existing selector working), and `'none'` draws
|
|
34
|
+
* nothing. Exposed so a legend swatch or a custom mark can match the series.
|
|
35
|
+
*/
|
|
36
|
+
export declare function markerPath(shape: ChartMarkerShape, cx: number, cy: number, size: number): string;
|
|
37
|
+
/** One positioned data label. `onBar` means it sits on a mark and should be
|
|
38
|
+
* painted in the contrast colour. */
|
|
39
|
+
export type ChartDataLabel = {
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
text: string;
|
|
43
|
+
anchor: 'start' | 'middle' | 'end';
|
|
44
|
+
onBar: boolean;
|
|
45
|
+
series: string;
|
|
46
|
+
/** Rotation in degrees about (x, y), from `dataLabels.rotation`. */
|
|
47
|
+
angle?: number;
|
|
48
|
+
/** A leader from the mark to a label that was pushed away from it. */
|
|
49
|
+
leader?: {
|
|
50
|
+
x1: number;
|
|
51
|
+
y1: number;
|
|
52
|
+
x2: number;
|
|
53
|
+
y2: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Place the data labels for a laid-out cartesian chart: one per bar and one
|
|
58
|
+
* per defined line point, positioned by `placement`, formatted by `formatter`
|
|
59
|
+
* (or `fmt`), and thinned so no label overlaps an earlier one when
|
|
60
|
+
* `hideOverlap` is on (the default).
|
|
61
|
+
*
|
|
62
|
+
* Pure, so the renderer derives it and a test can assert on it. Pie slices
|
|
63
|
+
* label themselves (a percentage at the centroid) and are not handled here.
|
|
64
|
+
*/
|
|
65
|
+
export declare function layoutDataLabels(geo: ChartGeometry, cfg: ChartDataLabelConfig, fmt: (v: number, series?: string) => string, opts?: {
|
|
66
|
+
stacked?: boolean;
|
|
67
|
+
dense?: boolean;
|
|
68
|
+
share?: boolean;
|
|
69
|
+
}): ChartDataLabel[];
|
|
70
|
+
/** @internal Lay out the scatter family. Called by buildChart. */
|
|
71
|
+
export declare function layoutScatter(ctx: LayoutCtx): ChartGeometry;
|
|
72
|
+
/**
|
|
73
|
+
* @internal Lay out the horizontal bars family. Called by buildChart.
|
|
74
|
+
* Categories run down the left, bars grow rightward. Bars-only (no combo).
|
|
75
|
+
*/
|
|
76
|
+
export declare function layoutHorizontalBars(ctx: LayoutCtx): ChartGeometry;
|
|
77
|
+
/** @internal Lay out the cartesian family. Called by buildChart. */
|
|
78
|
+
export declare function layoutCartesian(ctx: LayoutCtx): ChartGeometry;
|
|
79
|
+
/**
|
|
80
|
+
* @internal Lay out the bullet family. Called by buildChart.
|
|
81
|
+
*
|
|
82
|
+
* One row per category: qualitative ranges (poor / ok / good) as full-height
|
|
83
|
+
* bands, the measure as a bar at half height, the target as a tick. The
|
|
84
|
+
* first series is the measure; `targets` on it (or a second series) sets the
|
|
85
|
+
* ticks; `bulletRanges` (or `gaugeRanges`) draws the bands behind every row.
|
|
86
|
+
*/
|
|
87
|
+
export declare function layoutBullet(ctx: LayoutCtx): ChartGeometry;
|
|
88
|
+
export {};
|