@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.
Files changed (281) hide show
  1. package/README.md +2 -1
  2. package/dist/GridMenus.svelte +44 -2
  3. package/dist/SvChartMenu.svelte +69 -0
  4. package/dist/SvChartMenu.svelte.d.ts +11 -0
  5. package/dist/SvChartPanes.svelte +113 -0
  6. package/dist/SvChartPanes.svelte.d.ts +20 -0
  7. package/dist/SvGrid.controller.svelte.d.ts +110 -39
  8. package/dist/SvGrid.controller.svelte.js +655 -200
  9. package/dist/SvGrid.css +105 -2
  10. package/dist/SvGrid.svelte +386 -514
  11. package/dist/SvGrid.types.d.ts +210 -9
  12. package/dist/SvGridCellEditor.svelte +17 -0
  13. package/dist/SvGridChart.svelte +2257 -329
  14. package/dist/SvGridChart.svelte.d.ts +6 -106
  15. package/dist/SvGridChart.types.d.ts +338 -0
  16. package/dist/SvGridChart.types.js +1 -0
  17. package/dist/SvGridChartBuilder.svelte +323 -0
  18. package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
  19. package/dist/SvGridChartPanel.svelte +221 -275
  20. package/dist/SvGridChartPickers.svelte +417 -0
  21. package/dist/SvGridChartPickers.svelte.d.ts +70 -0
  22. package/dist/SvGridChartView.svelte +13 -14
  23. package/dist/SvModal.svelte +7 -1
  24. package/dist/SvModal.svelte.d.ts +5 -0
  25. package/dist/ai.d.ts +40 -8
  26. package/dist/ai.js +135 -24
  27. package/dist/aria.d.ts +11 -0
  28. package/dist/build-api.js +108 -22
  29. package/dist/cdn/GridMenus-7kbpnnBW.js +635 -0
  30. package/dist/cdn/GridMenus-BL7ZgQvU.js +636 -0
  31. package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
  32. package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
  33. package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
  34. package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
  35. package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
  36. package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
  37. package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
  38. package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
  39. package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
  40. package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
  41. package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
  42. package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
  43. package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
  44. package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
  45. package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
  46. package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
  47. package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
  48. package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
  49. package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
  50. package/dist/cdn/SvModal-CIZWCcad.js +397 -0
  51. package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
  52. package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
  53. package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
  54. package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
  55. package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
  56. package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
  57. package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
  58. package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
  59. package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
  60. package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
  61. package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
  62. package/dist/cdn/dismissable-DAHetSNk.js +44 -0
  63. package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
  64. package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
  65. package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
  66. package/dist/cdn/{src-DNbz94Ct.js → src-BKhZ6eXd.js} +10227 -9787
  67. package/dist/cdn/{src-BJ2opd0j.js → src-V1uu8iE9.js} +6568 -6128
  68. package/dist/cdn/svgrid.js +26 -15
  69. package/dist/cdn/svgrid.svelte-external.js +26 -15
  70. package/dist/chart-axes.d.ts +53 -0
  71. package/dist/chart-axes.js +351 -0
  72. package/dist/chart-cartesian.d.ts +88 -0
  73. package/dist/chart-cartesian.js +1862 -0
  74. package/dist/chart-decimate.d.ts +51 -0
  75. package/dist/chart-decimate.js +199 -0
  76. package/dist/chart-export-pdf.d.ts +47 -0
  77. package/dist/chart-export-pdf.js +187 -0
  78. package/dist/chart-export.d.ts +2 -1
  79. package/dist/chart-export.js +18 -3
  80. package/dist/chart-financial.d.ts +91 -0
  81. package/dist/chart-financial.js +175 -0
  82. package/dist/chart-flow.d.ts +10 -0
  83. package/dist/chart-flow.js +298 -0
  84. package/dist/chart-format.d.ts +18 -0
  85. package/dist/chart-format.js +65 -0
  86. package/dist/chart-grid.d.ts +8 -0
  87. package/dist/chart-grid.js +190 -0
  88. package/dist/chart-hierarchy.d.ts +17 -0
  89. package/dist/chart-hierarchy.js +146 -0
  90. package/dist/chart-indicators.d.ts +115 -0
  91. package/dist/chart-indicators.js +389 -0
  92. package/dist/chart-messages.d.ts +95 -0
  93. package/dist/chart-messages.js +92 -0
  94. package/dist/chart-motion.d.ts +16 -0
  95. package/dist/chart-motion.js +141 -0
  96. package/dist/chart-panel-messages.d.ts +265 -0
  97. package/dist/chart-panel-messages.js +285 -0
  98. package/dist/chart-pivot.d.ts +87 -0
  99. package/dist/chart-pivot.js +156 -0
  100. package/dist/chart-polar.d.ts +25 -0
  101. package/dist/chart-polar.js +700 -0
  102. package/dist/chart-samples.d.ts +16 -0
  103. package/dist/chart-samples.js +153 -0
  104. package/dist/chart-scale.d.ts +113 -0
  105. package/dist/chart-scale.js +405 -0
  106. package/dist/chart-stats.d.ts +111 -0
  107. package/dist/chart-stats.js +419 -0
  108. package/dist/chart-stream.d.ts +45 -0
  109. package/dist/chart-stream.js +91 -0
  110. package/dist/chart-summary.d.ts +27 -0
  111. package/dist/chart-summary.js +205 -0
  112. package/dist/chart-sync.svelte.d.ts +32 -0
  113. package/dist/chart-sync.svelte.js +23 -0
  114. package/dist/chart-table.d.ts +35 -0
  115. package/dist/chart-table.js +165 -0
  116. package/dist/chart-trend.d.ts +19 -0
  117. package/dist/chart-trend.js +61 -0
  118. package/dist/chart-types.d.ts +1456 -0
  119. package/dist/chart-types.js +6 -0
  120. package/dist/chart-validate.d.ts +43 -0
  121. package/dist/chart-validate.js +272 -0
  122. package/dist/chart-zoom.d.ts +40 -0
  123. package/dist/chart-zoom.js +145 -0
  124. package/dist/chart.d.ts +186 -865
  125. package/dist/chart.js +460 -2204
  126. package/dist/clipboard.d.ts +1 -0
  127. package/dist/clipboard.js +137 -18
  128. package/dist/column-resize.d.ts +3 -0
  129. package/dist/column-resize.js +4 -1
  130. package/dist/columns.js +3 -0
  131. package/dist/command-context.d.ts +19 -0
  132. package/dist/command-context.js +116 -0
  133. package/dist/core.d.ts +7 -0
  134. package/dist/createPopoverSelect.svelte.d.ts +2 -2
  135. package/dist/editing.d.ts +2 -8
  136. package/dist/editing.js +262 -33
  137. package/dist/fill-patterns.d.ts +0 -5
  138. package/dist/fill-patterns.js +56 -2
  139. package/dist/grid-messages.d.ts +10 -3
  140. package/dist/grid-messages.js +5 -0
  141. package/dist/history.d.ts +83 -0
  142. package/dist/history.js +133 -0
  143. package/dist/index.d.ts +24 -6
  144. package/dist/index.js +20 -2
  145. package/dist/keyboard-handlers.js +88 -31
  146. package/dist/keyboard.d.ts +55 -0
  147. package/dist/keyboard.js +132 -0
  148. package/dist/menus.d.ts +3 -1
  149. package/dist/menus.js +7 -2
  150. package/dist/merges.d.ts +97 -0
  151. package/dist/merges.js +147 -0
  152. package/dist/row-resize.d.ts +3 -0
  153. package/dist/row-resize.js +14 -0
  154. package/dist/scroll-sync.d.ts +1 -0
  155. package/dist/scroll-sync.js +24 -0
  156. package/dist/selection.d.ts +16 -0
  157. package/dist/selection.js +108 -16
  158. package/dist/shortcut-registry.d.ts +145 -0
  159. package/dist/shortcut-registry.js +44 -0
  160. package/dist/svgrid-wrapper.types.d.ts +213 -22
  161. package/dist/virtualization/virtualizer.js +38 -17
  162. package/package.json +6 -1
  163. package/src/GridMenus.svelte +44 -2
  164. package/src/SvChartMenu.svelte +69 -0
  165. package/src/SvChartPanes.svelte +113 -0
  166. package/src/SvChartPanes.test.ts +66 -0
  167. package/src/SvGrid.controller.svelte.ts +629 -172
  168. package/src/SvGrid.css +105 -2
  169. package/src/SvGrid.svelte +386 -514
  170. package/src/SvGrid.types.ts +188 -8
  171. package/src/SvGridCellEditor.svelte +17 -0
  172. package/src/SvGridChart.svelte +2257 -329
  173. package/src/SvGridChart.test.ts +1419 -1
  174. package/src/SvGridChart.types.ts +336 -0
  175. package/src/SvGridChartBuilder.svelte +323 -0
  176. package/src/SvGridChartPanel.svelte +221 -275
  177. package/src/SvGridChartPickers.svelte +417 -0
  178. package/src/SvGridChartView.svelte +13 -14
  179. package/src/SvModal.svelte +7 -1
  180. package/src/SvModal.test.ts +9 -0
  181. package/src/ai.test.ts +57 -2
  182. package/src/ai.ts +166 -22
  183. package/src/aria.d.ts +11 -0
  184. package/src/build-api.coverage.test.ts +184 -0
  185. package/src/build-api.ts +107 -18
  186. package/src/chart-axes.test.ts +573 -0
  187. package/src/chart-axes.ts +342 -0
  188. package/src/chart-cartesian.ts +1863 -0
  189. package/src/chart-decimate.test.ts +249 -0
  190. package/src/chart-decimate.ts +186 -0
  191. package/src/chart-export-pdf.test.ts +91 -0
  192. package/src/chart-export-pdf.ts +207 -0
  193. package/src/chart-export.test.ts +14 -0
  194. package/src/chart-export.ts +13 -3
  195. package/src/chart-financial.ts +208 -0
  196. package/src/chart-flow.ts +286 -0
  197. package/src/chart-format.ts +67 -0
  198. package/src/chart-grid.ts +179 -0
  199. package/src/chart-hierarchy.ts +135 -0
  200. package/src/chart-indicators.test.ts +443 -0
  201. package/src/chart-indicators.ts +384 -0
  202. package/src/chart-messages.test.ts +21 -0
  203. package/src/chart-messages.ts +184 -0
  204. package/src/chart-motion.test.ts +71 -0
  205. package/src/chart-motion.ts +148 -0
  206. package/src/chart-panel-messages.test.ts +29 -0
  207. package/src/chart-panel-messages.ts +550 -0
  208. package/src/chart-pivot.test.ts +54 -0
  209. package/src/chart-pivot.ts +225 -0
  210. package/src/chart-polar.ts +674 -0
  211. package/src/chart-samples.ts +106 -0
  212. package/src/chart-scale.ts +394 -0
  213. package/src/chart-series-types.test.ts +714 -0
  214. package/src/chart-stats.ts +409 -0
  215. package/src/chart-stream.test.ts +75 -0
  216. package/src/chart-stream.ts +114 -0
  217. package/src/chart-summary.test.ts +90 -0
  218. package/src/chart-summary.ts +197 -0
  219. package/src/chart-sync.svelte.ts +41 -0
  220. package/src/chart-table.test.ts +76 -0
  221. package/src/chart-table.ts +178 -0
  222. package/src/chart-trend.ts +47 -0
  223. package/src/chart-types.ts +1309 -0
  224. package/src/chart-validate.test.ts +100 -0
  225. package/src/chart-validate.ts +247 -0
  226. package/src/chart-zoom.test.ts +137 -0
  227. package/src/chart-zoom.ts +124 -0
  228. package/src/chart.coverage.test.ts +81 -0
  229. package/src/chart.test.ts +56 -3
  230. package/src/chart.ts +557 -2784
  231. package/src/clipboard.test.ts +151 -0
  232. package/src/clipboard.ts +145 -16
  233. package/src/column-resize.ts +7 -1
  234. package/src/columns.ts +2 -0
  235. package/src/command-context.test.ts +200 -0
  236. package/src/command-context.ts +137 -0
  237. package/src/core.ts +7 -0
  238. package/src/editing.test.ts +281 -3
  239. package/src/editing.ts +256 -38
  240. package/src/fill-patterns.test.ts +35 -0
  241. package/src/fill-patterns.ts +56 -2
  242. package/src/grid-messages.ts +16 -3
  243. package/src/history.test.ts +196 -0
  244. package/src/history.ts +162 -0
  245. package/src/icon-seam.test.ts +3 -2
  246. package/src/index.ts +150 -2
  247. package/src/keyboard-handlers.coverage.test.ts +126 -6
  248. package/src/keyboard-handlers.ts +91 -30
  249. package/src/keyboard-shortcuts.seam.test.ts +200 -0
  250. package/src/keyboard.test.ts +124 -1
  251. package/src/keyboard.ts +142 -0
  252. package/src/menus.test.ts +26 -0
  253. package/src/menus.ts +10 -3
  254. package/src/merges.test.ts +112 -0
  255. package/src/merges.ts +168 -0
  256. package/src/pivot.grid.test.ts +40 -5
  257. package/src/row-resize.test.ts +44 -0
  258. package/src/row-resize.ts +17 -0
  259. package/src/scroll-sync.test.ts +42 -1
  260. package/src/scroll-sync.ts +25 -0
  261. package/src/selection.multi-range.test.ts +34 -0
  262. package/src/selection.test.ts +30 -2
  263. package/src/selection.ts +111 -19
  264. package/src/shortcut-registry.test.ts +97 -0
  265. package/src/shortcut-registry.ts +181 -0
  266. package/src/svgrid-wrapper.types.ts +184 -13
  267. package/src/svgrid.behavior.test.ts +135 -0
  268. package/src/svgrid.charting.test.ts +513 -3
  269. package/src/svgrid.context-menu.test.ts +31 -0
  270. package/src/svgrid.new-features.wrapper.test.ts +10 -3
  271. package/src/virtualization/virtualizer.ts +39 -17
  272. package/dist/cdn/GridMenus-DyFA-QxA.js +0 -602
  273. package/dist/cdn/GridMenus-M2XP9t0_.js +0 -603
  274. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
  275. package/dist/cdn/SvGridChart-DAb9RbRs.js +0 -1480
  276. package/dist/cdn/SvGridChart-Yl-IXZFO.js +0 -1481
  277. package/dist/cdn/SvGridChartPanel-DayQpDsR.js +0 -834
  278. package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +0 -810
  279. package/dist/cdn/SvGridChartView-CxfakxR0.js +0 -56
  280. package/dist/cdn/SvGridChartView-Dm_YEx9L.js +0 -55
  281. package/dist/cdn/chart-CKJRCPOk.js +0 -1652
