@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,405 @@
|
|
|
1
|
+
// Number formatting lives in its own leaf (see chart-format.ts for why) and
|
|
2
|
+
// is re-exported here so every importer of chart-scale keeps working.
|
|
3
|
+
export { fmtTick, formatChartValue } from './chart-format.js';
|
|
4
|
+
/** Series colours used when a {@link ChartSeries} sets none, in order. */
|
|
5
|
+
export const DEFAULT_PALETTE = [
|
|
6
|
+
'#2563eb',
|
|
7
|
+
'#16a34a',
|
|
8
|
+
'#f59e0b',
|
|
9
|
+
'#ef4444',
|
|
10
|
+
'#8b5cf6',
|
|
11
|
+
'#0ea5e9',
|
|
12
|
+
'#ec4899',
|
|
13
|
+
'#14b8a6',
|
|
14
|
+
];
|
|
15
|
+
/** @internal Two-decimal rounding for SVG coordinates. */
|
|
16
|
+
export function round(n) {
|
|
17
|
+
return Math.round(n * 100) / 100;
|
|
18
|
+
}
|
|
19
|
+
/** @internal Nice-number rounding used by niceScale. */
|
|
20
|
+
export function niceNum(range, roundIt) {
|
|
21
|
+
if (range <= 0)
|
|
22
|
+
return 1;
|
|
23
|
+
const exp = Math.floor(Math.log10(range));
|
|
24
|
+
const f = range / Math.pow(10, exp);
|
|
25
|
+
let nf;
|
|
26
|
+
if (roundIt)
|
|
27
|
+
nf = f < 1.5 ? 1 : f < 3 ? 2 : f < 7 ? 5 : 10;
|
|
28
|
+
else
|
|
29
|
+
nf = f <= 1 ? 1 : f <= 2 ? 2 : f <= 5 ? 5 : 10;
|
|
30
|
+
return nf * Math.pow(10, exp);
|
|
31
|
+
}
|
|
32
|
+
// ---- Color helpers for heatmap / pattern fills ----------------------
|
|
33
|
+
/** Built-in sequential ramp (light cyan -> deep blue), perception-friendly. */
|
|
34
|
+
const SEQUENTIAL_STOPS = ['#eff6ff', '#bfdbfe', '#60a5fa', '#2563eb', '#1e3a8a'];
|
|
35
|
+
/** Built-in diverging ramp (red -> neutral -> blue). Use for signed data. */
|
|
36
|
+
const DIVERGING_STOPS = ['#b91c1c', '#fca5a5', '#f1f5f9', '#93c5fd', '#1d4ed8'];
|
|
37
|
+
/** Dark-theme ramps. The low (sequential) / neutral (diverging) end sits just
|
|
38
|
+
* above the dark grid surface instead of near-white, so empty / low cells read
|
|
39
|
+
* as "cold" rather than as glaring white rectangles. */
|
|
40
|
+
const SEQUENTIAL_STOPS_DARK = ['#1c2c4d', '#1d4ed8', '#3b82f6', '#60a5fa', '#bae6fd'];
|
|
41
|
+
const DIVERGING_STOPS_DARK = ['#f87171', '#b91c1c', '#222b3d', '#1d4ed8', '#60a5fa'];
|
|
42
|
+
/** @internal Pick the colour-ramp stops for a heatmap / calendar. */
|
|
43
|
+
export function resolveColorScale(scale, vMin, vMax, theme = 'light') {
|
|
44
|
+
if (Array.isArray(scale) && scale.length >= 2)
|
|
45
|
+
return scale;
|
|
46
|
+
const dark = theme === 'dark';
|
|
47
|
+
if (scale === 'diverging' || (scale == null && vMin < 0 && vMax > 0)) {
|
|
48
|
+
return dark ? DIVERGING_STOPS_DARK : DIVERGING_STOPS;
|
|
49
|
+
}
|
|
50
|
+
return dark ? SEQUENTIAL_STOPS_DARK : SEQUENTIAL_STOPS;
|
|
51
|
+
}
|
|
52
|
+
/** Sample a hex color from an array of hex stops at fractional position t.
|
|
53
|
+
* Linearly interpolates between the two nearest stops in RGB space. */
|
|
54
|
+
export function sampleGradient(stops, t) {
|
|
55
|
+
if (!stops.length)
|
|
56
|
+
return '#888';
|
|
57
|
+
const clamped = Math.max(0, Math.min(1, t));
|
|
58
|
+
if (stops.length === 1)
|
|
59
|
+
return stops[0];
|
|
60
|
+
const pos = clamped * (stops.length - 1);
|
|
61
|
+
const i = Math.floor(pos);
|
|
62
|
+
const frac = pos - i;
|
|
63
|
+
const a = hexToRgb(stops[i]);
|
|
64
|
+
const b = hexToRgb(stops[Math.min(stops.length - 1, i + 1)]);
|
|
65
|
+
if (!a || !b)
|
|
66
|
+
return stops[i] ?? '#888';
|
|
67
|
+
const lerp = (x, y) => Math.round(x + (y - x) * frac);
|
|
68
|
+
const toHex = (n) => n.toString(16).padStart(2, '0');
|
|
69
|
+
return '#' + toHex(lerp(a.r, b.r)) + toHex(lerp(a.g, b.g)) + toHex(lerp(a.b, b.b));
|
|
70
|
+
}
|
|
71
|
+
function hexToRgb(hex) {
|
|
72
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
|
|
73
|
+
if (!m)
|
|
74
|
+
return null;
|
|
75
|
+
const n = parseInt(m[1], 16);
|
|
76
|
+
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
|
|
77
|
+
}
|
|
78
|
+
/** Pick a black or white text color that has the better contrast against
|
|
79
|
+
* the given background. Uses the WCAG relative-luminance heuristic. */
|
|
80
|
+
export function pickContrastText(bgHex) {
|
|
81
|
+
const rgb = hexToRgb(bgHex);
|
|
82
|
+
if (!rgb)
|
|
83
|
+
return '#0f172a';
|
|
84
|
+
const lin = (c) => {
|
|
85
|
+
const s = c / 255;
|
|
86
|
+
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
87
|
+
};
|
|
88
|
+
const L = 0.2126 * lin(rgb.r) + 0.7152 * lin(rgb.g) + 0.0722 * lin(rgb.b);
|
|
89
|
+
return L > 0.5 ? '#0f172a' : '#ffffff';
|
|
90
|
+
}
|
|
91
|
+
/** Pick the largest power of 10 that fits at the bottom of [min,max], and
|
|
92
|
+
* the smallest that covers the top, then enumerate decade boundaries. Used
|
|
93
|
+
* by log-scale axes (yScale: 'log'). */
|
|
94
|
+
export function niceLogScale(min, max) {
|
|
95
|
+
// Only positive values are plottable on a log scale; callers should
|
|
96
|
+
// strip non-positive values before passing them in.
|
|
97
|
+
if (!Number.isFinite(min) || min <= 0)
|
|
98
|
+
min = 1;
|
|
99
|
+
if (!Number.isFinite(max) || max <= min)
|
|
100
|
+
max = min * 10;
|
|
101
|
+
const lo = Math.floor(Math.log10(min));
|
|
102
|
+
const hi = Math.ceil(Math.log10(max));
|
|
103
|
+
const ticks = [];
|
|
104
|
+
for (let p = lo; p <= hi; p += 1)
|
|
105
|
+
ticks.push(Math.pow(10, p));
|
|
106
|
+
return { min: Math.pow(10, lo), max: Math.pow(10, hi), step: 10, ticks };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* @internal Tick values for a logarithmic axis between two positive bounds:
|
|
110
|
+
* every decade, plus the 2 and 5 of each decade when the range spans fewer
|
|
111
|
+
* than three, so a two-decade axis does not read as three lonely labels.
|
|
112
|
+
*/
|
|
113
|
+
export function logTicks(min, max) {
|
|
114
|
+
if (!(min > 0) || !(max > min))
|
|
115
|
+
return [];
|
|
116
|
+
const lo = Math.floor(Math.log10(min));
|
|
117
|
+
const hi = Math.ceil(Math.log10(max));
|
|
118
|
+
const minors = hi - lo < 3 ? [2, 5] : [];
|
|
119
|
+
const out = [];
|
|
120
|
+
for (let p = lo; p <= hi; p += 1) {
|
|
121
|
+
const d = Math.pow(10, p);
|
|
122
|
+
for (const m of [1, ...minors]) {
|
|
123
|
+
const v = d * m;
|
|
124
|
+
if (v >= min * 0.999 && v <= max * 1.001)
|
|
125
|
+
out.push(v);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
/** @internal Map a value to a fractional position [0..1] across the axis domain.
|
|
131
|
+
* Pass the appropriate fn into projection code so linear / log share the
|
|
132
|
+
* same plumbing. Returns null for non-positive values on log. */
|
|
133
|
+
export function project(value, min, max, isLog) {
|
|
134
|
+
if (!Number.isFinite(value))
|
|
135
|
+
return null;
|
|
136
|
+
if (isLog) {
|
|
137
|
+
if (value <= 0 || min <= 0)
|
|
138
|
+
return null;
|
|
139
|
+
return (Math.log10(value) - Math.log10(min)) / (Math.log10(max) - Math.log10(min));
|
|
140
|
+
}
|
|
141
|
+
return (value - min) / (max - min);
|
|
142
|
+
}
|
|
143
|
+
/** Round a [min,max] domain out to nice tick boundaries. */
|
|
144
|
+
export function niceScale(min, max, tickCount = 4) {
|
|
145
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
146
|
+
min = 0;
|
|
147
|
+
max = 1;
|
|
148
|
+
}
|
|
149
|
+
if (min === max) {
|
|
150
|
+
if (min === 0)
|
|
151
|
+
max = 1;
|
|
152
|
+
else {
|
|
153
|
+
min = Math.min(0, min);
|
|
154
|
+
max = Math.max(0, max);
|
|
155
|
+
}
|
|
156
|
+
if (min === max)
|
|
157
|
+
max = min + 1;
|
|
158
|
+
}
|
|
159
|
+
const range = niceNum(max - min, false);
|
|
160
|
+
const step = niceNum(range / Math.max(1, tickCount), true);
|
|
161
|
+
const nMin = Math.floor(min / step) * step;
|
|
162
|
+
const nMax = Math.ceil(max / step) * step;
|
|
163
|
+
const ticks = [];
|
|
164
|
+
for (let v = nMin; v <= nMax + step * 0.5; v += step)
|
|
165
|
+
ticks.push(round(v));
|
|
166
|
+
return { min: nMin, max: nMax, step, ticks };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* A value-axis domain that honours an axis config on top of the data extent:
|
|
170
|
+
* a pinned `min` / `max` (hard edges - marks past them are clipped), an exact
|
|
171
|
+
* `tickInterval`, a `tickCount`, and `nice: false` for the raw extent with
|
|
172
|
+
* evenly spaced ticks. With no config this is exactly {@link niceScale}.
|
|
173
|
+
*/
|
|
174
|
+
export function axisScale(dataMin, dataMax, axis = {}) {
|
|
175
|
+
let min = axis.min ?? dataMin;
|
|
176
|
+
let max = axis.max ?? dataMax;
|
|
177
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
178
|
+
min = Number.isFinite(min) ? min : 0;
|
|
179
|
+
max = Number.isFinite(max) ? max : min + 1;
|
|
180
|
+
}
|
|
181
|
+
const nice = axis.nice !== false;
|
|
182
|
+
const interval = axis.tickInterval;
|
|
183
|
+
if (interval && interval > 0 && Number.isFinite(interval)) {
|
|
184
|
+
// An explicit interval is the whole axis: the ends land on multiples of it
|
|
185
|
+
// (unless pinned) and every tick is one interval apart.
|
|
186
|
+
if (nice && axis.min == null)
|
|
187
|
+
min = Math.floor(min / interval) * interval;
|
|
188
|
+
if (nice && axis.max == null)
|
|
189
|
+
max = Math.ceil(max / interval) * interval;
|
|
190
|
+
if (max <= min)
|
|
191
|
+
max = min + interval;
|
|
192
|
+
const ticks = [];
|
|
193
|
+
const first = Math.ceil(min / interval - 1e-9) * interval;
|
|
194
|
+
// Cap the count so a tiny interval on a huge domain cannot hang the layout.
|
|
195
|
+
// `+ 0` turns the -0 that `Math.ceil` hands back at the origin into a plain 0.
|
|
196
|
+
for (let v = first, n = 0; v <= max + interval * 1e-6 && n < 1000; v += interval, n += 1)
|
|
197
|
+
ticks.push(round(v) + 0);
|
|
198
|
+
return { min, max, step: interval, ticks };
|
|
199
|
+
}
|
|
200
|
+
if (nice) {
|
|
201
|
+
const ns = niceScale(min, max, axis.tickCount ?? 4);
|
|
202
|
+
if (axis.min == null && axis.max == null)
|
|
203
|
+
return ns;
|
|
204
|
+
// A pinned end wins over the rounding, and ticks past it go.
|
|
205
|
+
const lo = axis.min ?? ns.min;
|
|
206
|
+
const hi = axis.max ?? ns.max;
|
|
207
|
+
return { min: lo, max: hi, step: ns.step, ticks: ns.ticks.filter((t) => t >= lo - 1e-9 && t <= hi + 1e-9) };
|
|
208
|
+
}
|
|
209
|
+
if (max <= min)
|
|
210
|
+
max = min + 1;
|
|
211
|
+
const n = Math.max(1, Math.round(axis.tickCount ?? 4));
|
|
212
|
+
const step = (max - min) / n;
|
|
213
|
+
const ticks = Array.from({ length: n + 1 }, (_, i) => round(min + step * i));
|
|
214
|
+
return { min, max, step, ticks };
|
|
215
|
+
}
|
|
216
|
+
/** @internal One day in milliseconds. */
|
|
217
|
+
export const DAY = 86400000;
|
|
218
|
+
/**
|
|
219
|
+
* @internal Calendar-aligned tick timestamps across [min, max], at most about
|
|
220
|
+
* `target` of them.
|
|
221
|
+
*
|
|
222
|
+
* The unit is the finest of day, week (Mondays), month, quarter, year and
|
|
223
|
+
* multiples of years whose count fits the target, and every tick sits on a
|
|
224
|
+
* calendar boundary in UTC (the axis parses 'YYYY-MM-DD' categories as UTC
|
|
225
|
+
* midnight). The old version stepped a fixed 30 or 365 days from the epoch,
|
|
226
|
+
* so a tick labelled "2025" stood weeks away from New Year and a thirty-month
|
|
227
|
+
* axis carried two labels on a plot with room for eight.
|
|
228
|
+
*/
|
|
229
|
+
export function dateTicks(tMin, tMax, target = 6) {
|
|
230
|
+
if (!(tMax > tMin))
|
|
231
|
+
return [tMin];
|
|
232
|
+
// A little over the target beats dropping to the next unit, which halves
|
|
233
|
+
// or thirds the count: ten quarters on a plot with room for nine.
|
|
234
|
+
const want = Math.max(2, target) * 1.3;
|
|
235
|
+
const span = tMax - tMin;
|
|
236
|
+
// Steps as [days, months]: a year is twelve months, so multiples of twelve
|
|
237
|
+
// land on the first of January.
|
|
238
|
+
const steps = [[1, 0], [2, 0], [7, 0], [14, 0], [0, 1], [0, 3], [0, 6], [0, 12], [0, 24], [0, 60], [0, 120]];
|
|
239
|
+
let [d, m] = steps[steps.length - 1];
|
|
240
|
+
for (const [sd, sm] of steps) {
|
|
241
|
+
if (span / (sd * DAY + sm * 30.44 * DAY) <= want) {
|
|
242
|
+
d = sd;
|
|
243
|
+
m = sm;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const from = new Date(tMin);
|
|
248
|
+
const out = [];
|
|
249
|
+
if (d) {
|
|
250
|
+
let t = Date.UTC(from.getUTCFullYear(), from.getUTCMonth(), from.getUTCDate());
|
|
251
|
+
// Weekly steps sit on Mondays; UTC day 0 is Sunday.
|
|
252
|
+
if (d >= 7)
|
|
253
|
+
t += ((8 - new Date(t).getUTCDay()) % 7) * DAY;
|
|
254
|
+
while (t < tMin)
|
|
255
|
+
t += d * DAY;
|
|
256
|
+
for (; t <= tMax + 1; t += d * DAY)
|
|
257
|
+
out.push(t);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
let months = Math.ceil((from.getUTCFullYear() * 12 + from.getUTCMonth()) / m) * m;
|
|
261
|
+
const at = (mo) => Date.UTC(Math.floor(mo / 12), mo % 12, 1);
|
|
262
|
+
if (at(months) < tMin)
|
|
263
|
+
months += m;
|
|
264
|
+
for (let t = at(months); t <= tMax + 1; months += m, t = at(months))
|
|
265
|
+
out.push(t);
|
|
266
|
+
}
|
|
267
|
+
return out.length < 2 ? [tMin, tMax] : out;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Tick positions for an ordinal (evenly spaced) date axis, as INDICES into
|
|
271
|
+
* `times`.
|
|
272
|
+
*
|
|
273
|
+
* A time axis can put a tick anywhere, because x is a function of the
|
|
274
|
+
* timestamp. An ordinal axis cannot: x is a function of the index, so a tick
|
|
275
|
+
* has to land on a point that exists. This picks the first point of each
|
|
276
|
+
* calendar unit - day, week, month, year, whichever gets closest to `target`
|
|
277
|
+
* ticks without going over - so labels sit on real sessions and a weekend or a
|
|
278
|
+
* holiday never stretches the spacing.
|
|
279
|
+
*/
|
|
280
|
+
export function ordinalDateTicks(times, target = 6) {
|
|
281
|
+
if (times.length <= 1)
|
|
282
|
+
return times.length ? [0] : [];
|
|
283
|
+
const keyOf = {
|
|
284
|
+
day: (d) => `${d.getUTCFullYear()}-${d.getUTCMonth()}-${d.getUTCDate()}`,
|
|
285
|
+
// Weeks start on Monday: day 0 of the epoch was a Thursday, so +3 puts
|
|
286
|
+
// the boundary there (+4 put it on Sunday, and weekly ticks sat on Sundays).
|
|
287
|
+
week: (d) => Math.floor((Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) / DAY + 3) / 7),
|
|
288
|
+
month: (d) => `${d.getUTCFullYear()}-${d.getUTCMonth()}`,
|
|
289
|
+
year: (d) => d.getUTCFullYear(),
|
|
290
|
+
};
|
|
291
|
+
let chosen = null;
|
|
292
|
+
for (const unit of ['day', 'week', 'month', 'year']) {
|
|
293
|
+
const at = [];
|
|
294
|
+
let prev = null;
|
|
295
|
+
for (let i = 0; i < times.length; i += 1) {
|
|
296
|
+
const t = times[i];
|
|
297
|
+
if (!Number.isFinite(t))
|
|
298
|
+
continue;
|
|
299
|
+
const k = keyOf[unit](new Date(t));
|
|
300
|
+
if (k !== prev)
|
|
301
|
+
at.push(i);
|
|
302
|
+
prev = k;
|
|
303
|
+
}
|
|
304
|
+
chosen = at;
|
|
305
|
+
if (at.length <= target * 2)
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
let out = chosen ?? [];
|
|
309
|
+
// Even the coarsest unit can be too dense (a decade of yearly points), and a
|
|
310
|
+
// single trading day yields one boundary. Thin, or fall back to plain strides.
|
|
311
|
+
if (out.length > target) {
|
|
312
|
+
const stride = Math.ceil(out.length / target);
|
|
313
|
+
out = out.filter((_, i) => i % stride === 0);
|
|
314
|
+
}
|
|
315
|
+
if (out.length < 2) {
|
|
316
|
+
const stride = Math.max(1, Math.ceil(times.length / target));
|
|
317
|
+
out = times.map((_, i) => i).filter((i) => i % stride === 0);
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
/** @internal Date label sized to the gap between ticks: days and weeks read
|
|
322
|
+
* "Jan 5", months and quarters "Jun 25", years "2025". Read in UTC: the axis
|
|
323
|
+
* parses 'YYYY-MM-DD' as UTC midnight, and a local-time read of that in the
|
|
324
|
+
* Americas labelled every tick with the day before. Sized to the tick step
|
|
325
|
+
* rather than the axis span, so a thirty-month axis on quarters does not
|
|
326
|
+
* read "2024, 2024, 2024, 2025". */
|
|
327
|
+
export function fmtDate(t, step) {
|
|
328
|
+
const d = new Date(t);
|
|
329
|
+
if (step < 28 * DAY)
|
|
330
|
+
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', timeZone: 'UTC' });
|
|
331
|
+
if (step < 360 * DAY)
|
|
332
|
+
return d.toLocaleDateString(undefined, { month: 'short', year: '2-digit', timeZone: 'UTC' });
|
|
333
|
+
return String(d.getUTCFullYear());
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* @internal A category on a date axis, written out for a tooltip or a
|
|
337
|
+
* crosshair pill: "Jun 2025" when every category is the first of a month,
|
|
338
|
+
* "2025" when every one is New Year, otherwise "Jun 1, 2025". `grain` is
|
|
339
|
+
* what {@link dateGrain} found for the axis.
|
|
340
|
+
*/
|
|
341
|
+
export function fmtDateFull(t, grain = 'day') {
|
|
342
|
+
const d = new Date(t);
|
|
343
|
+
if (grain === 'year')
|
|
344
|
+
return String(d.getUTCFullYear());
|
|
345
|
+
if (grain === 'month')
|
|
346
|
+
return d.toLocaleDateString(undefined, { month: 'short', year: 'numeric', timeZone: 'UTC' });
|
|
347
|
+
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', timeZone: 'UTC' });
|
|
348
|
+
}
|
|
349
|
+
/** @internal The calendar grain a list of UTC timestamps sits on. */
|
|
350
|
+
export function dateGrain(times) {
|
|
351
|
+
let month = true, year = true, any = false;
|
|
352
|
+
for (const t of times) {
|
|
353
|
+
if (!Number.isFinite(t))
|
|
354
|
+
continue;
|
|
355
|
+
any = true;
|
|
356
|
+
const d = new Date(t);
|
|
357
|
+
if (d.getUTCDate() !== 1 || d.getUTCHours() !== 0) {
|
|
358
|
+
month = false;
|
|
359
|
+
year = false;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
if (d.getUTCMonth() !== 0)
|
|
363
|
+
year = false;
|
|
364
|
+
}
|
|
365
|
+
return !any ? 'day' : year ? 'year' : month ? 'month' : 'day';
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* @internal Category-axis labels, thinned so they do not overlap.
|
|
369
|
+
*
|
|
370
|
+
* A category axis used to emit one tick per category however many there were.
|
|
371
|
+
* At 5000 categories that is 5000 `<text>` nodes stacked into an unreadable
|
|
372
|
+
* grey band - the labels were the single biggest thing the renderer had to put
|
|
373
|
+
* in the DOM, and none of them could be read. The time and ordinal-time axes
|
|
374
|
+
* already thinned themselves (`dateTicks` / `ordinalDateTicks`); this brings
|
|
375
|
+
* the plain category axis in line.
|
|
376
|
+
*
|
|
377
|
+
* The first category always gets a label, so a thinned axis still starts where
|
|
378
|
+
* the data does.
|
|
379
|
+
*/
|
|
380
|
+
export function thinCategoryTicks(categories, xCenter, slot, rotated) {
|
|
381
|
+
// Rotated labels run diagonally and pack far tighter than upright ones. 18px
|
|
382
|
+
// is deliberately just under the spacing a 40-category chart at 800px already
|
|
383
|
+
// had, so charts that read fine before are untouched and only genuinely
|
|
384
|
+
// overlapping axes get thinned.
|
|
385
|
+
const minPx = rotated ? 18 : 60;
|
|
386
|
+
const step = Math.max(1, Math.ceil(minPx / Math.max(slot, 0.001)));
|
|
387
|
+
const out = [];
|
|
388
|
+
for (let i = 0; i < categories.length; i += step)
|
|
389
|
+
out.push({ label: categories[i], x: xCenter(i) });
|
|
390
|
+
return out;
|
|
391
|
+
}
|
|
392
|
+
/** @internal Normalize one `errors` entry to an absolute low/high pair around `value`. */
|
|
393
|
+
export function errorSpan(e, value) {
|
|
394
|
+
if (e == null)
|
|
395
|
+
return null;
|
|
396
|
+
if (typeof e === 'number') {
|
|
397
|
+
if (!Number.isFinite(e))
|
|
398
|
+
return null;
|
|
399
|
+
const m = Math.abs(e);
|
|
400
|
+
return { lo: value - m, hi: value + m };
|
|
401
|
+
}
|
|
402
|
+
if (!Number.isFinite(e.lo) || !Number.isFinite(e.hi))
|
|
403
|
+
return null;
|
|
404
|
+
return { lo: Math.min(e.lo, e.hi), hi: Math.max(e.lo, e.hi) };
|
|
405
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statistics the chart engine computes from raw values: five-number summaries,
|
|
3
|
+
* regression and moving-average overlays. DOM-free, spec-free.
|
|
4
|
+
*/
|
|
5
|
+
import type { BoxStats, ChartReducer } from './chart-types.js';
|
|
6
|
+
export { linearTrend, pearson } from './chart-trend.js';
|
|
7
|
+
/**
|
|
8
|
+
* Five-number summary of a raw sample, with the 1.5 IQR whisker rule.
|
|
9
|
+
*
|
|
10
|
+
* Whiskers stop at the last observation INSIDE the fence rather than at the
|
|
11
|
+
* fence itself, which is what makes them read as real data; anything past them
|
|
12
|
+
* comes back in `outliers`. Quartiles use linear interpolation between the two
|
|
13
|
+
* neighbouring order statistics.
|
|
14
|
+
*
|
|
15
|
+
* Returns `null` for an empty sample, so a category with no observations is a
|
|
16
|
+
* gap rather than a box drawn at zero.
|
|
17
|
+
*/
|
|
18
|
+
export declare function boxStats(sample: ReadonlyArray<number>, whisker?: number): BoxStats | null;
|
|
19
|
+
/**
|
|
20
|
+
* A regression's result: the fitted value at every index (NaN where the
|
|
21
|
+
* source was non-finite or the model cannot take the value), R-squared
|
|
22
|
+
* against the finite source values, the equation, and `predict`, the model
|
|
23
|
+
* at any x. With no `xs` the x of a value is its index, which is what a
|
|
24
|
+
* category chart's overlay wants; a scatter passes its points' x values, so
|
|
25
|
+
* the fit is y on x and `predict` draws the curve across the plot.
|
|
26
|
+
*/
|
|
27
|
+
export type RegressionFit = {
|
|
28
|
+
fitted: number[];
|
|
29
|
+
r2: number;
|
|
30
|
+
equation: string;
|
|
31
|
+
predict: (x: number) => number;
|
|
32
|
+
};
|
|
33
|
+
/** R-squared of a fit: 1 minus residual over total variance across the
|
|
34
|
+
* indices where both the value and the fit are finite. 0 when there is no
|
|
35
|
+
* variance to explain. */
|
|
36
|
+
export declare function rSquared(values: ReadonlyArray<number>, fitted: ReadonlyArray<number>): number;
|
|
37
|
+
/**
|
|
38
|
+
* Least-squares polynomial of `degree` (clamped to 1..6) through
|
|
39
|
+
* (x, values[i]), x the index or the caller's `xs`. The x values are
|
|
40
|
+
* normalised to [-1, 1] before the normal equations are solved, so a
|
|
41
|
+
* degree-6 fit over a thousand points does not lose the low coefficients to
|
|
42
|
+
* rounding. Non-finite values are skipped and fitted as NaN.
|
|
43
|
+
*/
|
|
44
|
+
export declare function polynomialFit(values: ReadonlyArray<number>, degree: number, xs?: ReadonlyArray<number>): RegressionFit;
|
|
45
|
+
/** Ordinary least squares of values on their index, or on `xs`, as a {@link RegressionFit}. */
|
|
46
|
+
export declare function linearFit(values: ReadonlyArray<number>, xs?: ReadonlyArray<number>): RegressionFit;
|
|
47
|
+
/** Fit y = a * e^(b x) by least squares on ln y, x the index or `xs`;
|
|
48
|
+
* values at or below zero cannot be logged and are skipped (fitted as NaN). */
|
|
49
|
+
export declare function exponentialFit(values: ReadonlyArray<number>, xs?: ReadonlyArray<number>): RegressionFit;
|
|
50
|
+
/** Fit y = a + b ln(x + 1) by least squares (x the index, shifted by one so
|
|
51
|
+
* the first point has a logarithm), or y = a + b ln(x) on `xs`, where
|
|
52
|
+
* points with x at or below zero are skipped. */
|
|
53
|
+
export declare function logarithmicFit(values: ReadonlyArray<number>, xs?: ReadonlyArray<number>): RegressionFit;
|
|
54
|
+
/** Fit y = a (x + 1)^b by least squares on ln y against ln(x + 1), or
|
|
55
|
+
* y = a x^b on `xs`; values (and x on `xs`) at or below zero are skipped
|
|
56
|
+
* (fitted as NaN). */
|
|
57
|
+
export declare function powerFit(values: ReadonlyArray<number>, xs?: ReadonlyArray<number>): RegressionFit;
|
|
58
|
+
/** Simple moving average over a window of `period` values. Window centres
|
|
59
|
+
* trail to the right (typical for time-series). NaN for points before the
|
|
60
|
+
* window is full. */
|
|
61
|
+
export declare function simpleMovingAverage(values: number[], period: number): number[];
|
|
62
|
+
/** Exponential moving average. Smoothing factor alpha = 2 / (period + 1). */
|
|
63
|
+
export declare function exponentialMovingAverage(values: number[], period: number): number[];
|
|
64
|
+
/** The reducers that need every observation, not a running sum and count. */
|
|
65
|
+
export declare function reducerNeedsSamples(reducer: ChartReducer): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Collapse a group's observations to one number. `sum` / `avg` / `count` are
|
|
68
|
+
* what `rowsToChartSpec` always did; the rest are the ones a pivot table
|
|
69
|
+
* offers and a chart panel was missing: `min`, `max`, `median`, `first`,
|
|
70
|
+
* `last`, `countDistinct`, and `pN` for the Nth percentile (`'p90'`). An
|
|
71
|
+
* empty group reduces to 0 for `sum` / `count` and to NaN otherwise, so a
|
|
72
|
+
* missing bucket draws as a gap rather than as a fake zero.
|
|
73
|
+
*/
|
|
74
|
+
export declare function reduceValues(samples: ReadonlyArray<number>, reducer: ChartReducer): number;
|
|
75
|
+
/** The p-th percentile (0..100) by linear interpolation between order statistics. */
|
|
76
|
+
export declare function percentile(samples: ReadonlyArray<number>, p: number): number;
|
|
77
|
+
/** How `rowsToChartSpec` groups dates when `bucket` is set. */
|
|
78
|
+
export type ChartTimeBucket = 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
79
|
+
/**
|
|
80
|
+
* The ISO date (`YYYY-MM-DD`, UTC) that starts the bucket containing `date`:
|
|
81
|
+
* the day itself, the Monday of its week, the first of its month, quarter or
|
|
82
|
+
* year. Returns `''` for a value that is not a date, so the caller can drop
|
|
83
|
+
* the row rather than file it under 1970.
|
|
84
|
+
*/
|
|
85
|
+
export declare function bucketStart(date: string | number | Date, bucket: ChartTimeBucket): string;
|
|
86
|
+
/** What {@link binValues} returns: one category per bin plus the edges. */
|
|
87
|
+
export type ChartBins = {
|
|
88
|
+
/** Bin midpoints as strings, ready to be the categories of a numeric axis. */
|
|
89
|
+
categories: string[];
|
|
90
|
+
/** Observations per bin. */
|
|
91
|
+
counts: number[];
|
|
92
|
+
/** Bin edges, one more than there are bins. */
|
|
93
|
+
edges: number[];
|
|
94
|
+
/** Width of every bin. */
|
|
95
|
+
width: number;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Bin a raw sample for a histogram. The bin count comes from `bins`, from a
|
|
99
|
+
* `binWidth`, or from a rule: `'sturges'` (default, log2 n + 1), `'fd'`
|
|
100
|
+
* (Freedman-Diaconis, 2 IQR / cbrt n) or `'sqrt'`. The range is the data's
|
|
101
|
+
* extent unless `min` / `max` pin it; the top edge is inclusive. Edges are
|
|
102
|
+
* rounded to a sensible precision for the width, so labels read as `10, 20,
|
|
103
|
+
* 30` rather than `10.000000001`.
|
|
104
|
+
*/
|
|
105
|
+
export declare function binValues(values: ReadonlyArray<number>, opts?: {
|
|
106
|
+
bins?: number;
|
|
107
|
+
binWidth?: number;
|
|
108
|
+
method?: 'sturges' | 'fd' | 'sqrt';
|
|
109
|
+
min?: number;
|
|
110
|
+
max?: number;
|
|
111
|
+
}): ChartBins;
|