@rowsncolumns/charts 27.0.30

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/LICENSE.md +100 -0
  2. package/Readme.md +81 -0
  3. package/dist/cjs/atoms/editing-chart.atom.d.ts +13 -0
  4. package/dist/cjs/atoms/editing-chart.atom.d.ts.map +1 -0
  5. package/dist/cjs/atoms/editing-chart.atom.js +26 -0
  6. package/dist/cjs/basic-chart/basic-chart.d.ts +5 -0
  7. package/dist/cjs/basic-chart/basic-chart.d.ts.map +1 -0
  8. package/dist/cjs/basic-chart/basic-chart.js +2250 -0
  9. package/dist/cjs/basic-chart/helpers.d.ts +12 -0
  10. package/dist/cjs/basic-chart/helpers.d.ts.map +1 -0
  11. package/dist/cjs/basic-chart/helpers.js +105 -0
  12. package/dist/cjs/basic-chart/index.d.ts +5 -0
  13. package/dist/cjs/basic-chart/index.d.ts.map +1 -0
  14. package/dist/cjs/basic-chart/index.js +12 -0
  15. package/dist/cjs/components/dialogs/chart-editor-dialog.d.ts +5 -0
  16. package/dist/cjs/components/dialogs/chart-editor-dialog.d.ts.map +1 -0
  17. package/dist/cjs/components/dialogs/chart-editor-dialog.js +25 -0
  18. package/dist/cjs/components/editors/chart-editor.d.ts +10 -0
  19. package/dist/cjs/components/editors/chart-editor.d.ts.map +1 -0
  20. package/dist/cjs/components/editors/chart-editor.js +631 -0
  21. package/dist/cjs/components/editors/chart-range-input.d.ts +17 -0
  22. package/dist/cjs/components/editors/chart-range-input.d.ts.map +1 -0
  23. package/dist/cjs/components/editors/chart-range-input.js +16 -0
  24. package/dist/cjs/config.d.ts +19 -0
  25. package/dist/cjs/config.d.ts.map +1 -0
  26. package/dist/cjs/config.js +196 -0
  27. package/dist/cjs/hooks/use-chart-editor.d.ts +8 -0
  28. package/dist/cjs/hooks/use-chart-editor.d.ts.map +1 -0
  29. package/dist/cjs/hooks/use-chart-editor.js +29 -0
  30. package/dist/cjs/hooks/use-charts.d.ts +35 -0
  31. package/dist/cjs/hooks/use-charts.d.ts.map +1 -0
  32. package/dist/cjs/hooks/use-charts.js +67 -0
  33. package/dist/cjs/hooks/use-on-create-chart.d.ts +13 -0
  34. package/dist/cjs/hooks/use-on-create-chart.d.ts.map +1 -0
  35. package/dist/cjs/hooks/use-on-create-chart.js +24 -0
  36. package/dist/cjs/hooks/use-on-delete-chart.d.ts +8 -0
  37. package/dist/cjs/hooks/use-on-delete-chart.d.ts.map +1 -0
  38. package/dist/cjs/hooks/use-on-delete-chart.js +19 -0
  39. package/dist/cjs/hooks/use-on-move-chart.d.ts +8 -0
  40. package/dist/cjs/hooks/use-on-move-chart.d.ts.map +1 -0
  41. package/dist/cjs/hooks/use-on-move-chart.js +15 -0
  42. package/dist/cjs/hooks/use-on-resize-chart.d.ts +8 -0
  43. package/dist/cjs/hooks/use-on-resize-chart.d.ts.map +1 -0
  44. package/dist/cjs/hooks/use-on-resize-chart.js +15 -0
  45. package/dist/cjs/hooks/use-on-update-chart.d.ts +9 -0
  46. package/dist/cjs/hooks/use-on-update-chart.d.ts.map +1 -0
  47. package/dist/cjs/hooks/use-on-update-chart.js +15 -0
  48. package/dist/cjs/index.d.ts +11 -0
  49. package/dist/cjs/index.d.ts.map +1 -0
  50. package/dist/cjs/index.js +18 -0
  51. package/dist/cjs/interface/create-chart.d.ts +9 -0
  52. package/dist/cjs/interface/create-chart.d.ts.map +1 -0
  53. package/dist/cjs/interface/create-chart.js +110 -0
  54. package/dist/cjs/interface/delete-chart.d.ts +4 -0
  55. package/dist/cjs/interface/delete-chart.d.ts.map +1 -0
  56. package/dist/cjs/interface/delete-chart.js +26 -0
  57. package/dist/cjs/interface/move-chart.d.ts +5 -0
  58. package/dist/cjs/interface/move-chart.d.ts.map +1 -0
  59. package/dist/cjs/interface/move-chart.js +28 -0
  60. package/dist/cjs/interface/resize-chart.d.ts +5 -0
  61. package/dist/cjs/interface/resize-chart.d.ts.map +1 -0
  62. package/dist/cjs/interface/resize-chart.js +30 -0
  63. package/dist/cjs/interface/update-chart.d.ts +4 -0
  64. package/dist/cjs/interface/update-chart.d.ts.map +1 -0
  65. package/dist/cjs/interface/update-chart.js +29 -0
  66. package/dist/cjs/plotly/basic-chart/basic-chart.d.ts +5 -0
  67. package/dist/cjs/plotly/basic-chart/basic-chart.d.ts.map +1 -0
  68. package/dist/cjs/plotly/basic-chart/basic-chart.js +1586 -0
  69. package/dist/cjs/plotly/basic-chart/helpers.d.ts +20 -0
  70. package/dist/cjs/plotly/basic-chart/helpers.d.ts.map +1 -0
  71. package/dist/cjs/plotly/basic-chart/helpers.js +114 -0
  72. package/dist/cjs/plotly/basic-chart/index.d.ts +5 -0
  73. package/dist/cjs/plotly/basic-chart/index.d.ts.map +1 -0
  74. package/dist/cjs/plotly/basic-chart/index.js +12 -0
  75. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
  76. package/dist/cjs/types.d.ts +27 -0
  77. package/dist/cjs/types.d.ts.map +1 -0
  78. package/dist/cjs/types.js +2 -0
  79. package/dist/cjs/utils/aggregation.d.ts +6 -0
  80. package/dist/cjs/utils/aggregation.d.ts.map +1 -0
  81. package/dist/cjs/utils/aggregation.js +127 -0
  82. package/dist/cjs/utils/bar-layout.d.ts +57 -0
  83. package/dist/cjs/utils/bar-layout.d.ts.map +1 -0
  84. package/dist/cjs/utils/bar-layout.js +81 -0
  85. package/dist/cjs/utils/boxplot.d.ts +62 -0
  86. package/dist/cjs/utils/boxplot.d.ts.map +1 -0
  87. package/dist/cjs/utils/boxplot.js +105 -0
  88. package/dist/cjs/utils/bubble.d.ts +52 -0
  89. package/dist/cjs/utils/bubble.d.ts.map +1 -0
  90. package/dist/cjs/utils/bubble.js +139 -0
  91. package/dist/cjs/utils/chart-spec-utils.d.ts +67 -0
  92. package/dist/cjs/utils/chart-spec-utils.d.ts.map +1 -0
  93. package/dist/cjs/utils/chart-spec-utils.js +355 -0
  94. package/dist/cjs/utils/echarts-gl-loader.d.ts +11 -0
  95. package/dist/cjs/utils/echarts-gl-loader.d.ts.map +1 -0
  96. package/dist/cjs/utils/echarts-gl-loader.js +59 -0
  97. package/dist/cjs/utils/error-bars.d.ts +58 -0
  98. package/dist/cjs/utils/error-bars.d.ts.map +1 -0
  99. package/dist/cjs/utils/error-bars.js +154 -0
  100. package/dist/cjs/utils/gradient.d.ts +52 -0
  101. package/dist/cjs/utils/gradient.d.ts.map +1 -0
  102. package/dist/cjs/utils/gradient.js +93 -0
  103. package/dist/cjs/utils/hierarchy.d.ts +28 -0
  104. package/dist/cjs/utils/hierarchy.d.ts.map +1 -0
  105. package/dist/cjs/utils/hierarchy.js +186 -0
  106. package/dist/cjs/utils/histogram.d.ts +46 -0
  107. package/dist/cjs/utils/histogram.d.ts.map +1 -0
  108. package/dist/cjs/utils/histogram.js +125 -0
  109. package/dist/cjs/utils/of-pie.d.ts +101 -0
  110. package/dist/cjs/utils/of-pie.d.ts.map +1 -0
  111. package/dist/cjs/utils/of-pie.js +159 -0
  112. package/dist/cjs/utils/overrides.d.ts +12 -0
  113. package/dist/cjs/utils/overrides.d.ts.map +1 -0
  114. package/dist/cjs/utils/overrides.js +171 -0
  115. package/dist/cjs/utils/radar.d.ts +37 -0
  116. package/dist/cjs/utils/radar.d.ts.map +1 -0
  117. package/dist/cjs/utils/radar.js +70 -0
  118. package/dist/cjs/utils/region-map-loader.d.ts +22 -0
  119. package/dist/cjs/utils/region-map-loader.d.ts.map +1 -0
  120. package/dist/cjs/utils/region-map-loader.js +174 -0
  121. package/dist/cjs/utils/region-map.d.ts +100 -0
  122. package/dist/cjs/utils/region-map.d.ts.map +1 -0
  123. package/dist/cjs/utils/region-map.js +327 -0
  124. package/dist/cjs/utils/stock.d.ts +64 -0
  125. package/dist/cjs/utils/stock.d.ts.map +1 -0
  126. package/dist/cjs/utils/stock.js +153 -0
  127. package/dist/cjs/utils/three-d.d.ts +96 -0
  128. package/dist/cjs/utils/three-d.d.ts.map +1 -0
  129. package/dist/cjs/utils/three-d.js +191 -0
  130. package/dist/cjs/utils/trendline.d.ts +41 -0
  131. package/dist/cjs/utils/trendline.d.ts.map +1 -0
  132. package/dist/cjs/utils/trendline.js +303 -0
  133. package/dist/cjs/utils/waterfall.d.ts +24 -0
  134. package/dist/cjs/utils/waterfall.d.ts.map +1 -0
  135. package/dist/cjs/utils/waterfall.js +61 -0
  136. package/dist/cjs/utils/webgl.d.ts +12 -0
  137. package/dist/cjs/utils/webgl.d.ts.map +1 -0
  138. package/dist/cjs/utils/webgl.js +36 -0
  139. package/dist/cjs/vitest.config.d.ts +3 -0
  140. package/dist/cjs/vitest.config.d.ts.map +1 -0
  141. package/dist/cjs/vitest.config.js +9 -0
  142. package/dist/esm/atoms/editing-chart.atom.d.ts +13 -0
  143. package/dist/esm/atoms/editing-chart.atom.d.ts.map +1 -0
  144. package/dist/esm/atoms/editing-chart.atom.js +12 -0
  145. package/dist/esm/basic-chart/basic-chart.d.ts +5 -0
  146. package/dist/esm/basic-chart/basic-chart.d.ts.map +1 -0
  147. package/dist/esm/basic-chart/basic-chart.js +2343 -0
  148. package/dist/esm/basic-chart/helpers.d.ts +12 -0
  149. package/dist/esm/basic-chart/helpers.d.ts.map +1 -0
  150. package/dist/esm/basic-chart/helpers.js +98 -0
  151. package/dist/esm/basic-chart/index.d.ts +5 -0
  152. package/dist/esm/basic-chart/index.d.ts.map +1 -0
  153. package/dist/esm/basic-chart/index.js +7 -0
  154. package/dist/esm/components/dialogs/chart-editor-dialog.d.ts +5 -0
  155. package/dist/esm/components/dialogs/chart-editor-dialog.d.ts.map +1 -0
  156. package/dist/esm/components/dialogs/chart-editor-dialog.js +19 -0
  157. package/dist/esm/components/editors/chart-editor.d.ts +10 -0
  158. package/dist/esm/components/editors/chart-editor.d.ts.map +1 -0
  159. package/dist/esm/components/editors/chart-editor.js +550 -0
  160. package/dist/esm/components/editors/chart-range-input.d.ts +17 -0
  161. package/dist/esm/components/editors/chart-range-input.d.ts.map +1 -0
  162. package/dist/esm/components/editors/chart-range-input.js +10 -0
  163. package/dist/esm/config.d.ts +19 -0
  164. package/dist/esm/config.d.ts.map +1 -0
  165. package/dist/esm/config.js +161 -0
  166. package/dist/esm/hooks/use-chart-editor.d.ts +8 -0
  167. package/dist/esm/hooks/use-chart-editor.d.ts.map +1 -0
  168. package/dist/esm/hooks/use-chart-editor.js +32 -0
  169. package/dist/esm/hooks/use-charts.d.ts +35 -0
  170. package/dist/esm/hooks/use-charts.d.ts.map +1 -0
  171. package/dist/esm/hooks/use-charts.js +62 -0
  172. package/dist/esm/hooks/use-on-create-chart.d.ts +13 -0
  173. package/dist/esm/hooks/use-on-create-chart.d.ts.map +1 -0
  174. package/dist/esm/hooks/use-on-create-chart.js +19 -0
  175. package/dist/esm/hooks/use-on-delete-chart.d.ts +8 -0
  176. package/dist/esm/hooks/use-on-delete-chart.d.ts.map +1 -0
  177. package/dist/esm/hooks/use-on-delete-chart.js +14 -0
  178. package/dist/esm/hooks/use-on-move-chart.d.ts +8 -0
  179. package/dist/esm/hooks/use-on-move-chart.d.ts.map +1 -0
  180. package/dist/esm/hooks/use-on-move-chart.js +10 -0
  181. package/dist/esm/hooks/use-on-resize-chart.d.ts +8 -0
  182. package/dist/esm/hooks/use-on-resize-chart.d.ts.map +1 -0
  183. package/dist/esm/hooks/use-on-resize-chart.js +10 -0
  184. package/dist/esm/hooks/use-on-update-chart.d.ts +9 -0
  185. package/dist/esm/hooks/use-on-update-chart.d.ts.map +1 -0
  186. package/dist/esm/hooks/use-on-update-chart.js +10 -0
  187. package/dist/esm/index.d.ts +11 -0
  188. package/dist/esm/index.d.ts.map +1 -0
  189. package/dist/esm/index.js +13 -0
  190. package/dist/esm/interface/create-chart.d.ts +9 -0
  191. package/dist/esm/interface/create-chart.d.ts.map +1 -0
  192. package/dist/esm/interface/create-chart.js +126 -0
  193. package/dist/esm/interface/delete-chart.d.ts +4 -0
  194. package/dist/esm/interface/delete-chart.d.ts.map +1 -0
  195. package/dist/esm/interface/delete-chart.js +20 -0
  196. package/dist/esm/interface/move-chart.d.ts +5 -0
  197. package/dist/esm/interface/move-chart.d.ts.map +1 -0
  198. package/dist/esm/interface/move-chart.js +22 -0
  199. package/dist/esm/interface/resize-chart.d.ts +5 -0
  200. package/dist/esm/interface/resize-chart.d.ts.map +1 -0
  201. package/dist/esm/interface/resize-chart.js +24 -0
  202. package/dist/esm/interface/update-chart.d.ts +4 -0
  203. package/dist/esm/interface/update-chart.d.ts.map +1 -0
  204. package/dist/esm/interface/update-chart.js +26 -0
  205. package/dist/esm/plotly/basic-chart/basic-chart.d.ts +5 -0
  206. package/dist/esm/plotly/basic-chart/basic-chart.d.ts.map +1 -0
  207. package/dist/esm/plotly/basic-chart/basic-chart.js +1682 -0
  208. package/dist/esm/plotly/basic-chart/helpers.d.ts +20 -0
  209. package/dist/esm/plotly/basic-chart/helpers.d.ts.map +1 -0
  210. package/dist/esm/plotly/basic-chart/helpers.js +107 -0
  211. package/dist/esm/plotly/basic-chart/index.d.ts +5 -0
  212. package/dist/esm/plotly/basic-chart/index.d.ts.map +1 -0
  213. package/dist/esm/plotly/basic-chart/index.js +7 -0
  214. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
  215. package/dist/esm/types.d.ts +27 -0
  216. package/dist/esm/types.d.ts.map +1 -0
  217. package/dist/esm/types.js +1 -0
  218. package/dist/esm/utils/aggregation.d.ts +6 -0
  219. package/dist/esm/utils/aggregation.d.ts.map +1 -0
  220. package/dist/esm/utils/aggregation.js +96 -0
  221. package/dist/esm/utils/bar-layout.d.ts +57 -0
  222. package/dist/esm/utils/bar-layout.d.ts.map +1 -0
  223. package/dist/esm/utils/bar-layout.js +70 -0
  224. package/dist/esm/utils/boxplot.d.ts +62 -0
  225. package/dist/esm/utils/boxplot.d.ts.map +1 -0
  226. package/dist/esm/utils/boxplot.js +91 -0
  227. package/dist/esm/utils/bubble.d.ts +52 -0
  228. package/dist/esm/utils/bubble.d.ts.map +1 -0
  229. package/dist/esm/utils/bubble.js +122 -0
  230. package/dist/esm/utils/chart-spec-utils.d.ts +67 -0
  231. package/dist/esm/utils/chart-spec-utils.d.ts.map +1 -0
  232. package/dist/esm/utils/chart-spec-utils.js +270 -0
  233. package/dist/esm/utils/echarts-gl-loader.d.ts +11 -0
  234. package/dist/esm/utils/echarts-gl-loader.d.ts.map +1 -0
  235. package/dist/esm/utils/echarts-gl-loader.js +51 -0
  236. package/dist/esm/utils/error-bars.d.ts +58 -0
  237. package/dist/esm/utils/error-bars.d.ts.map +1 -0
  238. package/dist/esm/utils/error-bars.js +134 -0
  239. package/dist/esm/utils/gradient.d.ts +52 -0
  240. package/dist/esm/utils/gradient.d.ts.map +1 -0
  241. package/dist/esm/utils/gradient.js +84 -0
  242. package/dist/esm/utils/hierarchy.d.ts +28 -0
  243. package/dist/esm/utils/hierarchy.d.ts.map +1 -0
  244. package/dist/esm/utils/hierarchy.js +150 -0
  245. package/dist/esm/utils/histogram.d.ts +46 -0
  246. package/dist/esm/utils/histogram.d.ts.map +1 -0
  247. package/dist/esm/utils/histogram.js +95 -0
  248. package/dist/esm/utils/of-pie.d.ts +101 -0
  249. package/dist/esm/utils/of-pie.d.ts.map +1 -0
  250. package/dist/esm/utils/of-pie.js +141 -0
  251. package/dist/esm/utils/overrides.d.ts +12 -0
  252. package/dist/esm/utils/overrides.d.ts.map +1 -0
  253. package/dist/esm/utils/overrides.js +159 -0
  254. package/dist/esm/utils/radar.d.ts +37 -0
  255. package/dist/esm/utils/radar.d.ts.map +1 -0
  256. package/dist/esm/utils/radar.js +49 -0
  257. package/dist/esm/utils/region-map-loader.d.ts +22 -0
  258. package/dist/esm/utils/region-map-loader.d.ts.map +1 -0
  259. package/dist/esm/utils/region-map-loader.js +141 -0
  260. package/dist/esm/utils/region-map.d.ts +100 -0
  261. package/dist/esm/utils/region-map.d.ts.map +1 -0
  262. package/dist/esm/utils/region-map.js +266 -0
  263. package/dist/esm/utils/stock.d.ts +64 -0
  264. package/dist/esm/utils/stock.d.ts.map +1 -0
  265. package/dist/esm/utils/stock.js +123 -0
  266. package/dist/esm/utils/three-d.d.ts +96 -0
  267. package/dist/esm/utils/three-d.d.ts.map +1 -0
  268. package/dist/esm/utils/three-d.js +162 -0
  269. package/dist/esm/utils/trendline.d.ts +41 -0
  270. package/dist/esm/utils/trendline.d.ts.map +1 -0
  271. package/dist/esm/utils/trendline.js +257 -0
  272. package/dist/esm/utils/waterfall.d.ts +24 -0
  273. package/dist/esm/utils/waterfall.d.ts.map +1 -0
  274. package/dist/esm/utils/waterfall.js +55 -0
  275. package/dist/esm/utils/webgl.d.ts +12 -0
  276. package/dist/esm/utils/webgl.d.ts.map +1 -0
  277. package/dist/esm/utils/webgl.js +30 -0
  278. package/dist/esm/vitest.config.d.ts +3 -0
  279. package/dist/esm/vitest.config.d.ts.map +1 -0
  280. package/dist/esm/vitest.config.js +7 -0
  281. package/package.json +96 -0
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Stock-chart shaping shared by chart renderers.
3
+ *
4
+ * Excel stock charts come in four variants distinguished by the ordered
5
+ * set of value series they carry:
6
+ * - HLC : High, Low, Close
7
+ * - OHLC : Open, High, Low, Close
8
+ * - VHLC : Volume, High, Low, Close
9
+ * - VOHLC : Volume, Open, High, Low, Close
10
+ *
11
+ * We render stock as a "line-derived" chart: each price role becomes its
12
+ * own line series (volume is kept out of the price plot — it lives on a
13
+ * different scale and would flatten the price lines — and is instead
14
+ * surfaced via `toVolumeSeries` for renderers to draw as a column
15
+ * sub-pane on its own value axis). The candlestick transform is also
16
+ * provided for renderers that prefer a true OHLC body (echarts
17
+ * CandlestickChart / plotly candlestick).
18
+ */
19
+ /**
20
+ * The ordered roles for each variant. Series are matched to roles by
21
+ * position — series[0] takes role[0], etc.
22
+ */
23
+ export const STOCK_ROLES = {
24
+ HLC: ["high", "low", "close"],
25
+ OHLC: ["open", "high", "low", "close"],
26
+ VHLC: ["volume", "high", "low", "close"],
27
+ VOHLC: ["volume", "open", "high", "low", "close"],
28
+ };
29
+ const isFiniteNum = (v) => typeof v === "number" && Number.isFinite(v);
30
+ const toNumber = (v) => isFiniteNum(v) ? v : null;
31
+ /**
32
+ * Map positional series to their stock roles for a given variant.
33
+ * Series beyond the variant's role count are ignored.
34
+ */
35
+ export const assignStockRoles = (seriesValues, variant = "HLC") => {
36
+ const roles = STOCK_ROLES[variant] ?? STOCK_ROLES.HLC;
37
+ const result = {};
38
+ for (let i = 0; i < roles.length; i++) {
39
+ const role = roles[i];
40
+ const values = seriesValues[i];
41
+ if (values)
42
+ result[role] = values.map(toNumber);
43
+ }
44
+ return result;
45
+ };
46
+ /**
47
+ * Line-derived view: emit one line series per PRICE role (open/high/low/
48
+ * close), preserving variant order and dropping volume. Each returned
49
+ * series carries a role-derived display name and its value vector.
50
+ */
51
+ export const toLineSeries = (seriesValues, variant = "HLC") => {
52
+ const byRole = assignStockRoles(seriesValues, variant);
53
+ const order = ["open", "high", "low", "close"];
54
+ const labels = {
55
+ volume: "Volume",
56
+ open: "Open",
57
+ high: "High",
58
+ low: "Low",
59
+ close: "Close",
60
+ };
61
+ const out = [];
62
+ for (const role of order) {
63
+ const values = byRole[role];
64
+ if (values)
65
+ out.push({ role, name: labels[role], values });
66
+ }
67
+ return out;
68
+ };
69
+ /**
70
+ * Volume view: extract the volume series for the V-variants and flag
71
+ * each point as an up/down day so renderers can color the columns to
72
+ * match the price plot. Up/down follows convention: close vs open where
73
+ * the point carries an open, otherwise close vs the previous close. A
74
+ * point with nothing to compare against (e.g. the first) counts as up.
75
+ * Returns null for variants without a volume role (HLC / OHLC) or when
76
+ * the volume series is absent.
77
+ */
78
+ export const toVolumeSeries = (seriesValues, variant = "HLC") => {
79
+ const byRole = assignStockRoles(seriesValues, variant);
80
+ const volume = byRole.volume;
81
+ if (!volume)
82
+ return null;
83
+ const open = byRole.open ?? [];
84
+ const close = byRole.close ?? [];
85
+ let prevClose = null;
86
+ const out = [];
87
+ for (let i = 0; i < volume.length; i++) {
88
+ const c = close[i] ?? null;
89
+ const o = open[i] ?? null;
90
+ let up = true;
91
+ if (o !== null && c !== null)
92
+ up = c >= o;
93
+ else if (prevClose !== null && c !== null)
94
+ up = c >= prevClose;
95
+ out.push({ value: volume[i] ?? null, up });
96
+ if (c !== null)
97
+ prevClose = c;
98
+ }
99
+ return out;
100
+ };
101
+ /**
102
+ * Candlestick view: produce [open, close, low, high] tuples (echarts'
103
+ * candlestick data order) per category index. When the variant has no
104
+ * `open` (HLC / VHLC) the close value is used as the open so the body
105
+ * collapses to a tick — matching how Excel renders HLC stock bars.
106
+ */
107
+ export const toCandlestick = (seriesValues, variant = "HLC") => {
108
+ const byRole = assignStockRoles(seriesValues, variant);
109
+ const high = byRole.high ?? [];
110
+ const low = byRole.low ?? [];
111
+ const close = byRole.close ?? [];
112
+ const open = byRole.open ?? [];
113
+ const len = Math.max(high.length, low.length, close.length, open.length);
114
+ const result = [];
115
+ for (let i = 0; i < len; i++) {
116
+ const c = close[i] ?? 0;
117
+ const o = open[i] ?? c;
118
+ const l = low[i] ?? 0;
119
+ const h = high[i] ?? 0;
120
+ result.push([o, c, l, h]);
121
+ }
122
+ return result;
123
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * 3-D / surface chart helpers.
3
+ *
4
+ * Excel's 3-D chart classes (Bar3D / Line3D / Pie3D / Area3D / Surface /
5
+ * Surface3D) degrade to a 2-D fallback `chartType` when imported, with the
6
+ * authored type preserved verbatim on the spec as `sourceChartType`. These
7
+ * helpers gate the renderer's 3-D pipeline off that token and shape the
8
+ * category/series value matrix into the data layouts the echarts-gl
9
+ * `bar3D` / `line3D` / `surface` series expect.
10
+ *
11
+ * Everything in this module is pure and independent of echarts-gl so it can
12
+ * be unit-tested without loading (or even installing a working) WebGL stack.
13
+ *
14
+ * Mapping decisions (see also basic-chart.tsx):
15
+ * - BAR_3D -> `bar3D` (x = category, y = series index, z = value)
16
+ * - LINE_3D -> `line3D` (one polyline per series at its series-index depth)
17
+ * - AREA_3D -> `line3D` (echarts-gl has no area3D; the polyline keeps the
18
+ * authored depth layout)
19
+ * - SURFACE / SURFACE_3D -> `surface` (values grid as z-heights over the
20
+ * categories × series plane)
21
+ * - PIE_3D -> null (echarts-gl has no pie3D; the renderer keeps the
22
+ * existing 2-D pie fallback rather than faking it)
23
+ */
24
+ declare const THREE_D_TOKENS: readonly ["BAR_3D", "LINE_3D", "PIE_3D", "AREA_3D", "SURFACE", "SURFACE_3D"];
25
+ export type ThreeDSourceType = (typeof THREE_D_TOKENS)[number];
26
+ /** The echarts-gl series type a 3-D spec renders as. */
27
+ export type ThreeDRenderKind = "bar3D" | "line3D" | "surface";
28
+ /**
29
+ * Normalize a raw `sourceChartType` value to one of the known 3-D tokens.
30
+ * Matching is case-insensitive and whitespace-tolerant; anything else
31
+ * (including null/undefined) returns null.
32
+ */
33
+ export declare const normalize3DSourceType: (sourceChartType?: string | null) => ThreeDSourceType | null;
34
+ /**
35
+ * True when the spec was authored as one of the six 3-D / surface chart
36
+ * classes (its `sourceChartType` is one of the degraded-payload tokens).
37
+ * Note PIE_3D is included here even though it renders as the 2-D pie —
38
+ * use `resolve3DRenderKind` to decide whether the 3-D pipeline runs.
39
+ */
40
+ export declare const is3DSpec: (spec: {
41
+ sourceChartType?: string | null;
42
+ } | null | undefined) => boolean;
43
+ /**
44
+ * Map a `sourceChartType` token to the echarts-gl series kind it renders
45
+ * as, or null when the 3-D pipeline should not run:
46
+ * - PIE_3D — echarts-gl has no pie3D, so the 2-D pie fallback stays.
47
+ * - AREA_3D — echarts-gl has no area3D; rendered as line3D.
48
+ * - non-3-D / unknown tokens.
49
+ */
50
+ export declare const resolve3DRenderKind: (sourceChartType?: string | null) => ThreeDRenderKind | null;
51
+ export type ThreeDSeriesInput = {
52
+ /** Resolved series display name (legend / y-axis label). */
53
+ name: string;
54
+ /** Sanitized numeric values, one per category (gaps allowed). */
55
+ values: (number | null | undefined)[];
56
+ };
57
+ /** [x = category index, y = series index, z = value] */
58
+ export type ThreeDPoint = [number, number, number];
59
+ /** Number of categories = the longest series' value count. */
60
+ export declare const getCategoryCount: (series: ThreeDSeriesInput[]) => number;
61
+ /**
62
+ * Shape series values into a single bar3D data array: one point per
63
+ * (category, series) cell — x is the category index, y the series index
64
+ * (depth row), z the value. Non-finite values are skipped (no bar).
65
+ */
66
+ export declare const buildBar3DData: (series: ThreeDSeriesInput[]) => ThreeDPoint[];
67
+ /**
68
+ * Shape series values into one line3D polyline per series, laid out at the
69
+ * series' index on the depth (y) axis. Non-finite values are skipped, so
70
+ * the polyline simply bridges gaps.
71
+ */
72
+ export declare const buildLine3DSeries: (series: ThreeDSeriesInput[]) => {
73
+ name: string;
74
+ data: ThreeDPoint[];
75
+ }[];
76
+ /**
77
+ * Shape series values into a complete surface grid over the categories (x)
78
+ * × series (y) plane with the values as z-heights. Surfaces require a
79
+ * complete grid, so ragged series are padded and non-finite values are
80
+ * flattened to 0. Points are emitted row-major (per series row, categories
81
+ * in order), the layout echarts-gl's surface parser expects.
82
+ */
83
+ export declare const buildSurfaceData: (series: ThreeDSeriesInput[]) => ThreeDPoint[];
84
+ /**
85
+ * [min, max] of the z values, for the surface visualMap range. Returns
86
+ * [0, 1] for an empty grid and pads a flat grid (min === max) by 1 so the
87
+ * visualMap never has a zero-width range.
88
+ */
89
+ export declare const getValueExtent: (points: ThreeDPoint[]) => [number, number];
90
+ /**
91
+ * Default z-height color ramp for surface plots (the classic echarts-gl
92
+ * blue → yellow → red diverging ramp). Used via visualMap.inRange.
93
+ */
94
+ export declare const SURFACE_COLOR_RAMP: readonly string[];
95
+ export {};
96
+ //# sourceMappingURL=three-d.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"three-d.d.ts","sourceRoot":"","sources":["../../../utils/three-d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,QAAA,MAAM,cAAc,8EAOV,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,wDAAwD;AACxD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAI9D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,kBAAkB,MAAM,GAAG,IAAI,KAC9B,gBAAgB,GAAG,IAIrB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GACnB,MAAM;IAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,KAC3D,OAAgE,CAAC;AAEpE;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC9B,kBAAkB,MAAM,GAAG,IAAI,KAC9B,gBAAgB,GAAG,IAcrB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;CACvC,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAKnD,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,GAAI,QAAQ,iBAAiB,EAAE,KAAG,MACO,CAAC;AAEvE;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,iBAAiB,EAAE,KAAG,WAAW,EAUvE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,iBAAiB,EAAE,KAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,EAAE,CAAA;CAAE,EASpC,CAAC;AAEL;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,iBAAiB,EAAE,KAC1B,WAAW,EAcb,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,WAAW,EAAE,KAAG,CAAC,MAAM,EAAE,MAAM,CASrE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAY/C,CAAC"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * 3-D / surface chart helpers.
3
+ *
4
+ * Excel's 3-D chart classes (Bar3D / Line3D / Pie3D / Area3D / Surface /
5
+ * Surface3D) degrade to a 2-D fallback `chartType` when imported, with the
6
+ * authored type preserved verbatim on the spec as `sourceChartType`. These
7
+ * helpers gate the renderer's 3-D pipeline off that token and shape the
8
+ * category/series value matrix into the data layouts the echarts-gl
9
+ * `bar3D` / `line3D` / `surface` series expect.
10
+ *
11
+ * Everything in this module is pure and independent of echarts-gl so it can
12
+ * be unit-tested without loading (or even installing a working) WebGL stack.
13
+ *
14
+ * Mapping decisions (see also basic-chart.tsx):
15
+ * - BAR_3D -> `bar3D` (x = category, y = series index, z = value)
16
+ * - LINE_3D -> `line3D` (one polyline per series at its series-index depth)
17
+ * - AREA_3D -> `line3D` (echarts-gl has no area3D; the polyline keeps the
18
+ * authored depth layout)
19
+ * - SURFACE / SURFACE_3D -> `surface` (values grid as z-heights over the
20
+ * categories × series plane)
21
+ * - PIE_3D -> null (echarts-gl has no pie3D; the renderer keeps the
22
+ * existing 2-D pie fallback rather than faking it)
23
+ */
24
+ const THREE_D_TOKENS = [
25
+ "BAR_3D",
26
+ "LINE_3D",
27
+ "PIE_3D",
28
+ "AREA_3D",
29
+ "SURFACE",
30
+ "SURFACE_3D",
31
+ ];
32
+ const THREE_D_TOKEN_SET = new Set(THREE_D_TOKENS);
33
+ /**
34
+ * Normalize a raw `sourceChartType` value to one of the known 3-D tokens.
35
+ * Matching is case-insensitive and whitespace-tolerant; anything else
36
+ * (including null/undefined) returns null.
37
+ */
38
+ export const normalize3DSourceType = (sourceChartType) => {
39
+ if (typeof sourceChartType !== "string")
40
+ return null;
41
+ const token = sourceChartType.trim().toUpperCase();
42
+ return THREE_D_TOKEN_SET.has(token) ? token : null;
43
+ };
44
+ /**
45
+ * True when the spec was authored as one of the six 3-D / surface chart
46
+ * classes (its `sourceChartType` is one of the degraded-payload tokens).
47
+ * Note PIE_3D is included here even though it renders as the 2-D pie —
48
+ * use `resolve3DRenderKind` to decide whether the 3-D pipeline runs.
49
+ */
50
+ export const is3DSpec = (spec) => normalize3DSourceType(spec?.sourceChartType) !== null;
51
+ /**
52
+ * Map a `sourceChartType` token to the echarts-gl series kind it renders
53
+ * as, or null when the 3-D pipeline should not run:
54
+ * - PIE_3D — echarts-gl has no pie3D, so the 2-D pie fallback stays.
55
+ * - AREA_3D — echarts-gl has no area3D; rendered as line3D.
56
+ * - non-3-D / unknown tokens.
57
+ */
58
+ export const resolve3DRenderKind = (sourceChartType) => {
59
+ switch (normalize3DSourceType(sourceChartType)) {
60
+ case "BAR_3D":
61
+ return "bar3D";
62
+ case "LINE_3D":
63
+ case "AREA_3D":
64
+ return "line3D";
65
+ case "SURFACE":
66
+ case "SURFACE_3D":
67
+ return "surface";
68
+ default:
69
+ // PIE_3D and non-3-D tokens: keep the 2-D fallback rendering.
70
+ return null;
71
+ }
72
+ };
73
+ const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
74
+ /** Number of categories = the longest series' value count. */
75
+ export const getCategoryCount = (series) => series.reduce((max, entry) => Math.max(max, entry.values.length), 0);
76
+ /**
77
+ * Shape series values into a single bar3D data array: one point per
78
+ * (category, series) cell — x is the category index, y the series index
79
+ * (depth row), z the value. Non-finite values are skipped (no bar).
80
+ */
81
+ export const buildBar3DData = (series) => {
82
+ const points = [];
83
+ series.forEach((entry, seriesIndex) => {
84
+ entry.values.forEach((value, categoryIndex) => {
85
+ if (isFiniteNumber(value)) {
86
+ points.push([categoryIndex, seriesIndex, value]);
87
+ }
88
+ });
89
+ });
90
+ return points;
91
+ };
92
+ /**
93
+ * Shape series values into one line3D polyline per series, laid out at the
94
+ * series' index on the depth (y) axis. Non-finite values are skipped, so
95
+ * the polyline simply bridges gaps.
96
+ */
97
+ export const buildLine3DSeries = (series) => series.map((entry, seriesIndex) => {
98
+ const data = [];
99
+ entry.values.forEach((value, categoryIndex) => {
100
+ if (isFiniteNumber(value)) {
101
+ data.push([categoryIndex, seriesIndex, value]);
102
+ }
103
+ });
104
+ return { name: entry.name, data };
105
+ });
106
+ /**
107
+ * Shape series values into a complete surface grid over the categories (x)
108
+ * × series (y) plane with the values as z-heights. Surfaces require a
109
+ * complete grid, so ragged series are padded and non-finite values are
110
+ * flattened to 0. Points are emitted row-major (per series row, categories
111
+ * in order), the layout echarts-gl's surface parser expects.
112
+ */
113
+ export const buildSurfaceData = (series) => {
114
+ const categoryCount = getCategoryCount(series);
115
+ const points = [];
116
+ series.forEach((entry, seriesIndex) => {
117
+ for (let categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {
118
+ const value = entry.values[categoryIndex];
119
+ points.push([
120
+ categoryIndex,
121
+ seriesIndex,
122
+ isFiniteNumber(value) ? value : 0,
123
+ ]);
124
+ }
125
+ });
126
+ return points;
127
+ };
128
+ /**
129
+ * [min, max] of the z values, for the surface visualMap range. Returns
130
+ * [0, 1] for an empty grid and pads a flat grid (min === max) by 1 so the
131
+ * visualMap never has a zero-width range.
132
+ */
133
+ export const getValueExtent = (points) => {
134
+ if (points.length === 0)
135
+ return [0, 1];
136
+ let min = points[0][2];
137
+ let max = points[0][2];
138
+ for (const [, , z] of points) {
139
+ if (z < min)
140
+ min = z;
141
+ if (z > max)
142
+ max = z;
143
+ }
144
+ return [min, max === min ? min + 1 : max];
145
+ };
146
+ /**
147
+ * Default z-height color ramp for surface plots (the classic echarts-gl
148
+ * blue → yellow → red diverging ramp). Used via visualMap.inRange.
149
+ */
150
+ export const SURFACE_COLOR_RAMP = [
151
+ "#313695",
152
+ "#4575b4",
153
+ "#74add1",
154
+ "#abd9e9",
155
+ "#e0f3f8",
156
+ "#ffffbf",
157
+ "#fee090",
158
+ "#fdae61",
159
+ "#f46d43",
160
+ "#d73027",
161
+ "#a50026",
162
+ ];
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Trendline regression shared by chart renderers.
3
+ *
4
+ * Excel/OOXML `<c:trendline>` supports linear / polynomial / exponential /
5
+ * logarithmic / power / moving-average fits. Neither echarts nor plotly
6
+ * compute these for us (echarts-stat is intentionally NOT a dependency),
7
+ * so the regressions are hand-rolled here as pure least-squares math and
8
+ * the renderers draw the returned points as an overlay line series.
9
+ *
10
+ * All fits operate on the index domain x = 0, 1, 2, … (one step per
11
+ * category) which matches how a category-axis chart positions points.
12
+ * The caller passes the raw y-values; gaps (null/NaN) are skipped during
13
+ * fitting but the fitted curve is still emitted for every x in range.
14
+ */
15
+ export type TrendlineType = "linear" | "exp" | "log" | "poly" | "power" | "movingAvg";
16
+ export type TrendlinePoint = {
17
+ x: number;
18
+ y: number;
19
+ };
20
+ export type TrendlineResult = {
21
+ /** Fitted points across the (optionally forecast-extended) domain. */
22
+ points: TrendlinePoint[];
23
+ /** Coefficient of determination (R²) of the fit, 0..1. null if N/A. */
24
+ rSquared: number | null;
25
+ /** Human-readable equation string, e.g. "y = 1.5x + 2". */
26
+ equation: string | null;
27
+ };
28
+ /**
29
+ * Compute a trendline for a series of y-values.
30
+ *
31
+ * @param values Raw y-values; the category index is used as x.
32
+ * @param spec Trendline parameters (type/order/period/forecast).
33
+ */
34
+ export declare const computeTrendline: (values: (number | null | undefined)[], spec?: {
35
+ type?: TrendlineType | null;
36
+ order?: number | null;
37
+ period?: number | null;
38
+ forecastForward?: number | null;
39
+ forecastBackward?: number | null;
40
+ }) => TrendlineResult | null;
41
+ //# sourceMappingURL=trendline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trendline.d.ts","sourceRoot":"","sources":["../../../utils/trendline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,KAAK,GACL,KAAK,GACL,MAAM,GACN,OAAO,GACP,WAAW,CAAC;AAEhB,MAAM,MAAM,cAAc,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG;IAC5B,sEAAsE;IACtE,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AA+IF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EACrC,OAAM;IACJ,IAAI,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,KACL,eAAe,GAAG,IA6FpB,CAAC"}
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Trendline regression shared by chart renderers.
3
+ *
4
+ * Excel/OOXML `<c:trendline>` supports linear / polynomial / exponential /
5
+ * logarithmic / power / moving-average fits. Neither echarts nor plotly
6
+ * compute these for us (echarts-stat is intentionally NOT a dependency),
7
+ * so the regressions are hand-rolled here as pure least-squares math and
8
+ * the renderers draw the returned points as an overlay line series.
9
+ *
10
+ * All fits operate on the index domain x = 0, 1, 2, … (one step per
11
+ * category) which matches how a category-axis chart positions points.
12
+ * The caller passes the raw y-values; gaps (null/NaN) are skipped during
13
+ * fitting but the fitted curve is still emitted for every x in range.
14
+ */
15
+ const isFiniteNum = (v) => typeof v === "number" && Number.isFinite(v);
16
+ /**
17
+ * Build (x, y) pairs from raw y-values using the category index as x.
18
+ * Non-finite y-values are dropped (they cannot be fitted).
19
+ */
20
+ const toPairs = (values) => {
21
+ const pairs = [];
22
+ for (let i = 0; i < values.length; i++) {
23
+ const y = values[i];
24
+ if (isFiniteNum(y))
25
+ pairs.push({ x: i, y });
26
+ }
27
+ return pairs;
28
+ };
29
+ /** Compute R² between observed y and predicted f(x) over the fit pairs. */
30
+ const rSquaredOf = (pairs, predict) => {
31
+ if (pairs.length < 2)
32
+ return null;
33
+ const meanY = pairs.reduce((s, p) => s + p.y, 0) / pairs.length;
34
+ let ssRes = 0;
35
+ let ssTot = 0;
36
+ for (const p of pairs) {
37
+ const yHat = predict(p.x);
38
+ if (!Number.isFinite(yHat))
39
+ return null;
40
+ ssRes += (p.y - yHat) ** 2;
41
+ ssTot += (p.y - meanY) ** 2;
42
+ }
43
+ if (ssTot === 0)
44
+ return null;
45
+ return 1 - ssRes / ssTot;
46
+ };
47
+ const round = (n, digits = 4) => {
48
+ const f = 10 ** digits;
49
+ return Math.round(n * f) / f;
50
+ };
51
+ /** Solve a small linear system A·x = b via Gaussian elimination. */
52
+ const solveLinearSystem = (A, b) => {
53
+ const n = b.length;
54
+ // Augmented matrix
55
+ const m = A.map((row, i) => [...row, b[i]]);
56
+ for (let col = 0; col < n; col++) {
57
+ // Partial pivot
58
+ let pivot = col;
59
+ for (let r = col + 1; r < n; r++) {
60
+ if (Math.abs(m[r][col]) > Math.abs(m[pivot][col]))
61
+ pivot = r;
62
+ }
63
+ if (Math.abs(m[pivot][col]) < 1e-12)
64
+ return null;
65
+ [m[col], m[pivot]] = [m[pivot], m[col]];
66
+ // Eliminate
67
+ for (let r = 0; r < n; r++) {
68
+ if (r === col)
69
+ continue;
70
+ const factor = m[r][col] / m[col][col];
71
+ for (let c = col; c <= n; c++) {
72
+ m[r][c] -= factor * m[col][c];
73
+ }
74
+ }
75
+ }
76
+ return m.map((row, i) => row[n] / m[i][i]);
77
+ };
78
+ /** Linear least squares: y = m·x + b. */
79
+ const fitLinear = (pairs) => {
80
+ const n = pairs.length;
81
+ if (n < 2)
82
+ return null;
83
+ let sx = 0;
84
+ let sy = 0;
85
+ let sxx = 0;
86
+ let sxy = 0;
87
+ for (const p of pairs) {
88
+ sx += p.x;
89
+ sy += p.y;
90
+ sxx += p.x * p.x;
91
+ sxy += p.x * p.y;
92
+ }
93
+ const denom = n * sxx - sx * sx;
94
+ if (denom === 0)
95
+ return null;
96
+ const m = (n * sxy - sx * sy) / denom;
97
+ const b = (sy - m * sx) / n;
98
+ return { m, b };
99
+ };
100
+ /**
101
+ * Polynomial least squares of the given order: y = c0 + c1·x + … + ck·x^k.
102
+ * Returns coefficients [c0, c1, …, ck] or null if singular.
103
+ */
104
+ const fitPoly = (pairs, order) => {
105
+ const k = Math.max(2, Math.min(6, Math.floor(order)));
106
+ const n = k + 1;
107
+ if (pairs.length < n)
108
+ return null;
109
+ // Build normal equations: (X^T X) c = X^T y where X has columns x^0..x^k
110
+ const A = Array.from({ length: n }, () => new Array(n).fill(0));
111
+ const b = new Array(n).fill(0);
112
+ for (const p of pairs) {
113
+ const powers = [];
114
+ for (let i = 0; i < 2 * k + 1; i++) {
115
+ powers.push(i === 0 ? 1 : powers[i - 1] * p.x);
116
+ }
117
+ for (let row = 0; row < n; row++) {
118
+ for (let col = 0; col < n; col++) {
119
+ A[row][col] += powers[row + col];
120
+ }
121
+ b[row] += powers[row] * p.y;
122
+ }
123
+ }
124
+ return solveLinearSystem(A, b);
125
+ };
126
+ /** Exponential least squares: y = a·e^(b·x). Requires y > 0. */
127
+ const fitExp = (pairs) => {
128
+ const positive = pairs.filter((p) => p.y > 0);
129
+ if (positive.length < 2)
130
+ return null;
131
+ // Linearize: ln(y) = ln(a) + b·x
132
+ const lin = fitLinear(positive.map((p) => ({ x: p.x, y: Math.log(p.y) })));
133
+ if (!lin)
134
+ return null;
135
+ return { a: Math.exp(lin.b), b: lin.m };
136
+ };
137
+ /** Logarithmic least squares: y = a + b·ln(x). Requires x > 0. */
138
+ const fitLog = (pairs) => {
139
+ const positive = pairs.filter((p) => p.x > 0);
140
+ if (positive.length < 2)
141
+ return null;
142
+ const lin = fitLinear(positive.map((p) => ({ x: Math.log(p.x), y: p.y })));
143
+ if (!lin)
144
+ return null;
145
+ return { a: lin.b, b: lin.m };
146
+ };
147
+ /** Power least squares: y = a·x^b. Requires x > 0 and y > 0. */
148
+ const fitPower = (pairs) => {
149
+ const positive = pairs.filter((p) => p.x > 0 && p.y > 0);
150
+ if (positive.length < 2)
151
+ return null;
152
+ const lin = fitLinear(positive.map((p) => ({ x: Math.log(p.x), y: Math.log(p.y) })));
153
+ if (!lin)
154
+ return null;
155
+ return { a: Math.exp(lin.b), b: lin.m };
156
+ };
157
+ /**
158
+ * Compute a trendline for a series of y-values.
159
+ *
160
+ * @param values Raw y-values; the category index is used as x.
161
+ * @param spec Trendline parameters (type/order/period/forecast).
162
+ */
163
+ export const computeTrendline = (values, spec = {}) => {
164
+ const pairs = toPairs(values);
165
+ if (pairs.length < 2)
166
+ return null;
167
+ const type = spec.type ?? "linear";
168
+ const forward = Math.max(0, Math.floor(spec.forecastForward ?? 0));
169
+ const backward = Math.max(0, Math.floor(spec.forecastBackward ?? 0));
170
+ // Moving average is a smoothing of the observed values, not a fitted
171
+ // function, so it is handled separately and ignores forecasting.
172
+ if (type === "movingAvg") {
173
+ const period = Math.max(2, Math.floor(spec.period ?? 2));
174
+ const points = [];
175
+ for (let i = period - 1; i < values.length; i++) {
176
+ let sum = 0;
177
+ let count = 0;
178
+ for (let j = i - period + 1; j <= i; j++) {
179
+ const v = values[j];
180
+ if (isFiniteNum(v)) {
181
+ sum += v;
182
+ count++;
183
+ }
184
+ }
185
+ if (count > 0)
186
+ points.push({ x: i, y: sum / count });
187
+ }
188
+ return { points, rSquared: null, equation: null };
189
+ }
190
+ const lastX = values.length - 1;
191
+ const xStart = -backward;
192
+ const xEnd = lastX + forward;
193
+ const xs = [];
194
+ for (let x = xStart; x <= xEnd; x++)
195
+ xs.push(x);
196
+ let predict = null;
197
+ let equation = null;
198
+ switch (type) {
199
+ case "linear": {
200
+ const fit = fitLinear(pairs);
201
+ if (!fit)
202
+ return null;
203
+ predict = (x) => fit.m * x + fit.b;
204
+ equation = `y = ${round(fit.m)}x + ${round(fit.b)}`;
205
+ break;
206
+ }
207
+ case "poly": {
208
+ const coeffs = fitPoly(pairs, spec.order ?? 2);
209
+ if (!coeffs)
210
+ return null;
211
+ predict = (x) => coeffs.reduce((acc, c, i) => acc + c * x ** i, 0);
212
+ equation =
213
+ "y = " +
214
+ coeffs
215
+ .map((c, i) => (i === 0 ? `${round(c)}` : `${round(c)}x^${i}`))
216
+ .join(" + ");
217
+ break;
218
+ }
219
+ case "exp": {
220
+ const fit = fitExp(pairs);
221
+ if (!fit)
222
+ return null;
223
+ predict = (x) => fit.a * Math.exp(fit.b * x);
224
+ equation = `y = ${round(fit.a)}e^(${round(fit.b)}x)`;
225
+ break;
226
+ }
227
+ case "log": {
228
+ const fit = fitLog(pairs);
229
+ if (!fit)
230
+ return null;
231
+ // ln(0) and ln(negative) are undefined; clamp x to >0 for the curve.
232
+ predict = (x) => fit.a + fit.b * Math.log(x <= 0 ? 1e-9 : x);
233
+ equation = `y = ${round(fit.a)} + ${round(fit.b)}ln(x)`;
234
+ break;
235
+ }
236
+ case "power": {
237
+ const fit = fitPower(pairs);
238
+ if (!fit)
239
+ return null;
240
+ predict = (x) => fit.a * (x <= 0 ? 0 : x ** fit.b);
241
+ equation = `y = ${round(fit.a)}x^${round(fit.b)}`;
242
+ break;
243
+ }
244
+ default:
245
+ return null;
246
+ }
247
+ const points = xs
248
+ .map((x) => ({ x, y: predict(x) }))
249
+ .filter((p) => Number.isFinite(p.y));
250
+ if (points.length < 2)
251
+ return null;
252
+ return {
253
+ points,
254
+ rSquared: rSquaredOf(pairs, predict),
255
+ equation,
256
+ };
257
+ };