@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,190 @@
1
+ import { round, formatChartValue, resolveColorScale, sampleGradient, pickContrastText, thinCategoryTicks } from './chart-scale.js';
2
+ // ---- Calendar heatmap --------------------------------------------
3
+ // GitHub-style year-of-days view: 7 rows (Sun..Sat) x N weeks. Each
4
+ // cell is a small square shaded by `calendarValues[i].value` via the
5
+ // sequential color scale. Days with no value render blank (border only)
6
+ // so missing data is visually obvious.
7
+ /** @internal Lay out the calendar family. Called by buildChart. */
8
+ export function layoutCalendar(ctx) {
9
+ const { spec, width, height, theme, empty, frame } = ctx;
10
+ const values = spec.calendarValues ?? [];
11
+ if (!values.length && !spec.calendarStart)
12
+ return { ...empty };
13
+ // Build a value lookup + figure out the date range.
14
+ const valueByDate = new Map();
15
+ let vMin = Infinity, vMax = -Infinity;
16
+ for (const v of values) {
17
+ valueByDate.set(v.date, v.value);
18
+ if (Number.isFinite(v.value)) {
19
+ if (v.value < vMin)
20
+ vMin = v.value;
21
+ if (v.value > vMax)
22
+ vMax = v.value;
23
+ }
24
+ }
25
+ if (vMin === Infinity) {
26
+ vMin = 0;
27
+ vMax = 1;
28
+ }
29
+ if (vMin === vMax)
30
+ vMax = vMin + 1;
31
+ const stops = resolveColorScale(spec.colorScale, vMin, vMax, theme);
32
+ const colorAt = (v) => sampleGradient(stops, (v - vMin) / (vMax - vMin));
33
+ // Determine date range. If calendarStart/End set, use them, otherwise
34
+ // span the data + round to whole weeks (Sun..Sat).
35
+ const sorted = values.map((v) => v.date).sort();
36
+ const startStr = spec.calendarStart ?? sorted[0] ?? '2026-01-01';
37
+ const endStr = spec.calendarEnd ?? sorted[sorted.length - 1] ?? startStr;
38
+ const start = new Date(startStr + 'T00:00:00Z');
39
+ const end = new Date(endStr + 'T00:00:00Z');
40
+ // Roll start back to its Sunday, end forward to its Saturday.
41
+ start.setUTCDate(start.getUTCDate() - start.getUTCDay());
42
+ end.setUTCDate(end.getUTCDate() + (6 - end.getUTCDay()));
43
+ const totalDays = Math.round((end.getTime() - start.getTime()) / 86400000) + 1;
44
+ const weeks = Math.ceil(totalDays / 7);
45
+ const padL = 36, padR = 80, padT = 26 + frame.top, padB = 16 + frame.bottom;
46
+ const plotW = Math.max(1, width - padL - padR);
47
+ const plotH = Math.max(1, height - padT - padB);
48
+ // Cell size: fit weeks across width, 7 rows down height.
49
+ const cellW = Math.floor(plotW / weeks);
50
+ const cellH = Math.floor(plotH / 7);
51
+ const cellSize = Math.max(6, Math.min(cellW, cellH));
52
+ const plot = { x: padL, y: padT, w: cellSize * weeks, h: cellSize * 7 };
53
+ const cells = [];
54
+ let lastMonth = -1;
55
+ const monthTicks = [];
56
+ for (let i = 0; i < totalDays; i += 1) {
57
+ const day = new Date(start.getTime() + i * 86400000);
58
+ const col = Math.floor(i / 7);
59
+ const row = i % 7;
60
+ const date = day.toISOString().slice(0, 10);
61
+ const has = valueByDate.has(date);
62
+ const v = valueByDate.get(date) ?? 0;
63
+ cells.push({
64
+ x: padL + col * cellSize,
65
+ y: padT + row * cellSize,
66
+ size: cellSize,
67
+ date, value: v,
68
+ defined: has,
69
+ color: has ? colorAt(v) : 'transparent',
70
+ });
71
+ // A month label only for a month inside the range: the end is rolled
72
+ // forward to its Saturday, and a December that ends on a Thursday used
73
+ // to pick up a "Jan" for the two padding days of the next year.
74
+ if (day.getUTCDate() === 1 && day.getUTCMonth() !== lastMonth && date >= startStr && date <= endStr) {
75
+ lastMonth = day.getUTCMonth();
76
+ monthTicks.push({
77
+ label: day.toLocaleDateString(undefined, { month: 'short' }),
78
+ x: padL + col * cellSize,
79
+ });
80
+ }
81
+ }
82
+ const legend = Array.from({ length: 5 }, (_, i) => {
83
+ const t = i / 4;
84
+ const value = legendValue(vMin, vMax, t);
85
+ return { value, color: colorAt(value), label: formatChartValue(value, spec.valueFormat, spec) };
86
+ });
87
+ return {
88
+ ...empty,
89
+ plot,
90
+ calendarCells: cells,
91
+ calendarMonthTicks: monthTicks,
92
+ calendarLegend: legend,
93
+ };
94
+ }
95
+ // ---- Heatmap ------------------------------------------------------
96
+ // Each series is one row, series.values are the cells across categories.
97
+ // Color comes from a sequential/diverging/custom palette mapped to the
98
+ // global value range. Cell text contrasts black/white against the cell.
99
+ /**
100
+ * A legend step between the extremes, rounded to the precision the range
101
+ * earns: whole numbers when the steps are a unit or more apart, so a count
102
+ * legend reads "20, 94, 167" rather than "20, 93.5, 167", and enough
103
+ * decimals to tell the steps apart when they are not.
104
+ */
105
+ function legendValue(vMin, vMax, t) {
106
+ const raw = vMin + (vMax - vMin) * t;
107
+ const step = Math.abs(vMax - vMin) / 4;
108
+ if (!(step > 0))
109
+ return raw;
110
+ const decimals = step >= 1 ? 0 : Math.min(6, Math.ceil(-Math.log10(step)) + 1);
111
+ return Number(raw.toFixed(decimals));
112
+ }
113
+ /** @internal Lay out the heatmap family. Called by buildChart. */
114
+ export function layoutHeatmap(ctx) {
115
+ const { spec, series, width, height, theme, empty, frame } = ctx;
116
+ if (!series.length || !spec.categories.length)
117
+ return { ...empty, plot: { x: 0, y: 0, w: width, h: height } };
118
+ // Layout: left gutter for row labels, bottom for column labels.
119
+ const maxRowLabel = series.reduce((m, s) => Math.max(m, s.label.length), 0);
120
+ const padL = 12 + Math.min(180, Math.max(60, maxRowLabel * 7));
121
+ const padR = 64; // room for the right-side legend bar
122
+ const padT = 12 + frame.top;
123
+ const padB = 32 + frame.bottom;
124
+ const plotW = Math.max(1, width - padL - padR);
125
+ const plotH = Math.max(1, height - padT - padB);
126
+ const plot = { x: padL, y: padT, w: plotW, h: plotH };
127
+ const cellW = plotW / spec.categories.length;
128
+ const cellH = plotH / series.length;
129
+ // Resolve value range across the whole matrix.
130
+ let vMin = Infinity, vMax = -Infinity;
131
+ for (const s of series)
132
+ for (const v of s.values) {
133
+ if (!Number.isFinite(v))
134
+ continue;
135
+ if (v < vMin)
136
+ vMin = v;
137
+ if (v > vMax)
138
+ vMax = v;
139
+ }
140
+ if (vMin === Infinity) {
141
+ vMin = 0;
142
+ vMax = 1;
143
+ }
144
+ if (vMin === vMax)
145
+ vMax = vMin + 1;
146
+ // Pick the palette stops.
147
+ const stops = resolveColorScale(spec.colorScale, vMin, vMax, theme);
148
+ const colorAt = (v) => sampleGradient(stops, (v - vMin) / (vMax - vMin));
149
+ const heatmapCells = [];
150
+ series.forEach((s, ri) => {
151
+ s.values.forEach((v, ci) => {
152
+ if (!Number.isFinite(v))
153
+ return;
154
+ const color = colorAt(v);
155
+ heatmapCells.push({
156
+ x: round(padL + cellW * ci),
157
+ y: round(padT + cellH * ri),
158
+ w: round(cellW),
159
+ h: round(cellH),
160
+ color,
161
+ textColor: pickContrastText(color),
162
+ value: v,
163
+ rowLabel: s.label,
164
+ colLabel: spec.categories[ci] ?? '',
165
+ });
166
+ });
167
+ });
168
+ const heatmapRowTicks = series.map((s, i) => ({
169
+ value: i,
170
+ y: round(padT + cellH * i + cellH / 2),
171
+ label: s.label,
172
+ }));
173
+ // Column labels thin out the way a bar chart's do: twenty-four hours at
174
+ // 33px a cell wrote "00:0001:0002:00" across the foot of the matrix.
175
+ const heatmapColTicks = thinCategoryTicks(spec.categories, (i) => round(padL + cellW * i + cellW / 2), cellW, false);
176
+ // Legend: sample 5 stops across the range.
177
+ const heatmapLegend = Array.from({ length: 5 }, (_, i) => {
178
+ const t = i / 4;
179
+ const value = legendValue(vMin, vMax, t);
180
+ return { value, color: colorAt(value), label: formatChartValue(value, spec.valueFormat, spec) };
181
+ });
182
+ return {
183
+ ...empty,
184
+ plot,
185
+ heatmapCells,
186
+ heatmapRowTicks,
187
+ heatmapColTicks,
188
+ heatmapLegend,
189
+ };
190
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Hierarchy layouts: the squarified tree-map.
3
+ */
4
+ import type { TreeNode, ChartGeometry, LayoutCtx } from './chart-types.js';
5
+ /** @internal Lay out the treemap family. Called by buildChart. */
6
+ export declare function layoutTreemap(ctx: LayoutCtx): ChartGeometry;
7
+ /**
8
+ * The subtree at `path` (a list of node names from the root's children down),
9
+ * or the tree itself for an empty path. What a drilldown shows after each
10
+ * click: the clicked node becomes the root. Null when the path does not exist.
11
+ */
12
+ export declare function drillTree(tree: TreeNode, path: ReadonlyArray<string>): TreeNode | null;
13
+ /**
14
+ * The path (node names from the root's children) to the first node called
15
+ * `name`, depth first, or null. What a click on a leaf's label needs.
16
+ */
17
+ export declare function pathTo(tree: TreeNode, name: string): string[] | null;
@@ -0,0 +1,146 @@
1
+ import { round, pickContrastText, DEFAULT_PALETTE } from './chart-scale.js';
2
+ // ---- Tree-map -----------------------------------------------------
3
+ // Squarified tree-map (Bruls et al. 2000): each level recursively
4
+ // partitions its rectangle in proportion to its children, picking the
5
+ // split orientation that keeps aspect ratios closest to 1.
6
+ /** @internal Lay out the treemap family. Called by buildChart. */
7
+ export function layoutTreemap(ctx) {
8
+ const { spec, width, height, empty, frame } = ctx;
9
+ const root = spec.treemap ?? spec.tree;
10
+ if (!root)
11
+ return { ...empty };
12
+ const padL = 4, padR = 4, padT = 4 + frame.top, padB = 4 + frame.bottom;
13
+ const plotW = Math.max(1, width - padL - padR);
14
+ const plotH = Math.max(1, height - padT - padB);
15
+ const plot = { x: padL, y: padT, w: plotW, h: plotH };
16
+ const palette = spec.palette ?? DEFAULT_PALETTE;
17
+ const cells = [];
18
+ function totalOf(n) {
19
+ if (n.children?.length)
20
+ return n.children.reduce((s, c) => s + totalOf(c), 0);
21
+ return Math.max(0, n.value ?? 0);
22
+ }
23
+ function squarify(items, x, y, w, h, depth) {
24
+ if (!items.length || w <= 0 || h <= 0)
25
+ return;
26
+ const totals = items.map(totalOf);
27
+ const sum = totals.reduce((a, b) => a + b, 0);
28
+ if (sum <= 0)
29
+ return;
30
+ // Process largest-first so big items dominate the first row.
31
+ const ordered = items
32
+ .map((n, i) => ({ node: n, value: totals[i] }))
33
+ .sort((a, b) => b.value - a.value);
34
+ let cx = x, cy = y, cw = w, ch = h, remaining = sum;
35
+ let row = [];
36
+ const worstRatio = (vals, shortSide, rowSum, scale) => {
37
+ if (rowSum <= 0)
38
+ return Infinity;
39
+ const rowArea = rowSum * scale;
40
+ const rowSide = rowArea / shortSide;
41
+ let worst = 0;
42
+ for (const v of vals) {
43
+ const cell = v * scale;
44
+ const long = cell / rowSide;
45
+ const r = Math.max(shortSide / long, long / shortSide);
46
+ if (r > worst)
47
+ worst = r;
48
+ }
49
+ return worst;
50
+ };
51
+ function flushRow() {
52
+ if (!row.length)
53
+ return;
54
+ const rowSum = row.reduce((a, b) => a + b.value, 0);
55
+ const scale = (cw * ch) / remaining;
56
+ const horizontal = cw >= ch;
57
+ const shortSide = horizontal ? ch : cw;
58
+ const rowSide = (rowSum * scale) / shortSide;
59
+ let offset = 0;
60
+ for (const it of row) {
61
+ const cellSize = (it.value * scale) / rowSide;
62
+ const cx2 = horizontal ? cx : cx + offset;
63
+ const cy2 = horizontal ? cy + offset : cy;
64
+ const ww = horizontal ? rowSide : cellSize;
65
+ const hh = horizontal ? cellSize : rowSide;
66
+ const color = it.node.color ?? palette[(depth + cells.length) % palette.length];
67
+ // Leaf: emit a cell. Branch: recurse into the rect minus a label gutter.
68
+ if (it.node.children?.length) {
69
+ cells.push({
70
+ x: round(cx2), y: round(cy2), w: round(ww), h: round(hh),
71
+ color, textColor: pickContrastText(color),
72
+ name: it.node.name, value: it.value, depth,
73
+ });
74
+ const labelH = Math.min(18, hh * 0.25);
75
+ squarify(it.node.children, cx2 + 1, cy2 + labelH, ww - 2, hh - labelH - 1, depth + 1);
76
+ }
77
+ else {
78
+ cells.push({
79
+ x: round(cx2), y: round(cy2), w: round(ww), h: round(hh),
80
+ color, textColor: pickContrastText(color),
81
+ name: it.node.name, value: it.value, depth,
82
+ });
83
+ }
84
+ offset += cellSize;
85
+ }
86
+ // Shrink the remaining strip.
87
+ if (horizontal) {
88
+ cx += rowSide;
89
+ cw -= rowSide;
90
+ }
91
+ else {
92
+ cy += rowSide;
93
+ ch -= rowSide;
94
+ }
95
+ remaining -= rowSum;
96
+ row = [];
97
+ }
98
+ for (const it of ordered) {
99
+ const scale = (cw * ch) / remaining;
100
+ const shortSide = Math.min(cw, ch);
101
+ const rowSum = row.reduce((a, b) => a + b.value, 0);
102
+ const currWorst = worstRatio(row.map((r) => r.value), shortSide, rowSum, scale);
103
+ const nextWorst = worstRatio([...row.map((r) => r.value), it.value], shortSide, rowSum + it.value, scale);
104
+ if (row.length && nextWorst > currWorst) {
105
+ flushRow();
106
+ }
107
+ row.push(it);
108
+ }
109
+ flushRow();
110
+ }
111
+ const seedItems = root.children ?? [root];
112
+ squarify(seedItems, padL, padT, plotW, plotH, 0);
113
+ return { ...empty, plot, treemapCells: cells };
114
+ }
115
+ /**
116
+ * The subtree at `path` (a list of node names from the root's children down),
117
+ * or the tree itself for an empty path. What a drilldown shows after each
118
+ * click: the clicked node becomes the root. Null when the path does not exist.
119
+ */
120
+ export function drillTree(tree, path) {
121
+ let node = tree;
122
+ for (const name of path) {
123
+ node = node?.children?.find((c) => c.name === name);
124
+ if (!node)
125
+ return null;
126
+ }
127
+ return node ?? null;
128
+ }
129
+ /**
130
+ * The path (node names from the root's children) to the first node called
131
+ * `name`, depth first, or null. What a click on a leaf's label needs.
132
+ */
133
+ export function pathTo(tree, name) {
134
+ const walk = (n, acc) => {
135
+ for (const c of n.children ?? []) {
136
+ const next = [...acc, c.name];
137
+ if (c.name === name)
138
+ return next;
139
+ const deeper = walk(c, next);
140
+ if (deeper)
141
+ return deeper;
142
+ }
143
+ return null;
144
+ };
145
+ return walk(tree, []);
146
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Technical indicators for financial charts: the overlays that sit on the
3
+ * price (Bollinger bands, VWAP, weighted moving average) and the oscillators
4
+ * that get a pane of their own (RSI, MACD, stochastic, ATR, OBV, volume).
5
+ *
6
+ * Every function is pure, takes plain arrays and returns arrays of the same
7
+ * length, NaN-padded where the window is not full yet, so the result lines up
8
+ * with `categories` and draws as a gap rather than as a fake zero. Wilder's
9
+ * smoothing (RSI, ATR) seeds with a simple average over the first period and
10
+ * then smooths recursively, which is what every charting terminal shows.
11
+ */
12
+ import type { ChartSpec, OhlcBar, SeriesOverlay } from './chart-types.js';
13
+ import { type RegressionFit } from './chart-stats.js';
14
+ /** Which indicator a pane shows, and with what parameters. */
15
+ export type ChartIndicatorSpec = {
16
+ kind: 'rsi' | 'macd' | 'stochastic' | 'atr' | 'obv' | 'volume';
17
+ /** The series (by label) the indicator reads. Defaults to the first
18
+ * series with `ohlc`, else the first series. */
19
+ source?: string;
20
+ /** Periods: `period` (RSI / ATR / stochastic %K), `fast` / `slow` /
21
+ * `signal` (MACD), `smooth` (stochastic %D). Each has the usual default. */
22
+ params?: {
23
+ period?: number;
24
+ fast?: number;
25
+ slow?: number;
26
+ signal?: number;
27
+ smooth?: number;
28
+ };
29
+ /** Pane height in px; the default is a third of the main chart. */
30
+ height?: number;
31
+ };
32
+ /** Weighted moving average: the latest value weighs `period`, the oldest 1. */
33
+ export declare function wma(values: ReadonlyArray<number>, period: number): number[];
34
+ /**
35
+ * Bollinger bands: a simple moving average with a band `k` standard
36
+ * deviations either side (population deviation, as the terminals compute it).
37
+ */
38
+ export declare function bollingerBands(values: ReadonlyArray<number>, period?: number, k?: number): {
39
+ middle: number[];
40
+ upper: number[];
41
+ lower: number[];
42
+ };
43
+ /**
44
+ * Relative strength index over `period` bars, 0 to 100. The first value lands
45
+ * at index `period`; before it there is nothing to average.
46
+ */
47
+ export declare function rsi(values: ReadonlyArray<number>, period?: number): number[];
48
+ /**
49
+ * Moving average convergence / divergence: the fast EMA minus the slow one,
50
+ * its signal EMA, and the histogram between them.
51
+ */
52
+ export declare function macd(values: ReadonlyArray<number>, fast?: number, slow?: number, signal?: number): {
53
+ macd: number[];
54
+ signal: number[];
55
+ histogram: number[];
56
+ };
57
+ /**
58
+ * Volume-weighted average price, cumulative from the first bar. Reset it per
59
+ * session by resampling or slicing before calling.
60
+ */
61
+ export declare function vwap(ohlc: ReadonlyArray<OhlcBar | null | undefined>, volumes: ReadonlyArray<number>): number[];
62
+ /** Average true range with Wilder's smoothing. */
63
+ export declare function atr(ohlc: ReadonlyArray<OhlcBar | null | undefined>, period?: number): number[];
64
+ /**
65
+ * Stochastic oscillator: %K is where the close sits in the last `period`
66
+ * bars' range (0 to 100), %D its `smooth`-bar simple average.
67
+ */
68
+ export declare function stochastic(ohlc: ReadonlyArray<OhlcBar | null | undefined>, period?: number, smooth?: number): {
69
+ k: number[];
70
+ d: number[];
71
+ };
72
+ /** On-balance volume: a running total that adds volume on up closes and
73
+ * subtracts it on down closes. */
74
+ export declare function obv(closes: ReadonlyArray<number>, volumes: ReadonlyArray<number>): number[];
75
+ /**
76
+ * Compute overlay values for a series spec like 'sma:7' / 'ema:14' / 'wma:9' /
77
+ * 'linear'. `'bb:N:K'` returns the band's middle line (the engine draws the
78
+ * band from `bollingerBands` itself) and `'vwap'` needs bars and volumes, so
79
+ * it comes back as gaps here; pass `extra` for those two.
80
+ */
81
+ export declare function computeOverlay(values: number[], spec: SeriesOverlay, extra?: {
82
+ ohlc?: ReadonlyArray<OhlcBar | null>;
83
+ volumes?: ReadonlyArray<number>;
84
+ }): number[];
85
+ /**
86
+ * {@link computeOverlay} plus what a regression knows about itself: its
87
+ * R-squared and equation. The moving averages, the bands and VWAP carry
88
+ * neither.
89
+ */
90
+ export declare function computeOverlayFit(values: number[], spec: SeriesOverlay, extra?: {
91
+ ohlc?: ReadonlyArray<OhlcBar | null>;
92
+ volumes?: ReadonlyArray<number>;
93
+ }): {
94
+ values: number[];
95
+ r2?: number;
96
+ equation?: string;
97
+ };
98
+ /**
99
+ * The regression an overlay string names (`linear`, `poly:N`, `exp`, `log`,
100
+ * `power`), fitted to `values` on their index or on `xs`; `null` for the
101
+ * moving averages and bands, which are not regressions. A scatter passes its
102
+ * points' x and y so the curve is y on x, and reads `predict` to draw it.
103
+ */
104
+ export declare function regressionFit(values: ReadonlyArray<number>, spec: SeriesOverlay, xs?: ReadonlyArray<number>): RegressionFit | null;
105
+ /** The name a tooltip or legend gives an overlay: `sma:20` reads "SMA 20",
106
+ * `poly:3` "poly 3", `bb:20:2` "BB 20", the rest as written. */
107
+ export declare function overlayName(spec: SeriesOverlay): string;
108
+ /**
109
+ * Build the spec of an indicator pane from a price spec: the same categories
110
+ * and x axis, the indicator's lines or bars, and the reference lines the
111
+ * indicator is read against (30 / 70 for RSI, 20 / 80 for the stochastic, a
112
+ * zero line for MACD). `SvChartPanes` stacks these under the price chart and
113
+ * syncs them; a pane is also a complete spec for a standalone `SvChart`.
114
+ */
115
+ export declare function indicatorPane(spec: ChartSpec, ind: ChartIndicatorSpec): ChartSpec;