@@ -0,0 +1,419 @@
1
+ // The two trend primitives live in their own leaf (see chart-trend.ts for
2
+ // why) and are re-exported here so every importer of chart-stats keeps working.
3
+ export { linearTrend, pearson } from './chart-trend.js';
4
+ /**
5
+ * Five-number summary of a raw sample, with the 1.5 IQR whisker rule.
6
+ *
7
+ * Whiskers stop at the last observation INSIDE the fence rather than at the
8
+ * fence itself, which is what makes them read as real data; anything past them
9
+ * comes back in `outliers`. Quartiles use linear interpolation between the two
10
+ * neighbouring order statistics.
11
+ *
12
+ * Returns `null` for an empty sample, so a category with no observations is a
13
+ * gap rather than a box drawn at zero.
14
+ */
15
+ export function boxStats(sample, whisker = 1.5) {
16
+ const v = sample.filter((n) => Number.isFinite(n)).slice().sort((a, b) => a - b);
17
+ if (!v.length)
18
+ return null;
19
+ const q = (p) => {
20
+ const pos = (v.length - 1) * p;
21
+ const lo = Math.floor(pos);
22
+ const hi = Math.ceil(pos);
23
+ return lo === hi ? v[lo] : v[lo] + (v[hi] - v[lo]) * (pos - lo);
24
+ };
25
+ const q1 = q(0.25);
26
+ const median = q(0.5);
27
+ const q3 = q(0.75);
28
+ const fenceLo = q1 - whisker * (q3 - q1);
29
+ const fenceHi = q3 + whisker * (q3 - q1);
30
+ const inside = v.filter((n) => n >= fenceLo && n <= fenceHi);
31
+ const outliers = v.filter((n) => n < fenceLo || n > fenceHi);
32
+ return {
33
+ // `inside` can only be empty if every point is an outlier, which the fence
34
+ // rule makes impossible (q1 and q3 are always within it) - but a degenerate
35
+ // sample should still produce a box rather than `undefined` coordinates.
36
+ min: inside.length ? inside[0] : v[0],
37
+ q1,
38
+ median,
39
+ q3,
40
+ max: inside.length ? inside[inside.length - 1] : v[v.length - 1],
41
+ ...(outliers.length ? { outliers } : {}),
42
+ };
43
+ }
44
+ const NO_FIT = (values) => ({ fitted: values.map(() => NaN), r2: 0, equation: '', predict: () => NaN });
45
+ /** The x of each value: the caller's, else the index. */
46
+ const xsOf = (values, xs) => (xs ? values.map((_, i) => xs[i] ?? NaN) : values.map((_, i) => i));
47
+ /** R-squared of a fit: 1 minus residual over total variance across the
48
+ * indices where both the value and the fit are finite. 0 when there is no
49
+ * variance to explain. */
50
+ export function rSquared(values, fitted) {
51
+ let n = 0;
52
+ let mean = 0;
53
+ for (let i = 0; i < values.length; i += 1) {
54
+ if (Number.isFinite(values[i]) && Number.isFinite(fitted[i])) {
55
+ n += 1;
56
+ mean += values[i];
57
+ }
58
+ }
59
+ if (n < 2)
60
+ return 0;
61
+ mean /= n;
62
+ let ssRes = 0;
63
+ let ssTot = 0;
64
+ for (let i = 0; i < values.length; i += 1) {
65
+ if (!Number.isFinite(values[i]) || !Number.isFinite(fitted[i]))
66
+ continue;
67
+ ssRes += (values[i] - fitted[i]) ** 2;
68
+ ssTot += (values[i] - mean) ** 2;
69
+ }
70
+ if (ssTot === 0)
71
+ return ssRes === 0 ? 1 : 0;
72
+ return Math.max(0, Math.min(1, 1 - ssRes / ssTot));
73
+ }
74
+ const fmtCoef = (c) => (Math.abs(c) >= 1000 || (Math.abs(c) < 0.001 && c !== 0) ? c.toExponential(2) : String(Math.round(c * 1000) / 1000));
75
+ /** Solve a small linear system by Gaussian elimination with partial pivoting;
76
+ * null when singular. */
77
+ function solve(a, b) {
78
+ const n = b.length;
79
+ const m = a.map((row, i) => [...row, b[i]]);
80
+ for (let c = 0; c < n; c += 1) {
81
+ let p = c;
82
+ for (let r = c + 1; r < n; r += 1)
83
+ if (Math.abs(m[r][c]) > Math.abs(m[p][c]))
84
+ p = r;
85
+ if (Math.abs(m[p][c]) < 1e-12)
86
+ return null;
87
+ [m[c], m[p]] = [m[p], m[c]];
88
+ for (let r = 0; r < n; r += 1) {
89
+ if (r === c)
90
+ continue;
91
+ const f = m[r][c] / m[c][c];
92
+ for (let k = c; k <= n; k += 1)
93
+ m[r][k] -= f * m[c][k];
94
+ }
95
+ }
96
+ return m.map((row, i) => row[n] / row[i]);
97
+ }
98
+ /**
99
+ * Least-squares polynomial of `degree` (clamped to 1..6) through
100
+ * (x, values[i]), x the index or the caller's `xs`. The x values are
101
+ * normalised to [-1, 1] before the normal equations are solved, so a
102
+ * degree-6 fit over a thousand points does not lose the low coefficients to
103
+ * rounding. Non-finite values are skipped and fitted as NaN.
104
+ */
105
+ export function polynomialFit(values, degree, xs) {
106
+ const d = Math.max(1, Math.min(6, Math.round(degree)));
107
+ const n = values.length;
108
+ const nan = NO_FIT(values);
109
+ const X = xsOf(values, xs);
110
+ const idx = [];
111
+ for (let i = 0; i < n; i += 1)
112
+ if (Number.isFinite(values[i]) && Number.isFinite(X[i]))
113
+ idx.push(i);
114
+ if (idx.length <= d)
115
+ return nan;
116
+ let lo = Infinity;
117
+ let hi = -Infinity;
118
+ for (const i of idx) {
119
+ lo = Math.min(lo, X[i]);
120
+ hi = Math.max(hi, X[i]);
121
+ }
122
+ if (!xs) {
123
+ lo = 0;
124
+ hi = Math.max(1, n - 1);
125
+ }
126
+ const scale = Math.max(hi - lo, 1e-12);
127
+ const nx = (x) => (2 * (x - lo)) / scale - 1;
128
+ // Normal equations: sum x^(j+k) c_k = sum x^j y.
129
+ const a = Array.from({ length: d + 1 }, () => new Array(d + 1).fill(0));
130
+ const b = new Array(d + 1).fill(0);
131
+ for (const i of idx) {
132
+ const x = nx(X[i]);
133
+ const y = values[i];
134
+ const pow = [1];
135
+ for (let k = 1; k <= 2 * d; k += 1)
136
+ pow.push(pow[k - 1] * x);
137
+ for (let j = 0; j <= d; j += 1) {
138
+ b[j] += pow[j] * y;
139
+ for (let k = 0; k <= d; k += 1)
140
+ a[j][k] += pow[j + k];
141
+ }
142
+ }
143
+ const c = solve(a, b);
144
+ if (!c)
145
+ return nan;
146
+ const predict = (x) => {
147
+ const u = nx(x);
148
+ let y = 0;
149
+ let p = 1;
150
+ for (let k = 0; k <= d; k += 1) {
151
+ y += c[k] * p;
152
+ p *= u;
153
+ }
154
+ return y;
155
+ };
156
+ const fitted = values.map((v, i) => (Number.isFinite(v) && Number.isFinite(X[i]) ? predict(X[i]) : NaN));
157
+ const terms = c.map((ck, k) => (k === 0 ? fmtCoef(ck) : `${fmtCoef(ck)} u^${k}`)).reverse();
158
+ const u = xs ? `u = 2(x - ${fmtCoef(lo)})/${fmtCoef(scale)} - 1` : `u = 2x/${scale} - 1`;
159
+ return { fitted, r2: rSquared(values, fitted), equation: `y = ${terms.join(' + ')} (${u})`, predict };
160
+ }
161
+ /** Ordinary least squares of values on their index, or on `xs`, as a {@link RegressionFit}. */
162
+ export function linearFit(values, xs) {
163
+ const X = xsOf(values, xs);
164
+ const { slope, intercept, ok } = lineThrough(X, values);
165
+ if (!ok)
166
+ return NO_FIT(values);
167
+ const predict = (x) => slope * x + intercept;
168
+ const fitted = values.map((v, i) => (Number.isFinite(v) && Number.isFinite(X[i]) ? predict(X[i]) : NaN));
169
+ return { fitted, r2: rSquared(values, fitted), equation: `y = ${fmtCoef(slope)} x + ${fmtCoef(intercept)}`, predict };
170
+ }
171
+ /** Fit y = a * e^(b x) by least squares on ln y, x the index or `xs`;
172
+ * values at or below zero cannot be logged and are skipped (fitted as NaN). */
173
+ export function exponentialFit(values, xs) {
174
+ const X = xsOf(values, xs);
175
+ const logs = values.map((v) => (Number.isFinite(v) && v > 0 ? Math.log(v) : NaN));
176
+ const { slope: b, intercept, ok } = lineThrough(X, logs);
177
+ if (!ok)
178
+ return NO_FIT(values);
179
+ const a = Math.exp(intercept);
180
+ const predict = (x) => a * Math.exp(b * x);
181
+ const fitted = values.map((v, i) => (Number.isFinite(v) && v > 0 && Number.isFinite(X[i]) ? predict(X[i]) : NaN));
182
+ return { fitted, r2: rSquared(values, fitted), equation: `y = ${fmtCoef(a)} e^(${fmtCoef(b)} x)`, predict };
183
+ }
184
+ /** Fit y = a + b ln(x + 1) by least squares (x the index, shifted by one so
185
+ * the first point has a logarithm), or y = a + b ln(x) on `xs`, where
186
+ * points with x at or below zero are skipped. */
187
+ export function logarithmicFit(values, xs) {
188
+ const shift = xs ? 0 : 1;
189
+ const lx = xsOf(values, xs).map((x) => (x + shift > 0 ? Math.log(x + shift) : NaN));
190
+ const { slope, intercept, ok } = lineThrough(lx, values);
191
+ if (!ok)
192
+ return NO_FIT(values);
193
+ const predict = (x) => (x + shift > 0 ? intercept + slope * Math.log(x + shift) : NaN);
194
+ const fitted = values.map((v, i) => (Number.isFinite(v) && Number.isFinite(lx[i]) ? intercept + slope * lx[i] : NaN));
195
+ const arg = xs ? 'x' : 'x + 1';
196
+ return { fitted, r2: rSquared(values, fitted), equation: `y = ${fmtCoef(intercept)} + ${fmtCoef(slope)} ln(${arg})`, predict };
197
+ }
198
+ /** Fit y = a (x + 1)^b by least squares on ln y against ln(x + 1), or
199
+ * y = a x^b on `xs`; values (and x on `xs`) at or below zero are skipped
200
+ * (fitted as NaN). */
201
+ export function powerFit(values, xs) {
202
+ const shift = xs ? 0 : 1;
203
+ const lx = xsOf(values, xs).map((x) => (x + shift > 0 ? Math.log(x + shift) : NaN));
204
+ const logs = values.map((v) => (Number.isFinite(v) && v > 0 ? Math.log(v) : NaN));
205
+ const { slope, intercept, ok } = lineThrough(lx, logs);
206
+ if (!ok)
207
+ return NO_FIT(values);
208
+ const a = Math.exp(intercept);
209
+ const predict = (x) => (x + shift > 0 ? a * Math.pow(x + shift, slope) : NaN);
210
+ const fitted = values.map((v, i) => (Number.isFinite(v) && v > 0 && Number.isFinite(lx[i]) ? a * Math.exp(slope * lx[i]) : NaN));
211
+ const arg = xs ? 'x' : '(x + 1)';
212
+ return { fitted, r2: rSquared(values, fitted), equation: `y = ${fmtCoef(a)} ${arg}^${fmtCoef(slope)}`, predict };
213
+ }
214
+ /** Least squares of y on arbitrary x, skipping non-finite pairs. */
215
+ function lineThrough(xs, ys) {
216
+ let n = 0, sx = 0, sy = 0, sxx = 0, sxy = 0;
217
+ for (let i = 0; i < ys.length; i += 1) {
218
+ const x = xs[i];
219
+ const y = ys[i];
220
+ if (!Number.isFinite(x) || !Number.isFinite(y))
221
+ continue;
222
+ n += 1;
223
+ sx += x;
224
+ sy += y;
225
+ sxx += x * x;
226
+ sxy += x * y;
227
+ }
228
+ if (n < 2)
229
+ return { slope: 0, intercept: 0, ok: false };
230
+ const denom = n * sxx - sx * sx;
231
+ const slope = denom === 0 ? 0 : (n * sxy - sx * sy) / denom;
232
+ return { slope, intercept: (sy - slope * sx) / n, ok: true };
233
+ }
234
+ /** Simple moving average over a window of `period` values. Window centres
235
+ * trail to the right (typical for time-series). NaN for points before the
236
+ * window is full. */
237
+ export function simpleMovingAverage(values, period) {
238
+ if (period < 1)
239
+ return values.slice();
240
+ const out = new Array(values.length).fill(NaN);
241
+ let sum = 0, count = 0;
242
+ for (let i = 0; i < values.length; i += 1) {
243
+ const v = values[i];
244
+ if (Number.isFinite(v)) {
245
+ sum += v;
246
+ count += 1;
247
+ }
248
+ if (i >= period) {
249
+ const drop = values[i - period];
250
+ if (Number.isFinite(drop)) {
251
+ sum -= drop;
252
+ count -= 1;
253
+ }
254
+ }
255
+ if (i >= period - 1 && count > 0)
256
+ out[i] = sum / count;
257
+ }
258
+ return out;
259
+ }
260
+ /** Exponential moving average. Smoothing factor alpha = 2 / (period + 1). */
261
+ export function exponentialMovingAverage(values, period) {
262
+ const alpha = 2 / (Math.max(1, period) + 1);
263
+ const out = new Array(values.length).fill(NaN);
264
+ let prev = null;
265
+ for (let i = 0; i < values.length; i += 1) {
266
+ const v = values[i];
267
+ if (!Number.isFinite(v)) {
268
+ out[i] = prev ?? NaN;
269
+ continue;
270
+ }
271
+ prev = prev == null ? v : alpha * v + (1 - alpha) * prev;
272
+ out[i] = prev;
273
+ }
274
+ return out;
275
+ }
276
+ /** The reducers that need every observation, not a running sum and count. */
277
+ export function reducerNeedsSamples(reducer) {
278
+ return !(reducer === 'sum' || reducer === 'avg' || reducer === 'count');
279
+ }
280
+ /**
281
+ * Collapse a group's observations to one number. `sum` / `avg` / `count` are
282
+ * what `rowsToChartSpec` always did; the rest are the ones a pivot table
283
+ * offers and a chart panel was missing: `min`, `max`, `median`, `first`,
284
+ * `last`, `countDistinct`, and `pN` for the Nth percentile (`'p90'`). An
285
+ * empty group reduces to 0 for `sum` / `count` and to NaN otherwise, so a
286
+ * missing bucket draws as a gap rather than as a fake zero.
287
+ */
288
+ export function reduceValues(samples, reducer) {
289
+ const n = samples.length;
290
+ switch (reducer) {
291
+ case 'count':
292
+ return n;
293
+ case 'sum': {
294
+ let s = 0;
295
+ for (const v of samples)
296
+ s += v;
297
+ return s;
298
+ }
299
+ case 'avg': {
300
+ if (!n)
301
+ return 0;
302
+ let s = 0;
303
+ for (const v of samples)
304
+ s += v;
305
+ return s / n;
306
+ }
307
+ case 'min':
308
+ return n ? Math.min(...samples) : Number.NaN;
309
+ case 'max':
310
+ return n ? Math.max(...samples) : Number.NaN;
311
+ case 'first':
312
+ return n ? samples[0] : Number.NaN;
313
+ case 'last':
314
+ return n ? samples[n - 1] : Number.NaN;
315
+ case 'countDistinct':
316
+ return new Set(samples).size;
317
+ case 'median':
318
+ return percentile(samples, 50);
319
+ default: {
320
+ const m = /^p(\d+(?:\.\d+)?)$/.exec(reducer);
321
+ return m ? percentile(samples, Number(m[1])) : Number.NaN;
322
+ }
323
+ }
324
+ }
325
+ /** The p-th percentile (0..100) by linear interpolation between order statistics. */
326
+ export function percentile(samples, p) {
327
+ const v = samples.filter((n) => Number.isFinite(n)).slice().sort((a, b) => a - b);
328
+ if (!v.length)
329
+ return Number.NaN;
330
+ const pos = (v.length - 1) * Math.max(0, Math.min(100, p)) / 100;
331
+ const lo = Math.floor(pos);
332
+ const hi = Math.ceil(pos);
333
+ return lo === hi ? v[lo] : v[lo] + (v[hi] - v[lo]) * (pos - lo);
334
+ }
335
+ /**
336
+ * The ISO date (`YYYY-MM-DD`, UTC) that starts the bucket containing `date`:
337
+ * the day itself, the Monday of its week, the first of its month, quarter or
338
+ * year. Returns `''` for a value that is not a date, so the caller can drop
339
+ * the row rather than file it under 1970.
340
+ */
341
+ export function bucketStart(date, bucket) {
342
+ const t = date instanceof Date ? date.getTime() : typeof date === 'number' ? date : Date.parse(date);
343
+ if (!Number.isFinite(t))
344
+ return '';
345
+ const d = new Date(t);
346
+ const y = d.getUTCFullYear();
347
+ const m = d.getUTCMonth();
348
+ let out;
349
+ switch (bucket) {
350
+ case 'day':
351
+ out = new Date(Date.UTC(y, m, d.getUTCDate()));
352
+ break;
353
+ case 'week': {
354
+ // Monday-based, which is what a business calendar reads.
355
+ const day = (d.getUTCDay() + 6) % 7;
356
+ out = new Date(Date.UTC(y, m, d.getUTCDate() - day));
357
+ break;
358
+ }
359
+ case 'month':
360
+ out = new Date(Date.UTC(y, m, 1));
361
+ break;
362
+ case 'quarter':
363
+ out = new Date(Date.UTC(y, m - (m % 3), 1));
364
+ break;
365
+ default:
366
+ out = new Date(Date.UTC(y, 0, 1));
367
+ }
368
+ return out.toISOString().slice(0, 10);
369
+ }
370
+ /**
371
+ * Bin a raw sample for a histogram. The bin count comes from `bins`, from a
372
+ * `binWidth`, or from a rule: `'sturges'` (default, log2 n + 1), `'fd'`
373
+ * (Freedman-Diaconis, 2 IQR / cbrt n) or `'sqrt'`. The range is the data's
374
+ * extent unless `min` / `max` pin it; the top edge is inclusive. Edges are
375
+ * rounded to a sensible precision for the width, so labels read as `10, 20,
376
+ * 30` rather than `10.000000001`.
377
+ */
378
+ export function binValues(values, opts = {}) {
379
+ const v = values.filter((n) => Number.isFinite(n));
380
+ if (!v.length)
381
+ return { categories: [], counts: [], edges: [], width: 0 };
382
+ const lo = opts.min ?? Math.min(...v);
383
+ let hi = opts.max ?? Math.max(...v);
384
+ if (hi <= lo)
385
+ hi = lo + 1;
386
+ let count;
387
+ if (opts.bins && opts.bins > 0)
388
+ count = Math.round(opts.bins);
389
+ else if (opts.binWidth && opts.binWidth > 0)
390
+ count = Math.max(1, Math.ceil((hi - lo) / opts.binWidth));
391
+ else if (opts.method === 'sqrt')
392
+ count = Math.ceil(Math.sqrt(v.length));
393
+ else if (opts.method === 'fd') {
394
+ const iqr = percentile(v, 75) - percentile(v, 25);
395
+ const w = iqr > 0 ? (2 * iqr) / Math.cbrt(v.length) : 0;
396
+ count = w > 0 ? Math.ceil((hi - lo) / w) : Math.ceil(Math.log2(v.length) + 1);
397
+ }
398
+ else
399
+ count = Math.ceil(Math.log2(v.length) + 1);
400
+ count = Math.max(1, Math.min(1000, count));
401
+ const width = opts.binWidth && opts.binWidth > 0 ? opts.binWidth : (hi - lo) / count;
402
+ // Precision from the width: a 0.25 width keeps two decimals, a 10 width none.
403
+ const decimals = width >= 1 ? 0 : Math.min(6, Math.ceil(-Math.log10(width)) + 1);
404
+ const fix = (n) => Number(n.toFixed(decimals));
405
+ const edges = [];
406
+ for (let i = 0; i <= count; i += 1)
407
+ edges.push(fix(lo + i * width));
408
+ const counts = new Array(count).fill(0);
409
+ for (const n of v) {
410
+ let i = Math.floor((n - lo) / width);
411
+ if (i >= count)
412
+ i = count - 1;
413
+ if (i < 0)
414
+ i = 0;
415
+ counts[i] = (counts[i] ?? 0) + 1;
416
+ }
417
+ const categories = counts.map((_, i) => String(fix(lo + (i + 0.5) * width)));
418
+ return { categories, counts, edges, width };
419
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Live data. A feed hands a chart one new point every few hundred
3
+ * milliseconds; the spec is immutable state, so each tick wants a new spec
4
+ * with the point appended and the oldest dropped, with every per-category
5
+ * array kept in step. Demo 159 rebuilt the whole spec from a rows array on
6
+ * every tick; `appendPoints` does the append in one call and keeps the
7
+ * confidence bands, row ids, candles and volumes parallel by construction,
8
+ * because it walks the same key list the zoom window and decimation use.
9
+ */
10
+ import type { ChartSpec, OhlcBar } from './chart-types.js';
11
+ /** One new category with its values, for {@link appendPoints}. */
12
+ export type ChartPoint = {
13
+ /** The category label (a date string on a time axis). */
14
+ category: string;
15
+ /** One value per series in spec order, or values keyed by series label;
16
+ * a series not named gets a gap (NaN). */
17
+ values: ReadonlyArray<number> | Record<string, number>;
18
+ /** The bar for a candlestick series (the first series with `ohlc`). */
19
+ ohlc?: OhlcBar | null;
20
+ /** The volume for that series. */
21
+ volume?: number;
22
+ /** Row ids behind the point, one array per series in spec order. */
23
+ rowIds?: ReadonlyArray<ReadonlyArray<string | number>>;
24
+ /** Confidence band ends, one per series in spec order. */
25
+ upper?: ReadonlyArray<number>;
26
+ lower?: ReadonlyArray<number>;
27
+ /** Range low / bullet target, one per series in spec order. */
28
+ low?: ReadonlyArray<number>;
29
+ target?: ReadonlyArray<number>;
30
+ };
31
+ /** What {@link appendPoints} takes besides the points. */
32
+ export type AppendPointsOptions = {
33
+ /** Keep only the last `window` categories after appending. */
34
+ window?: number;
35
+ };
36
+ /**
37
+ * Append one or more points to a spec and return the new spec, trimmed to
38
+ * the trailing `window` categories when one is given. Every array a series
39
+ * carries per category (values, row ids, bands, candles, volumes, colours,
40
+ * markers, errors, lows, targets) grows by the same count, with a gap where
41
+ * the point says nothing; so do the spec-level ones (waterfall totals). The
42
+ * input is never mutated: only the arrays that grow are copied, which keeps
43
+ * Svelte's state diffing cheap at 4 Hz.
44
+ */
45
+ export declare function appendPoints(spec: ChartSpec, points: ChartPoint | ReadonlyArray<ChartPoint>, opts?: AppendPointsOptions): ChartSpec;
@@ -0,0 +1,91 @@
1
+ import { PER_CATEGORY_SERIES_KEYS, PER_CATEGORY_SPEC_KEYS, pickCategories } from './chart-decimate.js';
2
+ /** The gap value each per-category array takes when a point does not set it. */
3
+ function gapFor(key) {
4
+ switch (key) {
5
+ case 'rowIds': return [];
6
+ case 'ohlc':
7
+ case 'boxes':
8
+ case 'markers':
9
+ case 'colors':
10
+ case 'errors': return null;
11
+ default: return Number.NaN;
12
+ }
13
+ }
14
+ /**
15
+ * Append one or more points to a spec and return the new spec, trimmed to
16
+ * the trailing `window` categories when one is given. Every array a series
17
+ * carries per category (values, row ids, bands, candles, volumes, colours,
18
+ * markers, errors, lows, targets) grows by the same count, with a gap where
19
+ * the point says nothing; so do the spec-level ones (waterfall totals). The
20
+ * input is never mutated: only the arrays that grow are copied, which keeps
21
+ * Svelte's state diffing cheap at 4 Hz.
22
+ */
23
+ export function appendPoints(spec, points, opts = {}) {
24
+ const list = Array.isArray(points) ? points : [points];
25
+ if (!list.length)
26
+ return opts.window ? trim(spec, opts.window) : spec;
27
+ const categories = [...spec.categories, ...list.map((p) => p.category)];
28
+ const ohlcOwner = spec.series.findIndex((s) => s.ohlc);
29
+ const series = spec.series.map((s, si) => {
30
+ const next = { ...s, values: [...s.values] };
31
+ for (const p of list) {
32
+ const v = Array.isArray(p.values) ? p.values[si] : p.values[s.label];
33
+ next.values.push(typeof v === 'number' ? v : Number.NaN);
34
+ }
35
+ for (const key of PER_CATEGORY_SERIES_KEYS) {
36
+ if (key === 'values')
37
+ continue;
38
+ const has = s[key] !== undefined;
39
+ // A key the series does not carry is only created when a point brings
40
+ // it (a candle, a volume), so a plain line stays a plain line.
41
+ const brings = list.some((p) => (key === 'ohlc' && p.ohlc !== undefined && si === (ohlcOwner < 0 ? 0 : ohlcOwner)) ||
42
+ (key === 'volumes' && p.volume !== undefined && si === (ohlcOwner < 0 ? 0 : ohlcOwner)) ||
43
+ (key === 'rowIds' && p.rowIds?.[si] !== undefined) ||
44
+ (key === 'upperValues' && p.upper?.[si] !== undefined) ||
45
+ (key === 'lowerValues' && p.lower?.[si] !== undefined) ||
46
+ (key === 'lowValues' && p.low?.[si] !== undefined) ||
47
+ (key === 'targets' && p.target?.[si] !== undefined));
48
+ if (!has && !brings)
49
+ continue;
50
+ const base = has ? [...s[key]] : new Array(s.values.length).fill(gapFor(key));
51
+ for (const p of list) {
52
+ let v = undefined;
53
+ if (key === 'ohlc' && si === (ohlcOwner < 0 ? 0 : ohlcOwner))
54
+ v = p.ohlc;
55
+ else if (key === 'volumes' && si === (ohlcOwner < 0 ? 0 : ohlcOwner))
56
+ v = p.volume;
57
+ else if (key === 'rowIds')
58
+ v = p.rowIds?.[si];
59
+ else if (key === 'upperValues')
60
+ v = p.upper?.[si];
61
+ else if (key === 'lowerValues')
62
+ v = p.lower?.[si];
63
+ else if (key === 'lowValues')
64
+ v = p.low?.[si];
65
+ else if (key === 'targets')
66
+ v = p.target?.[si];
67
+ base.push(v === undefined ? gapFor(key) : v);
68
+ }
69
+ ;
70
+ next[key] = base;
71
+ }
72
+ return next;
73
+ });
74
+ let out = { ...spec, categories, series };
75
+ for (const key of PER_CATEGORY_SPEC_KEYS) {
76
+ const arr = spec[key];
77
+ if (arr)
78
+ out[key] = [...arr, ...list.map(() => false)];
79
+ }
80
+ if (opts.window)
81
+ out = trim(out, opts.window);
82
+ return out;
83
+ }
84
+ /** The last `window` categories of a spec (the spec itself when it fits). */
85
+ function trim(spec, window) {
86
+ const n = spec.categories.length;
87
+ if (window <= 0 || n <= window)
88
+ return spec;
89
+ const indices = Array.from({ length: window }, (_, k) => n - window + k);
90
+ return pickCategories(spec, indices);
91
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A chart in a sentence or two. `chartSummary` reads a spec the way a
3
+ * colleague would read the picture: which way each series goes and by how
4
+ * much, where it peaks, what the biggest slices are, whether two measures
5
+ * move together, how the last session closed. `SvChart` puts the result in
6
+ * the SVG's `aria-description`, the context menu copies it, and the AI
7
+ * "explain this chart" prompt is grounded on it, so the words a screen
8
+ * reader hears and the words a model reasons from are the same words.
9
+ *
10
+ * Pure and dependency-light (two small leaves: the number formatter and the
11
+ * trend primitives, kept apart from the engine's modules so the chunk a
12
+ * bundler shares between this lazy module and the base stays tiny), so it
13
+ * runs on the server and in a test, and the renderer loads it lazily.
14
+ */
15
+ import type { ChartSpec } from './chart-types.js';
16
+ /** What {@link chartSummary} takes. */
17
+ export type ChartSummaryOptions = {
18
+ /** How numbers are written. Default: the spec's `valueFormat` / `locale` / `currency`. */
19
+ formatValue?: (v: number) => string;
20
+ /** How many series a cartesian chart describes before it says "and N more". Default 4. */
21
+ maxSeries?: number;
22
+ };
23
+ /**
24
+ * Describe a spec in plain words. Never throws: an empty spec of any type
25
+ * comes back as "No data." so a live region always has something to say.
26
+ */
27
+ export declare function chartSummary(spec: ChartSpec, opts?: ChartSummaryOptions): string;