@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,51 @@
1
+ /**
2
+ * Thinning long series before layout, and the one place that knows which
3
+ * arrays on a spec run parallel to `categories`.
4
+ *
5
+ * `buildChart` scales fine (100k line points is ~140 ms of maths); what does
6
+ * not scale is asking the DOM to hold a mark per point, and past one point
7
+ * per pixel the extra marks carry no information anyway. So a long line or
8
+ * area series is reduced to about one point per pixel with an algorithm that
9
+ * keeps its visual shape (Largest-Triangle-Three-Buckets) or its extremes
10
+ * (min / max per bucket), and every category-parallel array is picked with the
11
+ * same indices so tooltips, drill-through and error bars stay aligned.
12
+ */
13
+ import type { ChartDecimateConfig, ChartSpec } from './chart-types.js';
14
+ /**
15
+ * Every array on a {@link ChartSeries} indexed by category. Anything that
16
+ * narrows a spec to a subset of categories (the zoom window, decimation) picks
17
+ * each of these with the same indices; a key missing here is a mark drawn
18
+ * against the wrong category, which is why the list is one constant rather
19
+ * than a convention. `values` is included for completeness.
20
+ */
21
+ export declare const PER_CATEGORY_SERIES_KEYS: readonly ["values", "rowIds", "upperValues", "lowerValues", "ohlc", "boxes", "errors", "markers", "colors", "lowValues", "targets", "volumes"];
22
+ /** Spec-level arrays indexed by category (see {@link PER_CATEGORY_SERIES_KEYS}). */
23
+ export declare const PER_CATEGORY_SPEC_KEYS: readonly ["waterfallTotals"];
24
+ /**
25
+ * Narrow a spec to the categories at `indices` (ascending), keeping every
26
+ * category-parallel array in step. The zoom window and decimation both go
27
+ * through here, so there is exactly one list of arrays to keep in step.
28
+ */
29
+ export declare function pickCategories(spec: ChartSpec, indices: ReadonlyArray<number>): ChartSpec;
30
+ /**
31
+ * Largest-Triangle-Three-Buckets: the indices of the `target` points that best
32
+ * preserve the visual shape of `values` plotted against their index. The
33
+ * first and last points are always kept. Non-finite values are skipped as
34
+ * candidates, so a gap cannot be "chosen" as a representative point.
35
+ */
36
+ export declare function lttb(values: ReadonlyArray<number>, target: number, xs?: ReadonlyArray<number>): number[];
37
+ /**
38
+ * The indices of the minimum and maximum in each of `buckets` equal slices of
39
+ * `values`, plus the first and last point. Keeps every spike, which is what
40
+ * monitoring data wants and what LTTB will sometimes smooth over.
41
+ */
42
+ export declare function minMaxIndices(values: ReadonlyArray<number>, buckets: number): number[];
43
+ /**
44
+ * Thin a spec's line / area series to about one point per pixel of plot
45
+ * width. Nothing happens below the threshold, for a spec that says
46
+ * `decimate: false`, or when any series draws bars, candles or boxes (those
47
+ * marks ARE the data; thinning them would hide categories). Every series is
48
+ * decimated separately and the union of the kept indices is used, so a spike
49
+ * that only one series has still survives.
50
+ */
51
+ export declare function decimateSpec(spec: ChartSpec, plotW: number, cfg?: ChartDecimateConfig | undefined): ChartSpec;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Every array on a {@link ChartSeries} indexed by category. Anything that
3
+ * narrows a spec to a subset of categories (the zoom window, decimation) picks
4
+ * each of these with the same indices; a key missing here is a mark drawn
5
+ * against the wrong category, which is why the list is one constant rather
6
+ * than a convention. `values` is included for completeness.
7
+ */
8
+ export const PER_CATEGORY_SERIES_KEYS = [
9
+ 'values',
10
+ 'rowIds',
11
+ 'upperValues',
12
+ 'lowerValues',
13
+ 'ohlc',
14
+ 'boxes',
15
+ 'errors',
16
+ 'markers',
17
+ 'colors',
18
+ 'lowValues',
19
+ 'targets',
20
+ 'volumes',
21
+ ];
22
+ /** Spec-level arrays indexed by category (see {@link PER_CATEGORY_SERIES_KEYS}). */
23
+ export const PER_CATEGORY_SPEC_KEYS = ['waterfallTotals'];
24
+ /**
25
+ * Narrow a spec to the categories at `indices` (ascending), keeping every
26
+ * category-parallel array in step. The zoom window and decimation both go
27
+ * through here, so there is exactly one list of arrays to keep in step.
28
+ */
29
+ export function pickCategories(spec, indices) {
30
+ const pick = (arr) => {
31
+ if (!arr)
32
+ return undefined;
33
+ const out = new Array(indices.length);
34
+ for (let k = 0; k < indices.length; k += 1)
35
+ out[k] = arr[indices[k]];
36
+ return out;
37
+ };
38
+ const series = spec.series.map((s) => {
39
+ const next = { ...s, values: pick(s.values) ?? [] };
40
+ for (const key of PER_CATEGORY_SERIES_KEYS) {
41
+ if (key === 'values')
42
+ continue;
43
+ const arr = s[key];
44
+ if (arr)
45
+ next[key] = pick(arr);
46
+ }
47
+ return next;
48
+ });
49
+ const out = { ...spec, categories: pick(spec.categories) ?? [], series };
50
+ for (const key of PER_CATEGORY_SPEC_KEYS) {
51
+ const arr = spec[key];
52
+ if (arr)
53
+ out[key] = pick(arr);
54
+ }
55
+ return out;
56
+ }
57
+ /**
58
+ * Largest-Triangle-Three-Buckets: the indices of the `target` points that best
59
+ * preserve the visual shape of `values` plotted against their index. The
60
+ * first and last points are always kept. Non-finite values are skipped as
61
+ * candidates, so a gap cannot be "chosen" as a representative point.
62
+ */
63
+ export function lttb(values, target, xs) {
64
+ const n = values.length;
65
+ // The x of point j: its index unless the caller positions points by value
66
+ // (a numeric or log axis), where the triangles must use the real spacing.
67
+ const xAt = (j) => (xs && Number.isFinite(xs[j]) ? xs[j] : j);
68
+ if (target >= n || n <= 2)
69
+ return values.map((_, i) => i);
70
+ if (target < 3)
71
+ return [0, n - 1];
72
+ const out = [0];
73
+ const every = (n - 2) / (target - 2);
74
+ let a = 0;
75
+ for (let i = 0; i < target - 2; i += 1) {
76
+ // The next bucket's centroid is the far corner of every candidate triangle.
77
+ const nextStart = Math.floor((i + 1) * every) + 1;
78
+ const nextEnd = Math.min(Math.floor((i + 2) * every) + 1, n);
79
+ let avgX = 0;
80
+ let avgY = 0;
81
+ let cnt = 0;
82
+ for (let j = nextStart; j < nextEnd; j += 1) {
83
+ const v = values[j];
84
+ if (!Number.isFinite(v))
85
+ continue;
86
+ avgX += xAt(j);
87
+ avgY += v;
88
+ cnt += 1;
89
+ }
90
+ if (cnt) {
91
+ avgX /= cnt;
92
+ avgY /= cnt;
93
+ }
94
+ else {
95
+ avgX = (xAt(nextStart) + xAt(Math.min(nextEnd, n - 1))) / 2;
96
+ avgY = values[a];
97
+ }
98
+ const start = Math.floor(i * every) + 1;
99
+ const end = Math.min(Math.floor((i + 1) * every) + 1, n);
100
+ const ax = xAt(a);
101
+ const ay = Number.isFinite(values[a]) ? values[a] : 0;
102
+ let best = -1;
103
+ let bestArea = -1;
104
+ for (let j = start; j < end; j += 1) {
105
+ const v = values[j];
106
+ if (!Number.isFinite(v))
107
+ continue;
108
+ const area = Math.abs((ax - avgX) * (v - ay) - (ax - xAt(j)) * (avgY - ay));
109
+ if (area > bestArea) {
110
+ bestArea = area;
111
+ best = j;
112
+ }
113
+ }
114
+ if (best < 0)
115
+ best = start;
116
+ out.push(best);
117
+ a = best;
118
+ }
119
+ out.push(n - 1);
120
+ return out;
121
+ }
122
+ /**
123
+ * The indices of the minimum and maximum in each of `buckets` equal slices of
124
+ * `values`, plus the first and last point. Keeps every spike, which is what
125
+ * monitoring data wants and what LTTB will sometimes smooth over.
126
+ */
127
+ export function minMaxIndices(values, buckets) {
128
+ const n = values.length;
129
+ if (buckets >= n || n <= 2)
130
+ return values.map((_, i) => i);
131
+ const keep = new Set([0, n - 1]);
132
+ const size = n / Math.max(1, buckets);
133
+ for (let b = 0; b < buckets; b += 1) {
134
+ const start = Math.floor(b * size);
135
+ const end = Math.min(n, Math.floor((b + 1) * size));
136
+ let lo = -1;
137
+ let hi = -1;
138
+ for (let j = start; j < end; j += 1) {
139
+ const v = values[j];
140
+ if (!Number.isFinite(v))
141
+ continue;
142
+ if (lo < 0 || v < values[lo])
143
+ lo = j;
144
+ if (hi < 0 || v > values[hi])
145
+ hi = j;
146
+ }
147
+ if (lo >= 0)
148
+ keep.add(lo);
149
+ if (hi >= 0)
150
+ keep.add(hi);
151
+ }
152
+ return [...keep].sort((x, y) => x - y);
153
+ }
154
+ /** The decimation settings a spec asks for, defaults filled in. */
155
+ function resolveDecimate(cfg, plotW) {
156
+ if (cfg === false)
157
+ return null;
158
+ const o = typeof cfg === 'object' ? cfg : {};
159
+ const target = Math.max(8, Math.round(o.target ?? plotW));
160
+ return { method: o.method ?? 'lttb', target, threshold: Math.max(target, Math.round(o.threshold ?? target)) };
161
+ }
162
+ /**
163
+ * Thin a spec's line / area series to about one point per pixel of plot
164
+ * width. Nothing happens below the threshold, for a spec that says
165
+ * `decimate: false`, or when any series draws bars, candles or boxes (those
166
+ * marks ARE the data; thinning them would hide categories). Every series is
167
+ * decimated separately and the union of the kept indices is used, so a spike
168
+ * that only one series has still survives.
169
+ */
170
+ export function decimateSpec(spec, plotW, cfg = spec.decimate) {
171
+ const opt = resolveDecimate(cfg, plotW);
172
+ if (!opt)
173
+ return spec;
174
+ const n = spec.categories.length;
175
+ if (n <= opt.threshold || !spec.series.length)
176
+ return spec;
177
+ const lineLike = (t) => t === 'line' || t === 'area';
178
+ if (!lineLike(spec.type) && !spec.series.every((s) => lineLike(s.type)))
179
+ return spec;
180
+ if (spec.series.some((s) => (s.type && !lineLike(s.type)) || s.ohlc || s.boxes))
181
+ return spec;
182
+ // On a numeric axis the points sit where their values are, so the
183
+ // triangles are measured in those positions (log10 of them on a log axis).
184
+ const numeric = (spec.xAxis?.type ?? spec.xType) === 'number';
185
+ const isLog = numeric && spec.xAxis?.scale === 'log';
186
+ const xs = numeric
187
+ ? spec.categories.map((c) => { const v = Number(c); return isLog ? (v > 0 ? Math.log10(v) : Number.NaN) : v; })
188
+ : undefined;
189
+ const keep = new Set();
190
+ for (const s of spec.series) {
191
+ const idx = opt.method === 'minmax' ? minMaxIndices(s.values, Math.floor(opt.target / 2)) : lttb(s.values, opt.target, xs);
192
+ for (const i of idx)
193
+ keep.add(i);
194
+ }
195
+ const indices = [...keep].sort((a, b) => a - b);
196
+ if (indices.length >= n)
197
+ return spec;
198
+ return pickCategories(spec, indices);
199
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * PDF and print for a rendered chart, with no PDF library.
3
+ *
4
+ * A one-page PDF is a short text file with one binary blob in it, so the
5
+ * writer here is the whole format we need: a catalog, a page, a Helvetica
6
+ * font, one image XObject and a content stream that places the text and the
7
+ * image. The chart is rasterised through the existing PNG path and re-encoded
8
+ * as JPEG, which the PDF embeds as-is under `/DCTDecode`; nothing needs zlib.
9
+ * JPEG has no alpha, so the chart background is always painted.
10
+ *
11
+ * Print opens a window with the standalone SVG and calls `print()`.
12
+ */
13
+ import { type ChartExportOptions } from './chart-export.js';
14
+ /** Options for the PDF: page, orientation, the text around the chart. */
15
+ export type ChartPdfOptions = ChartExportOptions & {
16
+ /** Paper size. Default A4. */
17
+ page?: 'A4' | 'Letter';
18
+ /** Default: whichever fits the chart's aspect ratio better. */
19
+ orientation?: 'portrait' | 'landscape';
20
+ title?: string;
21
+ subtitle?: string;
22
+ caption?: string;
23
+ /** JPEG quality, 0..1. Default 0.92. */
24
+ quality?: number;
25
+ };
26
+ /**
27
+ * Assemble a one-page PDF from a JPEG and the text around it. Exported for
28
+ * tests and for callers that already have a JPEG; `chartToPdfBlob` is the
29
+ * one that starts from a chart element.
30
+ */
31
+ export declare function buildChartPdf(jpeg: {
32
+ bytes: Uint8Array;
33
+ width: number;
34
+ height: number;
35
+ }, opts?: Pick<ChartPdfOptions, 'page' | 'orientation' | 'title' | 'subtitle' | 'caption'>): Uint8Array;
36
+ /** The chart as a one-page PDF `Blob`. */
37
+ export declare function chartToPdfBlob(source: SVGSVGElement | HTMLElement, options?: ChartPdfOptions): Promise<Blob>;
38
+ /** Download the chart as a `.pdf` file. */
39
+ export declare function downloadChartPdf(source: SVGSVGElement | HTMLElement, filename?: string, options?: ChartPdfOptions): Promise<void>;
40
+ /**
41
+ * Print the chart: a new window with the standalone SVG (and the title
42
+ * above it), sized to the page, that opens the print dialog once it has
43
+ * drawn. Returns false when the window was blocked.
44
+ */
45
+ export declare function printChart(source: SVGSVGElement | HTMLElement, options?: ChartExportOptions & {
46
+ title?: string;
47
+ }): boolean;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * PDF and print for a rendered chart, with no PDF library.
3
+ *
4
+ * A one-page PDF is a short text file with one binary blob in it, so the
5
+ * writer here is the whole format we need: a catalog, a page, a Helvetica
6
+ * font, one image XObject and a content stream that places the text and the
7
+ * image. The chart is rasterised through the existing PNG path and re-encoded
8
+ * as JPEG, which the PDF embeds as-is under `/DCTDecode`; nothing needs zlib.
9
+ * JPEG has no alpha, so the chart background is always painted.
10
+ *
11
+ * Print opens a window with the standalone SVG and calls `print()`.
12
+ */
13
+ import { chartToPngBlob, chartToSvgString } from './chart-export.js';
14
+ const PAGES = { A4: [595.28, 841.89], Letter: [612, 792] };
15
+ /** Latin-1 bytes of a string; anything outside it becomes `?`. */
16
+ function latin1(s) {
17
+ const out = new Uint8Array(s.length);
18
+ for (let i = 0; i < s.length; i += 1) {
19
+ const c = s.charCodeAt(i);
20
+ out[i] = c < 256 ? c : 63;
21
+ }
22
+ return out;
23
+ }
24
+ /** A PDF string literal: parentheses and backslashes escaped. */
25
+ function pdfText(s) {
26
+ return '(' + s.replace(/[\\()]/g, (m) => '\\' + m).replace(/[\r\n]+/g, ' ') + ')';
27
+ }
28
+ /** Roughly how wide Helvetica text is at a size, for fitting a line. */
29
+ function textWidth(s, size) {
30
+ return s.length * size * 0.5;
31
+ }
32
+ /** Re-encode a PNG blob as JPEG on a painted background. */
33
+ function pngToJpeg(png, background, quality) {
34
+ return new Promise((resolve, reject) => {
35
+ const url = URL.createObjectURL(png);
36
+ const img = new Image();
37
+ img.onload = () => {
38
+ const canvas = document.createElement('canvas');
39
+ canvas.width = img.naturalWidth || img.width;
40
+ canvas.height = img.naturalHeight || img.height;
41
+ const ctx = canvas.getContext('2d');
42
+ if (!ctx) {
43
+ URL.revokeObjectURL(url);
44
+ reject(new Error('canvas 2d context unavailable'));
45
+ return;
46
+ }
47
+ ctx.fillStyle = background;
48
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
49
+ ctx.drawImage(img, 0, 0);
50
+ URL.revokeObjectURL(url);
51
+ canvas.toBlob((blob) => {
52
+ if (!blob) {
53
+ reject(new Error('toBlob failed'));
54
+ return;
55
+ }
56
+ blob.arrayBuffer().then((buf) => resolve({ bytes: new Uint8Array(buf), width: canvas.width, height: canvas.height }), reject);
57
+ }, 'image/jpeg', quality);
58
+ };
59
+ img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('failed to decode chart PNG')); };
60
+ img.src = url;
61
+ });
62
+ }
63
+ /**
64
+ * Assemble a one-page PDF from a JPEG and the text around it. Exported for
65
+ * tests and for callers that already have a JPEG; `chartToPdfBlob` is the
66
+ * one that starts from a chart element.
67
+ */
68
+ export function buildChartPdf(jpeg, opts = {}) {
69
+ const [pw0, ph0] = PAGES[opts.page ?? 'A4'];
70
+ const landscape = opts.orientation ? opts.orientation === 'landscape' : jpeg.width / jpeg.height > pw0 / ph0;
71
+ const pw = landscape ? ph0 : pw0;
72
+ const ph = landscape ? pw0 : ph0;
73
+ const margin = 40;
74
+ const title = (opts.title ?? '').trim();
75
+ const subtitle = (opts.subtitle ?? '').trim();
76
+ const caption = (opts.caption ?? '').trim();
77
+ // Text block at the top, image below it, caption at the foot.
78
+ let y = ph - margin;
79
+ const content = [];
80
+ const line = (text, size, gray) => {
81
+ y -= size + 4;
82
+ content.push(`BT /F1 ${size} Tf ${gray} g ${margin} ${y.toFixed(2)} Td ${pdfText(text)} Tj ET`);
83
+ };
84
+ if (title)
85
+ line(title, 16, 0);
86
+ if (subtitle)
87
+ line(subtitle, 11, 0.4);
88
+ if (title || subtitle)
89
+ y -= 8;
90
+ const footer = caption ? 20 : 0;
91
+ const availW = pw - margin * 2;
92
+ const availH = y - margin - footer;
93
+ const scale = Math.min(availW / jpeg.width, availH / jpeg.height);
94
+ const iw = jpeg.width * scale;
95
+ const ih = jpeg.height * scale;
96
+ const ix = margin + (availW - iw) / 2;
97
+ const iy = y - ih;
98
+ content.push(`q ${iw.toFixed(2)} 0 0 ${ih.toFixed(2)} ${ix.toFixed(2)} ${iy.toFixed(2)} cm /Im1 Do Q`);
99
+ if (caption) {
100
+ const size = 9;
101
+ const text = textWidth(caption, size) > availW ? caption.slice(0, Math.floor(availW / (size * 0.5)) - 3) + '...' : caption;
102
+ content.push(`BT /F1 ${size} Tf 0.4 g ${margin} ${(margin - 4).toFixed(2)} Td ${pdfText(text)} Tj ET`);
103
+ }
104
+ const stream = content.join('\n');
105
+ const objects = [];
106
+ const offsets = [];
107
+ let length = 0;
108
+ const push = (part) => {
109
+ objects.push(part);
110
+ length += typeof part === 'string' ? part.length : part.length;
111
+ };
112
+ push('%PDF-1.4\n%âãÏÓ\n');
113
+ const obj = (n, body, bin) => {
114
+ offsets[n] = length;
115
+ push(`${n} 0 obj\n${body}\n`);
116
+ if (bin) {
117
+ push('stream\n');
118
+ push(bin);
119
+ push('\nendstream\n');
120
+ }
121
+ push('endobj\n');
122
+ };
123
+ obj(1, '<< /Type /Catalog /Pages 2 0 R >>');
124
+ obj(2, '<< /Type /Pages /Kids [3 0 R] /Count 1 >>');
125
+ obj(3, `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${pw.toFixed(2)} ${ph.toFixed(2)}] /Resources << /XObject << /Im1 4 0 R >> /Font << /F1 5 0 R >> >> /Contents 6 0 R >>`);
126
+ obj(4, `<< /Type /XObject /Subtype /Image /Width ${jpeg.width} /Height ${jpeg.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${jpeg.bytes.length} >>`, jpeg.bytes);
127
+ obj(5, '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>');
128
+ obj(6, `<< /Length ${stream.length} >>`, latin1(stream));
129
+ const xref = length;
130
+ push(`xref\n0 7\n0000000000 65535 f \n`);
131
+ for (let n = 1; n <= 6; n += 1)
132
+ push(`${String(offsets[n]).padStart(10, '0')} 00000 n \n`);
133
+ push(`trailer\n<< /Size 7 /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`);
134
+ const out = new Uint8Array(new ArrayBuffer(length));
135
+ let at = 0;
136
+ for (const part of objects) {
137
+ const bytes = typeof part === 'string' ? latin1(part) : part;
138
+ out.set(bytes, at);
139
+ at += bytes.length;
140
+ }
141
+ return out;
142
+ }
143
+ /** The chart as a one-page PDF `Blob`. */
144
+ export async function chartToPdfBlob(source, options = {}) {
145
+ const svg = source instanceof SVGSVGElement ? source : (source.querySelector('svg.sv-grid-chart-svg') ?? source.querySelector('svg'));
146
+ const background = options.background ?? (svg ? getComputedStyle(svg).getPropertyValue('--sg-bg').trim() || '#ffffff' : '#ffffff');
147
+ const png = await chartToPngBlob(source, { ...options, background, scale: options.scale ?? 2 });
148
+ const jpeg = await pngToJpeg(png, background, options.quality ?? 0.92);
149
+ const bytes = buildChartPdf(jpeg, options);
150
+ return new Blob([bytes.buffer], { type: 'application/pdf' });
151
+ }
152
+ /** Download the chart as a `.pdf` file. */
153
+ export async function downloadChartPdf(source, filename = 'chart.pdf', options = {}) {
154
+ const blob = await chartToPdfBlob(source, options);
155
+ const url = URL.createObjectURL(blob);
156
+ const a = document.createElement('a');
157
+ a.href = url;
158
+ a.download = filename;
159
+ document.body.appendChild(a);
160
+ a.click();
161
+ a.remove();
162
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
163
+ }
164
+ /**
165
+ * Print the chart: a new window with the standalone SVG (and the title
166
+ * above it), sized to the page, that opens the print dialog once it has
167
+ * drawn. Returns false when the window was blocked.
168
+ */
169
+ export function printChart(source, options = {}) {
170
+ const svg = chartToSvgString(source, options);
171
+ const win = window.open('', '_blank', 'width=900,height=700');
172
+ if (!win)
173
+ return false;
174
+ const esc = (s) => s.replace(/[&<>]/g, (m) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' })[m]);
175
+ win.document.write(`<!doctype html><html><head><meta charset="utf-8"><title>${esc(options.title ?? 'Chart')}</title>` +
176
+ `<style>body{margin:24px;font-family:system-ui,sans-serif}h1{font-size:16px;margin:0 0 12px}svg{max-width:100%;height:auto}@page{margin:12mm}</style></head><body>` +
177
+ (options.title ? `<h1>${esc(options.title)}</h1>` : '') +
178
+ svg +
179
+ `</body></html>`);
180
+ win.document.close();
181
+ const go = () => { win.focus(); win.print(); };
182
+ if (win.document.readyState === 'complete')
183
+ setTimeout(go, 50);
184
+ else
185
+ win.addEventListener('load', () => setTimeout(go, 50));
186
+ return true;
187
+ }
@@ -31,7 +31,8 @@ export declare function downloadChartPng(source: SVGSVGElement | HTMLElement, fi
31
31
  *
32
32
  * A series that carries more than one number per category widens instead of
33
33
  * losing them. `ohlc` becomes four columns, `boxes` five plus its outliers,
34
- * `errors` two. This used to write `values` and nothing else, which quietly
34
+ * `errors` two, `lowValues` a low / high pair, `targets` a value / target
35
+ * pair. This used to write `values` and nothing else, which quietly
35
36
  * made "Export CSV" on a candlestick chart hand back the closing prices only -
36
37
  * the screen-reader table was given the four real numbers on purpose and the
37
38
  * export, which is what people actually take away, was not.
@@ -123,7 +123,9 @@ export async function downloadChartPng(source, filename = 'chart.png', options =
123
123
  setTimeout(() => URL.revokeObjectURL(url), 1000);
124
124
  }
125
125
  const csvCell = (v) => {
126
- const s = v == null ? '' : String(v);
126
+ // A gap is an empty cell: `null` already was, and a NaN gap (the way a
127
+ // forecast series marks the months before it starts) wrote the word "NaN".
128
+ const s = v == null || (typeof v === 'number' && !Number.isFinite(v)) ? '' : String(v);
127
129
  return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
128
130
  };
129
131
  /**
@@ -132,7 +134,8 @@ const csvCell = (v) => {
132
134
  *
133
135
  * A series that carries more than one number per category widens instead of
134
136
  * losing them. `ohlc` becomes four columns, `boxes` five plus its outliers,
135
- * `errors` two. This used to write `values` and nothing else, which quietly
137
+ * `errors` two, `lowValues` a low / high pair, `targets` a value / target
138
+ * pair. This used to write `values` and nothing else, which quietly
136
139
  * made "Export CSV" on a candlestick chart hand back the closing prices only -
137
140
  * the screen-reader table was given the four real numbers on purpose and the
138
141
  * export, which is what people actually take away, was not.
@@ -165,11 +168,15 @@ export function chartSpecToCsv(spec) {
165
168
  const header = ['Category'];
166
169
  for (const s of series) {
167
170
  if (s.ohlc)
168
- header.push(`${s.label} Open`, `${s.label} High`, `${s.label} Low`, `${s.label} Close`);
171
+ header.push(`${s.label} Open`, `${s.label} High`, `${s.label} Low`, `${s.label} Close`, ...(s.volumes ? [`${s.label} Volume`] : []));
169
172
  else if (s.boxes)
170
173
  header.push(`${s.label} Min`, `${s.label} Q1`, `${s.label} Median`, `${s.label} Q3`, `${s.label} Max`, `${s.label} Outliers`);
171
174
  else if (s.errors)
172
175
  header.push(s.label, `${s.label} Low`, `${s.label} High`);
176
+ else if (s.lowValues)
177
+ header.push(`${s.label} low`, `${s.label} high`);
178
+ else if (s.targets)
179
+ header.push(s.label, `${s.label} target`);
173
180
  else
174
181
  header.push(s.label);
175
182
  }
@@ -179,6 +186,8 @@ export function chartSpecToCsv(spec) {
179
186
  if (s.ohlc) {
180
187
  const k = s.ohlc[i];
181
188
  cells.push(k?.o ?? '', k?.h ?? '', k?.l ?? '', k?.c ?? '');
189
+ if (s.volumes)
190
+ cells.push(s.volumes[i] ?? '');
182
191
  }
183
192
  else if (s.boxes) {
184
193
  const b = s.boxes[i];
@@ -192,6 +201,12 @@ export function chartSpecToCsv(spec) {
192
201
  const hi = e == null ? '' : typeof e === 'number' ? (v ?? 0) + Math.abs(e) : Math.max(e.lo, e.hi);
193
202
  cells.push(v ?? '', lo, hi);
194
203
  }
204
+ else if (s.lowValues) {
205
+ cells.push(s.lowValues[i] ?? '', s.values[i] ?? '');
206
+ }
207
+ else if (s.targets) {
208
+ cells.push(s.values[i] ?? '', s.targets[i] ?? '');
209
+ }
195
210
  else {
196
211
  cells.push(s.values[i] ?? '');
197
212
  }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Price-chart helpers: candle transforms, calendar resampling and the
3
+ * row-to-candlestick adapter. Indicators live in chart-indicators. DOM-free.
4
+ */
5
+ import type { ChartSpec, OhlcBar, SeriesOverlay } from './chart-types.js';
6
+ import { type ChartTimeBucket } from './chart-stats.js';
7
+ import type { ChartIndicatorSpec } from './chart-indicators.js';
8
+ /**
9
+ * Heikin-Ashi ("average bar") candles from ordinary OHLC bars. Each close is
10
+ * the mean of the four prices, each open the midpoint of the previous
11
+ * Heikin-Ashi body, and the high / low stretch to include both. Runs of one
12
+ * colour then read as trends, at the cost of the true open and close.
13
+ * `null` bars (gaps) stay null and reset the chain.
14
+ */
15
+ export declare function heikinAshi(bars: ReadonlyArray<OhlcBar | null>): Array<OhlcBar | null>;
16
+ /**
17
+ * Roll daily (or finer) bars up to a coarser calendar bucket: the first
18
+ * open, the highest high, the lowest low, the last close, the summed volume.
19
+ * Categories are ISO dates and come back as the bucket starts (`bucketStart`),
20
+ * sorted; bars whose date does not parse are dropped. Pass `volumes` to get
21
+ * them summed alongside.
22
+ */
23
+ export declare function resampleOhlc(categories: ReadonlyArray<string>, ohlc: ReadonlyArray<OhlcBar | null>, opts: {
24
+ bucket: ChartTimeBucket;
25
+ volumes?: ReadonlyArray<number>;
26
+ }): {
27
+ categories: string[];
28
+ ohlc: Array<OhlcBar | null>;
29
+ volumes?: number[];
30
+ };
31
+ /** What the grid's chart panel stores for a price chart: the column ids. */
32
+ export type OhlcColumns = {
33
+ open: string | null;
34
+ high: string | null;
35
+ low: string | null;
36
+ close: string | null;
37
+ volume: string | null;
38
+ };
39
+ /**
40
+ * Guess the price columns from their names: the first numeric column called
41
+ * open / high / low / close (or last, price) / volume. What the chart panel
42
+ * shows before the reader picks anything; a picked column always wins.
43
+ */
44
+ export declare function guessOhlcColumns(measures: ReadonlyArray<{
45
+ id: string;
46
+ field: string;
47
+ label: string;
48
+ }>, picked?: Partial<OhlcColumns> | null): OhlcColumns;
49
+ /**
50
+ * The `rowsToDirectSpec` options for the grid panel's price chart, or null
51
+ * while a price column is still unpicked. `fieldOf` maps a column id to its
52
+ * field name; the date column is the category.
53
+ */
54
+ export declare function ohlcDirectOptions(fieldOf: (id: string | null) => string | null | undefined, dateId: string | null, cols: OhlcColumns, extra: {
55
+ bucket?: ChartTimeBucket | null;
56
+ candleStyle?: ChartSpec['candleStyle'] | null;
57
+ }): {
58
+ category: string;
59
+ ohlc: NonNullable<Parameters<typeof import('./chart.js').rowsToDirectSpec>[2]['ohlc']>;
60
+ } | null;
61
+ /** The indicator names the grid's chart panel offers. */
62
+ export type ChartPanelIndicator = 'volume' | 'rsi' | 'macd' | 'stochastic' | 'atr' | 'obv' | 'sma' | 'ema' | 'bb' | 'vwap';
63
+ /**
64
+ * Split the panel's indicator list into the panes that go under the price
65
+ * (in order) and the one overlay that rides on it (a series carries a single
66
+ * `overlay`, so the first overlay kind wins).
67
+ */
68
+ export declare function splitPanelIndicators(kinds: ReadonlyArray<ChartPanelIndicator>): {
69
+ panes: ChartIndicatorSpec[];
70
+ overlay: SeriesOverlay | null;
71
+ };
72
+ /**
73
+ * A candlestick spec straight from rows: one bar per date (sorted), with the
74
+ * volume alongside when a field is named, optionally rolled up to a calendar
75
+ * bucket first. The x axis is ordinal-time, so weekends and holidays leave no
76
+ * gaps.
77
+ */
78
+ export declare function rowsToOhlcSpec<T extends Record<string, unknown>>(rows: ReadonlyArray<T>, opts: {
79
+ date: keyof T & string;
80
+ open: keyof T & string;
81
+ high: keyof T & string;
82
+ low: keyof T & string;
83
+ close: keyof T & string;
84
+ volume?: keyof T & string;
85
+ bucket?: ChartTimeBucket;
86
+ /** The series label. Default "Price". */
87
+ label?: string;
88
+ type?: 'candlestick' | 'ohlc';
89
+ candleStyle?: ChartSpec['candleStyle'];
90
+ lastPriceLine?: boolean;
91
+ }): ChartSpec;