deepbox 0.1.0

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 (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +344 -0
  3. package/dist/CSRMatrix-CwGwQRea.d.cts +219 -0
  4. package/dist/CSRMatrix-KzNt6QpS.d.ts +219 -0
  5. package/dist/Tensor-BQLk1ltW.d.cts +147 -0
  6. package/dist/Tensor-g8mUClel.d.ts +147 -0
  7. package/dist/chunk-4S73VUBD.js +677 -0
  8. package/dist/chunk-4S73VUBD.js.map +1 -0
  9. package/dist/chunk-5R4S63PF.js +2925 -0
  10. package/dist/chunk-5R4S63PF.js.map +1 -0
  11. package/dist/chunk-6AE5FKKQ.cjs +9264 -0
  12. package/dist/chunk-6AE5FKKQ.cjs.map +1 -0
  13. package/dist/chunk-AD436M45.js +3854 -0
  14. package/dist/chunk-AD436M45.js.map +1 -0
  15. package/dist/chunk-ALS7ETWZ.cjs +4263 -0
  16. package/dist/chunk-ALS7ETWZ.cjs.map +1 -0
  17. package/dist/chunk-AU7XHGKJ.js +2092 -0
  18. package/dist/chunk-AU7XHGKJ.js.map +1 -0
  19. package/dist/chunk-B5TNKUEY.js +1481 -0
  20. package/dist/chunk-B5TNKUEY.js.map +1 -0
  21. package/dist/chunk-BCR7G3A6.js +9136 -0
  22. package/dist/chunk-BCR7G3A6.js.map +1 -0
  23. package/dist/chunk-C4PKXY74.cjs +1917 -0
  24. package/dist/chunk-C4PKXY74.cjs.map +1 -0
  25. package/dist/chunk-DWZY6PIP.cjs +6400 -0
  26. package/dist/chunk-DWZY6PIP.cjs.map +1 -0
  27. package/dist/chunk-E3EU5FZO.cjs +2113 -0
  28. package/dist/chunk-E3EU5FZO.cjs.map +1 -0
  29. package/dist/chunk-F3JWBINJ.js +1054 -0
  30. package/dist/chunk-F3JWBINJ.js.map +1 -0
  31. package/dist/chunk-FJYLIGJX.js +1940 -0
  32. package/dist/chunk-FJYLIGJX.js.map +1 -0
  33. package/dist/chunk-JSCDE774.cjs +729 -0
  34. package/dist/chunk-JSCDE774.cjs.map +1 -0
  35. package/dist/chunk-LWECRCW2.cjs +2412 -0
  36. package/dist/chunk-LWECRCW2.cjs.map +1 -0
  37. package/dist/chunk-MLBMYKCG.js +6379 -0
  38. package/dist/chunk-MLBMYKCG.js.map +1 -0
  39. package/dist/chunk-OX6QXFMV.cjs +3874 -0
  40. package/dist/chunk-OX6QXFMV.cjs.map +1 -0
  41. package/dist/chunk-PHV2DKRS.cjs +1072 -0
  42. package/dist/chunk-PHV2DKRS.cjs.map +1 -0
  43. package/dist/chunk-PL7TAYKI.js +4056 -0
  44. package/dist/chunk-PL7TAYKI.js.map +1 -0
  45. package/dist/chunk-PR647I7R.js +1898 -0
  46. package/dist/chunk-PR647I7R.js.map +1 -0
  47. package/dist/chunk-QERHVCHC.cjs +2960 -0
  48. package/dist/chunk-QERHVCHC.cjs.map +1 -0
  49. package/dist/chunk-XEG44RF6.cjs +1514 -0
  50. package/dist/chunk-XEG44RF6.cjs.map +1 -0
  51. package/dist/chunk-XMWVME2W.js +2377 -0
  52. package/dist/chunk-XMWVME2W.js.map +1 -0
  53. package/dist/chunk-ZB75FESB.cjs +1979 -0
  54. package/dist/chunk-ZB75FESB.cjs.map +1 -0
  55. package/dist/chunk-ZLW62TJG.cjs +4061 -0
  56. package/dist/chunk-ZLW62TJG.cjs.map +1 -0
  57. package/dist/chunk-ZXKBDFP3.js +4235 -0
  58. package/dist/chunk-ZXKBDFP3.js.map +1 -0
  59. package/dist/core/index.cjs +204 -0
  60. package/dist/core/index.cjs.map +1 -0
  61. package/dist/core/index.d.cts +2 -0
  62. package/dist/core/index.d.ts +2 -0
  63. package/dist/core/index.js +3 -0
  64. package/dist/core/index.js.map +1 -0
  65. package/dist/dataframe/index.cjs +22 -0
  66. package/dist/dataframe/index.cjs.map +1 -0
  67. package/dist/dataframe/index.d.cts +3 -0
  68. package/dist/dataframe/index.d.ts +3 -0
  69. package/dist/dataframe/index.js +5 -0
  70. package/dist/dataframe/index.js.map +1 -0
  71. package/dist/datasets/index.cjs +134 -0
  72. package/dist/datasets/index.cjs.map +1 -0
  73. package/dist/datasets/index.d.cts +3 -0
  74. package/dist/datasets/index.d.ts +3 -0
  75. package/dist/datasets/index.js +5 -0
  76. package/dist/datasets/index.js.map +1 -0
  77. package/dist/index-74AB8Cyh.d.cts +1126 -0
  78. package/dist/index-9oQx1HgV.d.cts +1180 -0
  79. package/dist/index-BJY2SI4i.d.ts +483 -0
  80. package/dist/index-BWGhrDlr.d.ts +733 -0
  81. package/dist/index-B_DK4FKY.d.cts +242 -0
  82. package/dist/index-BbA2Gxfl.d.ts +456 -0
  83. package/dist/index-BgHYAoSS.d.cts +837 -0
  84. package/dist/index-BndMbqsM.d.ts +1439 -0
  85. package/dist/index-C1mfVYoo.d.ts +2517 -0
  86. package/dist/index-CCvlwAmL.d.cts +809 -0
  87. package/dist/index-CDw5CnOU.d.ts +785 -0
  88. package/dist/index-Cn3SdB0O.d.ts +1126 -0
  89. package/dist/index-CrqLlS-a.d.ts +776 -0
  90. package/dist/index-D61yaSMY.d.cts +483 -0
  91. package/dist/index-D9Loo1_A.d.cts +2517 -0
  92. package/dist/index-DIT_OO9C.d.cts +785 -0
  93. package/dist/index-DIp_RrRt.d.ts +242 -0
  94. package/dist/index-DbultU6X.d.cts +1427 -0
  95. package/dist/index-DmEg_LCm.d.cts +776 -0
  96. package/dist/index-DoPWVxPo.d.cts +1439 -0
  97. package/dist/index-DuCxd-8d.d.ts +837 -0
  98. package/dist/index-Dx42TZaY.d.ts +809 -0
  99. package/dist/index-DyZ4QQf5.d.cts +456 -0
  100. package/dist/index-GFAVyOWO.d.ts +1427 -0
  101. package/dist/index-WHQLn0e8.d.cts +733 -0
  102. package/dist/index-ZtI1Iy4L.d.ts +1180 -0
  103. package/dist/index-eJgeni9c.d.cts +1911 -0
  104. package/dist/index-tk4lSYod.d.ts +1911 -0
  105. package/dist/index.cjs +72 -0
  106. package/dist/index.cjs.map +1 -0
  107. package/dist/index.d.cts +17 -0
  108. package/dist/index.d.ts +17 -0
  109. package/dist/index.js +15 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/linalg/index.cjs +86 -0
  112. package/dist/linalg/index.cjs.map +1 -0
  113. package/dist/linalg/index.d.cts +3 -0
  114. package/dist/linalg/index.d.ts +3 -0
  115. package/dist/linalg/index.js +5 -0
  116. package/dist/linalg/index.js.map +1 -0
  117. package/dist/metrics/index.cjs +158 -0
  118. package/dist/metrics/index.cjs.map +1 -0
  119. package/dist/metrics/index.d.cts +3 -0
  120. package/dist/metrics/index.d.ts +3 -0
  121. package/dist/metrics/index.js +5 -0
  122. package/dist/metrics/index.js.map +1 -0
  123. package/dist/ml/index.cjs +87 -0
  124. package/dist/ml/index.cjs.map +1 -0
  125. package/dist/ml/index.d.cts +3 -0
  126. package/dist/ml/index.d.ts +3 -0
  127. package/dist/ml/index.js +6 -0
  128. package/dist/ml/index.js.map +1 -0
  129. package/dist/ndarray/index.cjs +501 -0
  130. package/dist/ndarray/index.cjs.map +1 -0
  131. package/dist/ndarray/index.d.cts +5 -0
  132. package/dist/ndarray/index.d.ts +5 -0
  133. package/dist/ndarray/index.js +4 -0
  134. package/dist/ndarray/index.js.map +1 -0
  135. package/dist/nn/index.cjs +142 -0
  136. package/dist/nn/index.cjs.map +1 -0
  137. package/dist/nn/index.d.cts +6 -0
  138. package/dist/nn/index.d.ts +6 -0
  139. package/dist/nn/index.js +5 -0
  140. package/dist/nn/index.js.map +1 -0
  141. package/dist/optim/index.cjs +77 -0
  142. package/dist/optim/index.cjs.map +1 -0
  143. package/dist/optim/index.d.cts +4 -0
  144. package/dist/optim/index.d.ts +4 -0
  145. package/dist/optim/index.js +4 -0
  146. package/dist/optim/index.js.map +1 -0
  147. package/dist/plot/index.cjs +114 -0
  148. package/dist/plot/index.cjs.map +1 -0
  149. package/dist/plot/index.d.cts +6 -0
  150. package/dist/plot/index.d.ts +6 -0
  151. package/dist/plot/index.js +5 -0
  152. package/dist/plot/index.js.map +1 -0
  153. package/dist/preprocess/index.cjs +82 -0
  154. package/dist/preprocess/index.cjs.map +1 -0
  155. package/dist/preprocess/index.d.cts +4 -0
  156. package/dist/preprocess/index.d.ts +4 -0
  157. package/dist/preprocess/index.js +5 -0
  158. package/dist/preprocess/index.js.map +1 -0
  159. package/dist/random/index.cjs +74 -0
  160. package/dist/random/index.cjs.map +1 -0
  161. package/dist/random/index.d.cts +3 -0
  162. package/dist/random/index.d.ts +3 -0
  163. package/dist/random/index.js +5 -0
  164. package/dist/random/index.js.map +1 -0
  165. package/dist/stats/index.cjs +142 -0
  166. package/dist/stats/index.cjs.map +1 -0
  167. package/dist/stats/index.d.cts +3 -0
  168. package/dist/stats/index.d.ts +3 -0
  169. package/dist/stats/index.js +5 -0
  170. package/dist/stats/index.js.map +1 -0
  171. package/dist/tensor-B96jjJLQ.d.cts +205 -0
  172. package/dist/tensor-B96jjJLQ.d.ts +205 -0
  173. package/package.json +226 -0
@@ -0,0 +1,4235 @@
1
+ import { tensor } from './chunk-BCR7G3A6.js';
2
+ import { __export, InvalidParameterError, NotImplementedError, ShapeError, DTypeError } from './chunk-4S73VUBD.js';
3
+
4
+ // src/plot/index.ts
5
+ var plot_exports = {};
6
+ __export(plot_exports, {
7
+ Axes: () => Axes,
8
+ Figure: () => Figure,
9
+ bar: () => bar,
10
+ barh: () => barh,
11
+ boxplot: () => boxplot,
12
+ contour: () => contour,
13
+ contourf: () => contourf,
14
+ figure: () => figure,
15
+ gca: () => gca,
16
+ heatmap: () => heatmap,
17
+ hist: () => hist,
18
+ imshow: () => imshow,
19
+ legend: () => legend,
20
+ pie: () => pie,
21
+ plot: () => plot,
22
+ plotConfusionMatrix: () => plotConfusionMatrix,
23
+ plotDecisionBoundary: () => plotDecisionBoundary,
24
+ plotLearningCurve: () => plotLearningCurve,
25
+ plotPrecisionRecallCurve: () => plotPrecisionRecallCurve,
26
+ plotRocCurve: () => plotRocCurve,
27
+ plotValidationCurve: () => plotValidationCurve,
28
+ saveFig: () => saveFig,
29
+ scatter: () => scatter,
30
+ show: () => show,
31
+ subplot: () => subplot,
32
+ violinplot: () => violinplot
33
+ });
34
+
35
+ // src/plot/utils/colors.ts
36
+ var colorCache = /* @__PURE__ */ new Map();
37
+ var namedColors = {
38
+ aliceblue: "#f0f8ff",
39
+ antiquewhite: "#faebd7",
40
+ aqua: "#00ffff",
41
+ aquamarine: "#7fffd4",
42
+ azure: "#f0ffff",
43
+ beige: "#f5f5dc",
44
+ bisque: "#ffe4c4",
45
+ black: "#000000",
46
+ blanchedalmond: "#ffebcd",
47
+ blue: "#0000ff",
48
+ blueviolet: "#8a2be2",
49
+ brown: "#a52a2a",
50
+ burlywood: "#deb887",
51
+ cadetblue: "#5f9ea0",
52
+ chartreuse: "#7fff00",
53
+ chocolate: "#d2691e",
54
+ coral: "#ff7f50",
55
+ cornflowerblue: "#6495ed",
56
+ cornsilk: "#fff8dc",
57
+ crimson: "#dc143c",
58
+ cyan: "#00ffff",
59
+ darkblue: "#00008b",
60
+ darkcyan: "#008b8b",
61
+ darkgoldenrod: "#b8860b",
62
+ darkgray: "#a9a9a9",
63
+ darkgrey: "#a9a9a9",
64
+ darkgreen: "#006400",
65
+ darkkhaki: "#bdb76b",
66
+ darkmagenta: "#8b008b",
67
+ darkolivegreen: "#556b2f",
68
+ darkorange: "#ff8c00",
69
+ darkorchid: "#9932cc",
70
+ darkred: "#8b0000",
71
+ darksalmon: "#e9967a",
72
+ darkseagreen: "#8fbc8f",
73
+ darkslateblue: "#483d8b",
74
+ darkslategray: "#2f4f4f",
75
+ darkslategrey: "#2f4f4f",
76
+ darkturquoise: "#00ced1",
77
+ darkviolet: "#9400d3",
78
+ deeppink: "#ff1493",
79
+ deepskyblue: "#00bfff",
80
+ dimgray: "#696969",
81
+ dimgrey: "#696969",
82
+ dodgerblue: "#1e90ff",
83
+ firebrick: "#b22222",
84
+ floralwhite: "#fffaf0",
85
+ forestgreen: "#228b22",
86
+ fuchsia: "#ff00ff",
87
+ gainsboro: "#dcdcdc",
88
+ ghostwhite: "#f8f8ff",
89
+ gold: "#ffd700",
90
+ goldenrod: "#daa520",
91
+ gray: "#808080",
92
+ grey: "#808080",
93
+ green: "#008000",
94
+ greenyellow: "#adff2f",
95
+ honeydew: "#f0fff0",
96
+ hotpink: "#ff69b4",
97
+ indianred: "#cd5c5c",
98
+ indigo: "#4b0082",
99
+ ivory: "#fffff0",
100
+ khaki: "#f0e68c",
101
+ lavender: "#e6e6fa",
102
+ lavenderblush: "#fff0f5",
103
+ lawngreen: "#7cfc00",
104
+ lemonchiffon: "#fffacd",
105
+ lightblue: "#add8e6",
106
+ lightcoral: "#f08080",
107
+ lightcyan: "#e0ffff",
108
+ lightgoldenrodyellow: "#fafad2",
109
+ lightgray: "#d3d3d3",
110
+ lightgrey: "#d3d3d3",
111
+ lightgreen: "#90ee90",
112
+ lightpink: "#ffb6c1",
113
+ lightsalmon: "#ffa07a",
114
+ lightseagreen: "#20b2aa",
115
+ lightskyblue: "#87cefa",
116
+ lightslategray: "#778899",
117
+ lightslategrey: "#778899",
118
+ lightsteelblue: "#b0c4de",
119
+ lightyellow: "#ffffe0",
120
+ lime: "#00ff00",
121
+ limegreen: "#32cd32",
122
+ linen: "#faf0e6",
123
+ magenta: "#ff00ff",
124
+ maroon: "#800000",
125
+ mediumaquamarine: "#66cdaa",
126
+ mediumblue: "#0000cd",
127
+ mediumorchid: "#ba55d3",
128
+ mediumpurple: "#9370db",
129
+ mediumseagreen: "#3cb371",
130
+ mediumslateblue: "#7b68ee",
131
+ mediumspringgreen: "#00fa9a",
132
+ mediumturquoise: "#48d1cc",
133
+ mediumvioletred: "#c71585",
134
+ midnightblue: "#191970",
135
+ mintcream: "#f5fffa",
136
+ mistyrose: "#ffe4e1",
137
+ moccasin: "#ffe4b5",
138
+ navajowhite: "#ffdead",
139
+ navy: "#000080",
140
+ oldlace: "#fdf5e6",
141
+ olive: "#808000",
142
+ olivedrab: "#6b8e23",
143
+ orange: "#ffa500",
144
+ orangered: "#ff4500",
145
+ orchid: "#da70d6",
146
+ palegoldenrod: "#eee8aa",
147
+ palegreen: "#98fb98",
148
+ paleturquoise: "#afeeee",
149
+ palevioletred: "#db7093",
150
+ papayawhip: "#ffefd5",
151
+ peachpuff: "#ffdab9",
152
+ peru: "#cd853f",
153
+ pink: "#ffc0cb",
154
+ plum: "#dda0dd",
155
+ powderblue: "#b0e0e6",
156
+ purple: "#800080",
157
+ rebeccapurple: "#663399",
158
+ red: "#ff0000",
159
+ rosybrown: "#bc8f8f",
160
+ royalblue: "#4169e1",
161
+ saddlebrown: "#8b4513",
162
+ salmon: "#fa8072",
163
+ sandybrown: "#f4a460",
164
+ seagreen: "#2e8b57",
165
+ seashell: "#fff5ee",
166
+ sienna: "#a0522d",
167
+ silver: "#c0c0c0",
168
+ skyblue: "#87ceeb",
169
+ slateblue: "#6a5acd",
170
+ slategray: "#708090",
171
+ slategrey: "#708090",
172
+ snow: "#fffafa",
173
+ springgreen: "#00ff7f",
174
+ steelblue: "#4682b4",
175
+ tan: "#d2b48c",
176
+ teal: "#008080",
177
+ thistle: "#d8bfd8",
178
+ tomato: "#ff6347",
179
+ turquoise: "#40e0d0",
180
+ violet: "#ee82ee",
181
+ wheat: "#f5deb3",
182
+ white: "#ffffff",
183
+ whitesmoke: "#f5f5f5",
184
+ yellow: "#ffff00",
185
+ yellowgreen: "#9acd32"
186
+ };
187
+ function normalizeColor(c, fallback) {
188
+ if (!c) return fallback;
189
+ const s = c.trim();
190
+ if (s.length === 0) return fallback;
191
+ const hex8 = s.match(/^#([0-9a-fA-F]{8})$/);
192
+ if (hex8 && hex8[1] !== void 0) {
193
+ return `#${hex8[1].toLowerCase()}`;
194
+ }
195
+ const rgba = parseHexColorToRGBA(s);
196
+ const r = rgba.r.toString(16).padStart(2, "0");
197
+ const g = rgba.g.toString(16).padStart(2, "0");
198
+ const b = rgba.b.toString(16).padStart(2, "0");
199
+ if (rgba.a === 255) {
200
+ return `#${r}${g}${b}`;
201
+ }
202
+ const a = rgba.a.toString(16).padStart(2, "0");
203
+ return `#${r}${g}${b}${a}`;
204
+ }
205
+ function parseHexColorToRGBA(c) {
206
+ const cached = colorCache.get(c);
207
+ if (cached) return cached;
208
+ const s = c.trim().toLowerCase();
209
+ const clampByte = (value) => {
210
+ if (!Number.isFinite(value)) return 0;
211
+ return Math.min(255, Math.max(0, Math.round(value)));
212
+ };
213
+ const clampAlpha = (value) => {
214
+ if (!Number.isFinite(value)) return 1;
215
+ return Math.min(1, Math.max(0, value));
216
+ };
217
+ if (namedColors[s]) {
218
+ const namedColor = namedColors[s];
219
+ if (typeof namedColor === "string") {
220
+ const result3 = parseHexColorToRGBA(namedColor);
221
+ colorCache.set(c, result3);
222
+ return result3;
223
+ }
224
+ const result2 = { r: 0, g: 0, b: 0, a: 255 };
225
+ colorCache.set(c, result2);
226
+ return result2;
227
+ }
228
+ if (s.startsWith("#")) {
229
+ const hex = s.slice(1);
230
+ if (hex.length === 6 || hex.length === 8) {
231
+ const r = Number.parseInt(hex.slice(0, 2), 16);
232
+ const g = Number.parseInt(hex.slice(2, 4), 16);
233
+ const b = Number.parseInt(hex.slice(4, 6), 16);
234
+ const a = hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) : 255;
235
+ if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b) && Number.isFinite(a)) {
236
+ const result2 = { r, g, b, a };
237
+ colorCache.set(c, result2);
238
+ return result2;
239
+ }
240
+ }
241
+ }
242
+ const rgbMatch = s.match(/^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)$/);
243
+ if (rgbMatch) {
244
+ const rStr = rgbMatch[1];
245
+ const gStr = rgbMatch[2];
246
+ const bStr = rgbMatch[3];
247
+ if (!rStr || !gStr || !bStr) {
248
+ const result3 = { r: 0, g: 0, b: 0, a: 255 };
249
+ colorCache.set(c, result3);
250
+ return result3;
251
+ }
252
+ const r = clampByte(Number.parseInt(rStr, 10));
253
+ const g = clampByte(Number.parseInt(gStr, 10));
254
+ const b = clampByte(Number.parseInt(bStr, 10));
255
+ const alpha = rgbMatch[4] ? clampAlpha(Number.parseFloat(rgbMatch[4])) : 1;
256
+ const result2 = { r, g, b, a: Math.round(alpha * 255) };
257
+ colorCache.set(c, result2);
258
+ return result2;
259
+ }
260
+ const hslMatch = s.match(
261
+ /^hsla?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%\s*(?:,\s*([\d.]+)\s*)?\)$/
262
+ );
263
+ if (hslMatch) {
264
+ const hRaw = hslMatch[1];
265
+ const sRaw = hslMatch[2];
266
+ const lRaw = hslMatch[3];
267
+ if (!hRaw || !sRaw || !lRaw) {
268
+ const result3 = { r: 0, g: 0, b: 0, a: 255 };
269
+ colorCache.set(c, result3);
270
+ return result3;
271
+ }
272
+ const hueDegrees = Number.parseFloat(hRaw);
273
+ const sat = Number.parseFloat(sRaw);
274
+ const light = Number.parseFloat(lRaw);
275
+ if (!Number.isFinite(hueDegrees) || !Number.isFinite(sat) || !Number.isFinite(light)) {
276
+ const result3 = { r: 0, g: 0, b: 0, a: 255 };
277
+ colorCache.set(c, result3);
278
+ return result3;
279
+ }
280
+ const h = (hueDegrees % 360 + 360) % 360 / 360;
281
+ const sl = Math.min(1, Math.max(0, sat / 100));
282
+ const l = Math.min(1, Math.max(0, light / 100));
283
+ const alpha = hslMatch[4] ? clampAlpha(Number.parseFloat(hslMatch[4])) : 1;
284
+ const hslToRgb = (h2, s2, l2) => {
285
+ let r2, g2, b2;
286
+ if (s2 === 0) {
287
+ r2 = g2 = b2 = l2;
288
+ } else {
289
+ const hue2rgb = (p2, q2, t) => {
290
+ if (t < 0) t += 1;
291
+ if (t > 1) t -= 1;
292
+ if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
293
+ if (t < 1 / 2) return q2;
294
+ if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
295
+ return p2;
296
+ };
297
+ const q = l2 < 0.5 ? l2 * (1 + s2) : l2 + s2 - l2 * s2;
298
+ const p = 2 * l2 - q;
299
+ r2 = hue2rgb(p, q, h2 + 1 / 3);
300
+ g2 = hue2rgb(p, q, h2);
301
+ b2 = hue2rgb(p, q, h2 - 1 / 3);
302
+ }
303
+ return [Math.round(r2 * 255), Math.round(g2 * 255), Math.round(b2 * 255)];
304
+ };
305
+ const [r, g, b] = hslToRgb(h, sl, l);
306
+ const result2 = {
307
+ r: clampByte(r),
308
+ g: clampByte(g),
309
+ b: clampByte(b),
310
+ a: Math.round(alpha * 255)
311
+ };
312
+ colorCache.set(c, result2);
313
+ return result2;
314
+ }
315
+ const result = { r: 0, g: 0, b: 0, a: 255 };
316
+ colorCache.set(c, result);
317
+ return result;
318
+ }
319
+
320
+ // src/plot/utils/legend.ts
321
+ function normalizeLegendLabel(label) {
322
+ if (typeof label !== "string") return null;
323
+ const trimmed = label.trim();
324
+ return trimmed.length > 0 ? trimmed : null;
325
+ }
326
+ function buildLegendEntry(label, entry) {
327
+ if (!label) return null;
328
+ return { label, ...entry };
329
+ }
330
+
331
+ // src/plot/utils/validation.ts
332
+ function assertPositiveInt(name, v) {
333
+ if (!Number.isFinite(v)) {
334
+ throw new InvalidParameterError(`${name} must be a positive integer; received ${v}`, name, v);
335
+ }
336
+ if (v <= 0) {
337
+ throw new InvalidParameterError(`${name} must be a positive integer; received ${v}`, name, v);
338
+ }
339
+ if (Math.trunc(v) !== v) {
340
+ throw new InvalidParameterError(`${name} must be a positive integer; received ${v}`, name, v);
341
+ }
342
+ }
343
+ function isFiniteNumber(x) {
344
+ return Number.isFinite(x);
345
+ }
346
+ function clampInt(x, lo, hi) {
347
+ if (!Number.isFinite(x)) return lo;
348
+ if (x < lo) return lo;
349
+ if (x > hi) return hi;
350
+ return Math.trunc(x);
351
+ }
352
+
353
+ // src/plot/utils/xml.ts
354
+ function escapeXml(s) {
355
+ return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
356
+ }
357
+
358
+ // src/plot/plots/Bar2D.ts
359
+ var Bar2D = class {
360
+ kind = "bar";
361
+ x;
362
+ height;
363
+ color;
364
+ edgecolor;
365
+ barWidth;
366
+ label;
367
+ constructor(x, height, options) {
368
+ if (x.length !== height.length) throw new ShapeError("x and height must have the same length");
369
+ this.x = x;
370
+ this.height = height;
371
+ this.color = normalizeColor(options.color, "#2ca02c");
372
+ this.edgecolor = normalizeColor(options.edgecolor, "#000000");
373
+ this.barWidth = 0.8;
374
+ this.label = normalizeLegendLabel(options.label);
375
+ }
376
+ getDataRange() {
377
+ let xmin = Number.POSITIVE_INFINITY;
378
+ let xmax = Number.NEGATIVE_INFINITY;
379
+ let ymin = 0;
380
+ let ymax = 0;
381
+ let sawValue = false;
382
+ for (let i = 0; i < this.x.length; i++) {
383
+ const xi = this.x[i] ?? 0;
384
+ const hi = this.height[i] ?? 0;
385
+ if (!isFiniteNumber(xi) || !isFiniteNumber(hi)) continue;
386
+ sawValue = true;
387
+ xmin = Math.min(xmin, xi - this.barWidth / 2);
388
+ xmax = Math.max(xmax, xi + this.barWidth / 2);
389
+ ymin = Math.min(ymin, hi);
390
+ ymax = Math.max(ymax, hi);
391
+ }
392
+ if (!sawValue || !Number.isFinite(xmin) || !Number.isFinite(xmax)) {
393
+ return null;
394
+ }
395
+ return { xmin, xmax, ymin, ymax };
396
+ }
397
+ drawSVG(ctx) {
398
+ for (let i = 0; i < this.x.length; i++) {
399
+ const xi = this.x[i] ?? 0;
400
+ const hi = this.height[i] ?? 0;
401
+ if (!isFiniteNumber(xi) || !isFiniteNumber(hi)) continue;
402
+ const x0 = ctx.transform.xToPx(xi - this.barWidth / 2);
403
+ const x1 = ctx.transform.xToPx(xi + this.barWidth / 2);
404
+ const y0 = ctx.transform.yToPx(0);
405
+ const y1 = ctx.transform.yToPx(hi);
406
+ const w = Math.abs(x1 - x0);
407
+ const h = Math.abs(y1 - y0);
408
+ const rx = Math.min(x0, x1);
409
+ const ry = Math.min(y0, y1);
410
+ ctx.push(
411
+ `<rect x="${rx.toFixed(2)}" y="${ry.toFixed(2)}" width="${w.toFixed(2)}" height="${h.toFixed(2)}" fill="${escapeXml(this.color)}" stroke="${escapeXml(this.edgecolor)}" />`
412
+ );
413
+ }
414
+ }
415
+ drawRaster(ctx) {
416
+ const rgba = parseHexColorToRGBA(this.color);
417
+ const edge = parseHexColorToRGBA(this.edgecolor);
418
+ for (let i = 0; i < this.x.length; i++) {
419
+ const xi = this.x[i] ?? 0;
420
+ const hi = this.height[i] ?? 0;
421
+ if (!isFiniteNumber(xi) || !isFiniteNumber(hi)) continue;
422
+ const x0 = Math.round(ctx.transform.xToPx(xi - this.barWidth / 2));
423
+ const x1 = Math.round(ctx.transform.xToPx(xi + this.barWidth / 2));
424
+ const y0 = Math.round(ctx.transform.yToPx(0));
425
+ const y1 = Math.round(ctx.transform.yToPx(hi));
426
+ const w = Math.abs(x1 - x0);
427
+ const h = Math.abs(y1 - y0);
428
+ const rx = Math.min(x0, x1);
429
+ const ry = Math.min(y0, y1);
430
+ ctx.canvas.fillRectRGBA(rx, ry, w, h, rgba.r, rgba.g, rgba.b, rgba.a);
431
+ const r_e = edge.r;
432
+ const g_e = edge.g;
433
+ const b_e = edge.b;
434
+ const a_e = edge.a;
435
+ ctx.canvas.drawLineRGBA(rx, ry, rx + w, ry, r_e, g_e, b_e, a_e);
436
+ ctx.canvas.drawLineRGBA(rx + w, ry, rx + w, ry + h, r_e, g_e, b_e, a_e);
437
+ ctx.canvas.drawLineRGBA(rx + w, ry + h, rx, ry + h, r_e, g_e, b_e, a_e);
438
+ ctx.canvas.drawLineRGBA(rx, ry + h, rx, ry, r_e, g_e, b_e, a_e);
439
+ }
440
+ }
441
+ getLegendEntries() {
442
+ const entry = buildLegendEntry(this.label, { color: this.color, shape: "box" });
443
+ return entry ? [entry] : null;
444
+ }
445
+ };
446
+
447
+ // src/plot/utils/statistics.ts
448
+ function calculateQuartiles(sortedData) {
449
+ const n = sortedData.length;
450
+ if (n === 0) {
451
+ return { q1: 0, median: 0, q3: 0 };
452
+ }
453
+ if (n === 1) {
454
+ const val = sortedData[0] ?? 0;
455
+ return { q1: val, median: val, q3: val };
456
+ }
457
+ const medianOf = (arr, start, end) => {
458
+ const length = end - start;
459
+ if (length === 0) return 0;
460
+ if (length === 1) return arr[start] ?? 0;
461
+ const mid2 = start + Math.floor(length / 2);
462
+ if (length % 2 === 1) {
463
+ return arr[mid2] ?? 0;
464
+ } else {
465
+ return ((arr[mid2 - 1] ?? 0) + (arr[mid2] ?? 0)) / 2;
466
+ }
467
+ };
468
+ const mid = Math.floor(n / 2);
469
+ const median = n % 2 === 1 ? sortedData[mid] ?? 0 : ((sortedData[mid - 1] ?? 0) + (sortedData[mid] ?? 0)) / 2;
470
+ const q1 = medianOf(sortedData, 0, mid);
471
+ const q3 = medianOf(sortedData, n % 2 === 1 ? mid + 1 : mid, n);
472
+ return { q1, median, q3 };
473
+ }
474
+ function calculateWhiskers(sortedData, q1, q3) {
475
+ if (sortedData.length === 0) {
476
+ return { lowerWhisker: 0, upperWhisker: 0, outliers: [] };
477
+ }
478
+ const iqr = q3 - q1;
479
+ const lowerBound = q1 - 1.5 * iqr;
480
+ const upperBound = q3 + 1.5 * iqr;
481
+ const outliers = [];
482
+ let lowerWhisker = null;
483
+ let upperWhisker = null;
484
+ for (const value of sortedData) {
485
+ if (value < lowerBound || value > upperBound) {
486
+ outliers.push(value);
487
+ } else {
488
+ if (lowerWhisker === null) {
489
+ lowerWhisker = value;
490
+ }
491
+ upperWhisker = value;
492
+ }
493
+ }
494
+ if (lowerWhisker === null) {
495
+ lowerWhisker = q1;
496
+ }
497
+ if (upperWhisker === null) {
498
+ upperWhisker = q3;
499
+ }
500
+ return { lowerWhisker, upperWhisker, outliers };
501
+ }
502
+ function kernelDensityEstimation(data, points, bandwidth) {
503
+ const n = data.length;
504
+ const m = points.length;
505
+ const result = new Float64Array(m);
506
+ if (n === 0) {
507
+ return Array.from(result);
508
+ }
509
+ if (!Number.isFinite(bandwidth) || bandwidth <= 0) {
510
+ let sum = 0;
511
+ let sumSq = 0;
512
+ let min = Number.POSITIVE_INFINITY;
513
+ let max = Number.NEGATIVE_INFINITY;
514
+ for (let i = 0; i < n; i++) {
515
+ const v = data[i] ?? 0;
516
+ sum += v;
517
+ sumSq += v * v;
518
+ if (v < min) min = v;
519
+ if (v > max) max = v;
520
+ }
521
+ const mean = sum / n;
522
+ const variance = Math.max(0, sumSq / n - mean * mean);
523
+ const stdDev = Math.sqrt(variance);
524
+ const silverman = 1.06 * stdDev * n ** -0.2;
525
+ if (Number.isFinite(silverman) && silverman > 0) {
526
+ bandwidth = silverman;
527
+ } else {
528
+ const range = max - min;
529
+ bandwidth = Number.isFinite(range) && range > 0 ? range * 0.1 : 1;
530
+ }
531
+ }
532
+ if (!Number.isFinite(bandwidth) || bandwidth <= 0) {
533
+ bandwidth = 1;
534
+ }
535
+ const invSqrt2PiBandwidth = 1 / (bandwidth * Math.sqrt(2 * Math.PI));
536
+ for (let i = 0; i < m; i++) {
537
+ const x = points[i] ?? 0;
538
+ let sum = 0;
539
+ for (let j = 0; j < n; j++) {
540
+ const u = (x - (data[j] ?? 0)) / bandwidth;
541
+ sum += Math.exp(-0.5 * u * u);
542
+ }
543
+ result[i] = sum * invSqrt2PiBandwidth / n;
544
+ }
545
+ return Array.from(result);
546
+ }
547
+
548
+ // src/plot/plots/Boxplot.ts
549
+ var Boxplot = class {
550
+ kind = "boxplot";
551
+ position;
552
+ q1;
553
+ median;
554
+ q3;
555
+ whiskerLow;
556
+ whiskerHigh;
557
+ outliers;
558
+ color;
559
+ edgecolor;
560
+ boxWidth;
561
+ label;
562
+ hasData;
563
+ constructor(position, data, options) {
564
+ this.position = position;
565
+ this.color = normalizeColor(options.color, "#8c564b");
566
+ this.edgecolor = normalizeColor(options.edgecolor, "#000000");
567
+ this.boxWidth = 0.5;
568
+ this.label = normalizeLegendLabel(options.label);
569
+ const sorted = Array.from(data).filter(isFiniteNumber).sort((a, b) => a - b);
570
+ const n = sorted.length;
571
+ if (n === 0) {
572
+ if (data.length > 0) {
573
+ throw new InvalidParameterError(
574
+ "boxplot data must contain at least one finite value",
575
+ "data",
576
+ data
577
+ );
578
+ }
579
+ this.hasData = false;
580
+ this.q1 = 0;
581
+ this.median = 0;
582
+ this.q3 = 0;
583
+ this.whiskerLow = 0;
584
+ this.whiskerHigh = 0;
585
+ this.outliers = [];
586
+ return;
587
+ }
588
+ this.hasData = true;
589
+ const { q1, median, q3 } = calculateQuartiles(sorted);
590
+ this.q1 = q1;
591
+ this.median = median;
592
+ this.q3 = q3;
593
+ const { lowerWhisker, upperWhisker, outliers } = calculateWhiskers(sorted, q1, q3);
594
+ this.whiskerLow = lowerWhisker;
595
+ this.whiskerHigh = upperWhisker;
596
+ this.outliers = outliers;
597
+ }
598
+ getDataRange() {
599
+ if (!this.hasData) return null;
600
+ let ymin = this.whiskerLow;
601
+ let ymax = this.whiskerHigh;
602
+ for (const o of this.outliers) {
603
+ if (o < ymin) ymin = o;
604
+ if (o > ymax) ymax = o;
605
+ }
606
+ return {
607
+ xmin: this.position - this.boxWidth,
608
+ xmax: this.position + this.boxWidth,
609
+ ymin,
610
+ ymax
611
+ };
612
+ }
613
+ drawSVG(ctx) {
614
+ if (!this.hasData) return;
615
+ const x = this.position;
616
+ const x0 = ctx.transform.xToPx(x - this.boxWidth / 2);
617
+ const x1 = ctx.transform.xToPx(x + this.boxWidth / 2);
618
+ const xc = ctx.transform.xToPx(x);
619
+ const yq1 = ctx.transform.yToPx(this.q1);
620
+ const ymed = ctx.transform.yToPx(this.median);
621
+ const yq3 = ctx.transform.yToPx(this.q3);
622
+ const ywl = ctx.transform.yToPx(this.whiskerLow);
623
+ const ywh = ctx.transform.yToPx(this.whiskerHigh);
624
+ ctx.push(
625
+ `<rect x="${Math.min(x0, x1).toFixed(2)}" y="${Math.min(yq1, yq3).toFixed(2)}" width="${Math.abs(x1 - x0).toFixed(2)}" height="${Math.abs(yq3 - yq1).toFixed(2)}" fill="${escapeXml(this.color)}" stroke="${escapeXml(this.edgecolor)}" />`
626
+ );
627
+ ctx.push(
628
+ `<line x1="${x0.toFixed(2)}" y1="${ymed.toFixed(2)}" x2="${x1.toFixed(2)}" y2="${ymed.toFixed(2)}" stroke="${escapeXml(this.edgecolor)}" stroke-width="2" />`
629
+ );
630
+ ctx.push(
631
+ `<line x1="${xc.toFixed(2)}" y1="${yq1.toFixed(2)}" x2="${xc.toFixed(2)}" y2="${ywl.toFixed(2)}" stroke="${escapeXml(this.edgecolor)}" />`
632
+ );
633
+ ctx.push(
634
+ `<line x1="${xc.toFixed(2)}" y1="${yq3.toFixed(2)}" x2="${xc.toFixed(2)}" y2="${ywh.toFixed(2)}" stroke="${escapeXml(this.edgecolor)}" />`
635
+ );
636
+ const capHalf = Math.abs(x1 - x0) / 4;
637
+ ctx.push(
638
+ `<line x1="${(xc - capHalf).toFixed(2)}" y1="${ywl.toFixed(2)}" x2="${(xc + capHalf).toFixed(2)}" y2="${ywl.toFixed(2)}" stroke="${escapeXml(this.edgecolor)}" />`
639
+ );
640
+ ctx.push(
641
+ `<line x1="${(xc - capHalf).toFixed(2)}" y1="${ywh.toFixed(2)}" x2="${(xc + capHalf).toFixed(2)}" y2="${ywh.toFixed(2)}" stroke="${escapeXml(this.edgecolor)}" />`
642
+ );
643
+ for (const outlier of this.outliers) {
644
+ const yo = ctx.transform.yToPx(outlier);
645
+ ctx.push(
646
+ `<circle cx="${xc.toFixed(2)}" cy="${yo.toFixed(2)}" r="3" fill="${escapeXml(this.edgecolor)}" stroke="none" />`
647
+ );
648
+ }
649
+ }
650
+ drawRaster(ctx) {
651
+ if (!this.hasData) return;
652
+ const rgba = parseHexColorToRGBA(this.color);
653
+ const edge = parseHexColorToRGBA(this.edgecolor);
654
+ const x = this.position;
655
+ const x0 = Math.round(ctx.transform.xToPx(x - this.boxWidth / 2));
656
+ const x1 = Math.round(ctx.transform.xToPx(x + this.boxWidth / 2));
657
+ const xc = Math.round(ctx.transform.xToPx(x));
658
+ const yq1 = Math.round(ctx.transform.yToPx(this.q1));
659
+ const ymed = Math.round(ctx.transform.yToPx(this.median));
660
+ const yq3 = Math.round(ctx.transform.yToPx(this.q3));
661
+ const ywl = Math.round(ctx.transform.yToPx(this.whiskerLow));
662
+ const ywh = Math.round(ctx.transform.yToPx(this.whiskerHigh));
663
+ const rx = Math.min(x0, x1);
664
+ const ry = Math.min(yq1, yq3);
665
+ const w = Math.abs(x1 - x0);
666
+ const h = Math.abs(yq3 - yq1);
667
+ ctx.canvas.fillRectRGBA(rx, ry, w, h, rgba.r, rgba.g, rgba.b, rgba.a);
668
+ ctx.canvas.drawLineRGBA(rx, ry, rx + w, ry, edge.r, edge.g, edge.b, edge.a);
669
+ ctx.canvas.drawLineRGBA(rx + w, ry, rx + w, ry + h, edge.r, edge.g, edge.b, edge.a);
670
+ ctx.canvas.drawLineRGBA(rx + w, ry + h, rx, ry + h, edge.r, edge.g, edge.b, edge.a);
671
+ ctx.canvas.drawLineRGBA(rx, ry + h, rx, ry, edge.r, edge.g, edge.b, edge.a);
672
+ ctx.canvas.drawLineRGBA(x0, ymed, x1, ymed, edge.r, edge.g, edge.b, edge.a);
673
+ ctx.canvas.drawLineRGBA(xc, yq1, xc, ywl, edge.r, edge.g, edge.b, edge.a);
674
+ ctx.canvas.drawLineRGBA(xc, yq3, xc, ywh, edge.r, edge.g, edge.b, edge.a);
675
+ const capHalf = Math.round(Math.abs(x1 - x0) / 4);
676
+ ctx.canvas.drawLineRGBA(xc - capHalf, ywl, xc + capHalf, ywl, edge.r, edge.g, edge.b, edge.a);
677
+ ctx.canvas.drawLineRGBA(xc - capHalf, ywh, xc + capHalf, ywh, edge.r, edge.g, edge.b, edge.a);
678
+ for (const outlier of this.outliers) {
679
+ const yo = Math.round(ctx.transform.yToPx(outlier));
680
+ ctx.canvas.drawCircleRGBA(xc, yo, 3, edge.r, edge.g, edge.b, edge.a);
681
+ }
682
+ }
683
+ getLegendEntries() {
684
+ const entry = buildLegendEntry(this.label, {
685
+ color: this.color,
686
+ shape: "box"
687
+ });
688
+ return entry ? [entry] : null;
689
+ }
690
+ };
691
+
692
+ // src/plot/utils/colormaps.ts
693
+ var colormaps = {
694
+ viridis: [
695
+ [68, 1, 84],
696
+ [72, 40, 120],
697
+ [62, 73, 137],
698
+ [49, 104, 142],
699
+ [38, 130, 142],
700
+ [31, 158, 137],
701
+ [53, 183, 121],
702
+ [110, 206, 88],
703
+ [181, 222, 43],
704
+ [253, 231, 37]
705
+ ],
706
+ plasma: [
707
+ [13, 8, 135],
708
+ [75, 3, 161],
709
+ [125, 3, 168],
710
+ [168, 34, 150],
711
+ [203, 70, 121],
712
+ [229, 107, 93],
713
+ [248, 148, 65],
714
+ [253, 195, 40],
715
+ [240, 249, 33],
716
+ [240, 249, 33]
717
+ ],
718
+ inferno: [
719
+ [0, 0, 4],
720
+ [40, 11, 84],
721
+ [101, 21, 110],
722
+ [159, 42, 99],
723
+ [212, 72, 66],
724
+ [245, 125, 21],
725
+ [250, 193, 39],
726
+ [245, 251, 161],
727
+ [252, 255, 164],
728
+ [252, 255, 164]
729
+ ],
730
+ magma: [
731
+ [0, 0, 4],
732
+ [28, 16, 68],
733
+ [79, 18, 123],
734
+ [129, 37, 129],
735
+ [181, 54, 122],
736
+ [229, 80, 100],
737
+ [251, 135, 97],
738
+ [254, 194, 135],
739
+ [252, 253, 191],
740
+ [252, 253, 191]
741
+ ],
742
+ grayscale: [
743
+ [0, 0, 0],
744
+ [28, 28, 28],
745
+ [57, 57, 57],
746
+ [85, 85, 85],
747
+ [113, 113, 113],
748
+ [142, 142, 142],
749
+ [170, 170, 170],
750
+ [198, 198, 198],
751
+ [227, 227, 227],
752
+ [255, 255, 255]
753
+ ]
754
+ };
755
+ function applyColormap(value, colormap) {
756
+ const cmap = colormaps[colormap];
757
+ const n = cmap.length;
758
+ const clamped = Math.max(0, Math.min(1, value));
759
+ const idx = Math.max(0, Math.min(n - 2, Math.floor(clamped * (n - 1))));
760
+ const nextIdx = idx + 1;
761
+ const t = Math.max(0, Math.min(1, clamped * (n - 1) - idx));
762
+ const c1 = cmap[idx];
763
+ const c2 = cmap[nextIdx];
764
+ if (!c1 || !c2) return [0, 0, 0];
765
+ return [
766
+ Math.round(c1[0] + (c2[0] - c1[0]) * t),
767
+ Math.round(c1[1] + (c2[1] - c1[1]) * t),
768
+ Math.round(c1[2] + (c2[2] - c1[2]) * t)
769
+ ];
770
+ }
771
+
772
+ // src/plot/plots/Contour2D.ts
773
+ var DEFAULT_COLORS = [
774
+ "#1f77b4",
775
+ "#ff7f0e",
776
+ "#2ca02c",
777
+ "#d62728",
778
+ "#9467bd",
779
+ "#8c564b",
780
+ "#e377c2",
781
+ "#7f7f7f",
782
+ "#bcbd22",
783
+ "#17becf"
784
+ ];
785
+ function resolveLevels(min, max, levels) {
786
+ if (Array.isArray(levels)) {
787
+ const filtered = levels.filter((v) => Number.isFinite(v));
788
+ if (filtered.length === 0) {
789
+ throw new InvalidParameterError(
790
+ "levels must contain at least one finite value",
791
+ "levels",
792
+ levels
793
+ );
794
+ }
795
+ const unique = Array.from(new Set(filtered));
796
+ unique.sort((a, b) => a - b);
797
+ return unique;
798
+ }
799
+ const numLevels = typeof levels === "number" ? levels : 10;
800
+ if (!Number.isFinite(numLevels) || Math.trunc(numLevels) !== numLevels || numLevels <= 0) {
801
+ throw new InvalidParameterError(
802
+ `levels must be a positive integer; received ${numLevels}`,
803
+ "levels",
804
+ numLevels
805
+ );
806
+ }
807
+ if (numLevels === 1) {
808
+ return [min === max ? min : (min + max) / 2];
809
+ }
810
+ const step = (max - min) / (numLevels - 1);
811
+ const values = new Array(numLevels);
812
+ for (let i = 0; i < numLevels; i++) {
813
+ values[i] = min + i * step;
814
+ }
815
+ return values;
816
+ }
817
+ function resolveLevelColors(levels, options) {
818
+ const count = levels.length;
819
+ const colors = [];
820
+ if (options.colors && options.colors.length > 0) {
821
+ for (let i = 0; i < count; i++) {
822
+ const color = options.colors[i % options.colors.length];
823
+ colors.push(normalizeColor(color, DEFAULT_COLORS[i % DEFAULT_COLORS.length] ?? "#1f77b4"));
824
+ }
825
+ } else if (options.color) {
826
+ const normalized = normalizeColor(options.color, "#1f77b4");
827
+ for (let i = 0; i < count; i++) colors.push(normalized);
828
+ } else if (options.colormap) {
829
+ if (!["viridis", "plasma", "inferno", "magma", "grayscale"].includes(options.colormap)) {
830
+ throw new InvalidParameterError(
831
+ `colormap must be one of viridis, plasma, inferno, magma, grayscale; received ${options.colormap}`,
832
+ "colormap",
833
+ options.colormap
834
+ );
835
+ }
836
+ const denom = Math.max(1, count - 1);
837
+ for (let i = 0; i < count; i++) {
838
+ const t = i / denom;
839
+ const [r, g, b] = applyColormap(t, options.colormap);
840
+ colors.push(normalizeColor(`rgb(${r},${g},${b})`, "#1f77b4"));
841
+ }
842
+ } else {
843
+ for (let i = 0; i < count; i++) {
844
+ const color = DEFAULT_COLORS[i % DEFAULT_COLORS.length] ?? "#1f77b4";
845
+ colors.push(i === 0 ? color : normalizeColor(color, "#1f77b4"));
846
+ }
847
+ }
848
+ const rgba = colors.map((c) => parseHexColorToRGBA(c));
849
+ return { colors, rgba };
850
+ }
851
+ function interpolate(level, v0, v1, c0, c1) {
852
+ if (v0 === v1) return (c0 + c1) / 2;
853
+ const t = Math.max(0, Math.min(1, (level - v0) / (v1 - v0)));
854
+ return c0 + t * (c1 - c0);
855
+ }
856
+ var Contour2D = class {
857
+ kind = "contour";
858
+ segments;
859
+ levelColors;
860
+ levelRGBA;
861
+ linewidth;
862
+ xmin;
863
+ xmax;
864
+ ymin;
865
+ ymax;
866
+ label;
867
+ constructor(grid, options = {}) {
868
+ if (options.vmin !== void 0 && !Number.isFinite(options.vmin)) {
869
+ throw new InvalidParameterError(
870
+ `vmin must be finite; received ${options.vmin}`,
871
+ "vmin",
872
+ options.vmin
873
+ );
874
+ }
875
+ if (options.vmax !== void 0 && !Number.isFinite(options.vmax)) {
876
+ throw new InvalidParameterError(
877
+ `vmax must be finite; received ${options.vmax}`,
878
+ "vmax",
879
+ options.vmax
880
+ );
881
+ }
882
+ const min = options.vmin ?? grid.dataMin;
883
+ const max = options.vmax ?? grid.dataMax;
884
+ if (Number.isFinite(min) && Number.isFinite(max) && min > max) {
885
+ throw new InvalidParameterError(
886
+ `vmin must be <= vmax; received vmin=${min} vmax=${max}`,
887
+ "vmin/vmax",
888
+ { vmin: min, vmax: max }
889
+ );
890
+ }
891
+ const levels = resolveLevels(min, max, options.levels);
892
+ const { colors, rgba } = resolveLevelColors(levels, options);
893
+ let levelColors = colors;
894
+ let levelRGBA = rgba;
895
+ this.label = normalizeLegendLabel(options.label);
896
+ const lw = options.linewidth ?? 1;
897
+ if (!Number.isFinite(lw) || lw <= 0) {
898
+ throw new InvalidParameterError(
899
+ `linewidth must be a positive number; received ${lw}`,
900
+ "linewidth",
901
+ lw
902
+ );
903
+ }
904
+ this.linewidth = lw;
905
+ let xMin = Number.POSITIVE_INFINITY;
906
+ let xMax = Number.NEGATIVE_INFINITY;
907
+ let yMin = Number.POSITIVE_INFINITY;
908
+ let yMax = Number.NEGATIVE_INFINITY;
909
+ for (let i = 0; i < grid.xCoords.length; i++) {
910
+ const v = grid.xCoords[i] ?? 0;
911
+ if (v < xMin) xMin = v;
912
+ if (v > xMax) xMax = v;
913
+ }
914
+ for (let i = 0; i < grid.yCoords.length; i++) {
915
+ const v = grid.yCoords[i] ?? 0;
916
+ if (v < yMin) yMin = v;
917
+ if (v > yMax) yMax = v;
918
+ }
919
+ this.xmin = xMin;
920
+ this.xmax = xMax;
921
+ this.ymin = yMin;
922
+ this.ymax = yMax;
923
+ let segments = [];
924
+ const rows = grid.rows;
925
+ const cols = grid.cols;
926
+ const data = grid.data;
927
+ const xCoords = grid.xCoords;
928
+ const yCoords = grid.yCoords;
929
+ if (rows >= 2 && cols >= 2) {
930
+ for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {
931
+ const level = levels[levelIndex] ?? 0;
932
+ for (let i = 0; i < rows - 1; i++) {
933
+ const y0 = yCoords[i] ?? 0;
934
+ const y1 = yCoords[i + 1] ?? 0;
935
+ const rowOffset = i * cols;
936
+ const rowOffsetNext = (i + 1) * cols;
937
+ for (let j = 0; j < cols - 1; j++) {
938
+ const x0 = xCoords[j] ?? 0;
939
+ const x1 = xCoords[j + 1] ?? 0;
940
+ const v00 = data[rowOffset + j] ?? 0;
941
+ const v10 = data[rowOffset + j + 1] ?? 0;
942
+ const v11 = data[rowOffsetNext + j + 1] ?? 0;
943
+ const v01 = data[rowOffsetNext + j] ?? 0;
944
+ if (!isFiniteNumber(v00) || !isFiniteNumber(v10) || !isFiniteNumber(v11) || !isFiniteNumber(v01)) {
945
+ continue;
946
+ }
947
+ const idx = (v00 >= level ? 1 : 0) | (v10 >= level ? 2 : 0) | (v11 >= level ? 4 : 0) | (v01 >= level ? 8 : 0);
948
+ if (idx === 0 || idx === 15) continue;
949
+ const edgePoint = (edge) => {
950
+ switch (edge) {
951
+ case 0:
952
+ return { x: interpolate(level, v00, v10, x0, x1), y: y0 };
953
+ case 1:
954
+ return { x: x1, y: interpolate(level, v10, v11, y0, y1) };
955
+ case 2:
956
+ return { x: interpolate(level, v11, v01, x1, x0), y: y1 };
957
+ case 3:
958
+ return { x: x0, y: interpolate(level, v01, v00, y1, y0) };
959
+ default:
960
+ return { x: x0, y: y0 };
961
+ }
962
+ };
963
+ const pushSegment = (e1, e2) => {
964
+ const p1 = edgePoint(e1);
965
+ const p2 = edgePoint(e2);
966
+ segments.push({
967
+ x1: p1.x,
968
+ y1: p1.y,
969
+ x2: p2.x,
970
+ y2: p2.y,
971
+ levelIndex
972
+ });
973
+ };
974
+ switch (idx) {
975
+ case 1:
976
+ pushSegment(3, 0);
977
+ break;
978
+ case 2:
979
+ pushSegment(0, 1);
980
+ break;
981
+ case 3:
982
+ pushSegment(3, 1);
983
+ break;
984
+ case 4:
985
+ pushSegment(1, 2);
986
+ break;
987
+ case 5: {
988
+ const center = (v00 + v10 + v11 + v01) / 4;
989
+ if (center >= level) {
990
+ pushSegment(3, 2);
991
+ pushSegment(0, 1);
992
+ } else {
993
+ pushSegment(3, 0);
994
+ pushSegment(1, 2);
995
+ }
996
+ break;
997
+ }
998
+ case 6:
999
+ pushSegment(0, 2);
1000
+ break;
1001
+ case 7:
1002
+ pushSegment(3, 2);
1003
+ break;
1004
+ case 8:
1005
+ pushSegment(2, 3);
1006
+ break;
1007
+ case 9:
1008
+ pushSegment(0, 2);
1009
+ break;
1010
+ case 10: {
1011
+ const center = (v00 + v10 + v11 + v01) / 4;
1012
+ if (center >= level) {
1013
+ pushSegment(0, 3);
1014
+ pushSegment(2, 1);
1015
+ } else {
1016
+ pushSegment(0, 1);
1017
+ pushSegment(2, 3);
1018
+ }
1019
+ break;
1020
+ }
1021
+ case 11:
1022
+ pushSegment(1, 2);
1023
+ break;
1024
+ case 12:
1025
+ pushSegment(1, 3);
1026
+ break;
1027
+ case 13:
1028
+ pushSegment(0, 1);
1029
+ break;
1030
+ case 14:
1031
+ pushSegment(3, 0);
1032
+ break;
1033
+ }
1034
+ }
1035
+ }
1036
+ }
1037
+ }
1038
+ if (segments.length > 0) {
1039
+ const used = Array.from(new Set(segments.map((seg) => seg.levelIndex))).sort((a, b) => a - b);
1040
+ if (used.length > 0 && used.length < levels.length) {
1041
+ const usedLevels = used.map((idx) => levels[idx] ?? 0);
1042
+ const resolved = resolveLevelColors(usedLevels, options);
1043
+ levelColors = resolved.colors;
1044
+ levelRGBA = resolved.rgba;
1045
+ const map = /* @__PURE__ */ new Map();
1046
+ for (let i = 0; i < used.length; i++) {
1047
+ const idx = used[i];
1048
+ if (idx !== void 0) {
1049
+ map.set(idx, i);
1050
+ }
1051
+ }
1052
+ segments = segments.map((seg) => ({
1053
+ x1: seg.x1,
1054
+ y1: seg.y1,
1055
+ x2: seg.x2,
1056
+ y2: seg.y2,
1057
+ levelIndex: map.get(seg.levelIndex) ?? 0
1058
+ }));
1059
+ }
1060
+ }
1061
+ this.levelColors = levelColors;
1062
+ this.levelRGBA = levelRGBA;
1063
+ this.segments = segments;
1064
+ }
1065
+ getDataRange() {
1066
+ if (!Number.isFinite(this.xmin) || !Number.isFinite(this.xmax)) return null;
1067
+ if (!Number.isFinite(this.ymin) || !Number.isFinite(this.ymax)) return null;
1068
+ return {
1069
+ xmin: this.xmin,
1070
+ xmax: this.xmax,
1071
+ ymin: this.ymin,
1072
+ ymax: this.ymax
1073
+ };
1074
+ }
1075
+ drawSVG(ctx) {
1076
+ for (const seg of this.segments) {
1077
+ const color = this.levelColors[seg.levelIndex] ?? "#1f77b4";
1078
+ ctx.push(
1079
+ `<line x1="${ctx.transform.xToPx(seg.x1).toFixed(2)}" y1="${ctx.transform.yToPx(seg.y1).toFixed(2)}" x2="${ctx.transform.xToPx(seg.x2).toFixed(2)}" y2="${ctx.transform.yToPx(seg.y2).toFixed(
1080
+ 2
1081
+ )}" stroke="${escapeXml(color)}" stroke-width="${this.linewidth}" fill="none" />`
1082
+ );
1083
+ }
1084
+ }
1085
+ drawRaster(ctx) {
1086
+ for (const seg of this.segments) {
1087
+ const rgba = this.levelRGBA[seg.levelIndex] ?? {
1088
+ r: 0,
1089
+ g: 0,
1090
+ b: 0,
1091
+ a: 255
1092
+ };
1093
+ ctx.canvas.drawLineRGBA(
1094
+ Math.round(ctx.transform.xToPx(seg.x1)),
1095
+ Math.round(ctx.transform.yToPx(seg.y1)),
1096
+ Math.round(ctx.transform.xToPx(seg.x2)),
1097
+ Math.round(ctx.transform.yToPx(seg.y2)),
1098
+ rgba.r,
1099
+ rgba.g,
1100
+ rgba.b,
1101
+ rgba.a
1102
+ );
1103
+ }
1104
+ }
1105
+ getLegendEntries() {
1106
+ const color = this.levelColors[0] ?? "#1f77b4";
1107
+ const entry = buildLegendEntry(this.label, {
1108
+ color,
1109
+ shape: "line",
1110
+ lineWidth: this.linewidth
1111
+ });
1112
+ return entry ? [entry] : null;
1113
+ }
1114
+ };
1115
+
1116
+ // src/plot/plots/ContourF2D.ts
1117
+ var DEFAULT_COLORS2 = [
1118
+ "#1f77b4",
1119
+ "#ff7f0e",
1120
+ "#2ca02c",
1121
+ "#d62728",
1122
+ "#9467bd",
1123
+ "#8c564b",
1124
+ "#e377c2",
1125
+ "#7f7f7f",
1126
+ "#bcbd22",
1127
+ "#17becf"
1128
+ ];
1129
+ function interpolateVertex(a, b, threshold) {
1130
+ const dv = b.v - a.v;
1131
+ const t = dv === 0 ? 0.5 : Math.max(0, Math.min(1, (threshold - a.v) / dv));
1132
+ return {
1133
+ x: a.x + t * (b.x - a.x),
1134
+ y: a.y + t * (b.y - a.y),
1135
+ v: threshold
1136
+ };
1137
+ }
1138
+ function clipPolygonByThreshold(points, threshold, keepAbove) {
1139
+ if (points.length === 0) return [];
1140
+ const output = [];
1141
+ for (let i = 0; i < points.length; i++) {
1142
+ const current = points[i];
1143
+ const next = points[(i + 1) % points.length];
1144
+ if (!current || !next) continue;
1145
+ const currentInside = keepAbove ? current.v >= threshold : current.v <= threshold;
1146
+ const nextInside = keepAbove ? next.v >= threshold : next.v <= threshold;
1147
+ if (currentInside && nextInside) {
1148
+ output.push(next);
1149
+ } else if (currentInside && !nextInside) {
1150
+ output.push(interpolateVertex(current, next, threshold));
1151
+ } else if (!currentInside && nextInside) {
1152
+ output.push(interpolateVertex(current, next, threshold));
1153
+ output.push(next);
1154
+ }
1155
+ }
1156
+ return output;
1157
+ }
1158
+ function triangulatePolygon(polygon, band, out) {
1159
+ if (polygon.length < 3) return;
1160
+ const origin = polygon[0];
1161
+ if (!origin) return;
1162
+ for (let i = 1; i < polygon.length - 1; i++) {
1163
+ const v1 = polygon[i];
1164
+ const v2 = polygon[i + 1];
1165
+ if (!v1 || !v2) continue;
1166
+ out.push({
1167
+ x1: origin.x,
1168
+ y1: origin.y,
1169
+ x2: v1.x,
1170
+ y2: v1.y,
1171
+ x3: v2.x,
1172
+ y3: v2.y,
1173
+ band
1174
+ });
1175
+ }
1176
+ }
1177
+ function addBandTriangles(tri, levels, out) {
1178
+ if (tri.length !== 3 || levels.length < 2) return;
1179
+ const v0 = tri[0]?.v ?? 0;
1180
+ const v1 = tri[1]?.v ?? 0;
1181
+ const v2 = tri[2]?.v ?? 0;
1182
+ const triMin = Math.min(v0, v1, v2);
1183
+ const triMax = Math.max(v0, v1, v2);
1184
+ const lastLevel = levels[levels.length - 1] ?? 0;
1185
+ if (triMax < (levels[0] ?? 0) || triMin > lastLevel) return;
1186
+ const bandCount = levels.length - 1;
1187
+ for (let band = 0; band < bandCount; band++) {
1188
+ const lo = levels[band] ?? 0;
1189
+ const hi = levels[band + 1] ?? lo;
1190
+ if (triMax < lo || triMin > hi) continue;
1191
+ let poly = clipPolygonByThreshold(tri, lo, true);
1192
+ if (poly.length === 0) continue;
1193
+ poly = clipPolygonByThreshold(poly, hi, false);
1194
+ if (poly.length < 3) continue;
1195
+ triangulatePolygon(poly, band, out);
1196
+ }
1197
+ }
1198
+ function resolveFillLevels(min, max, levels) {
1199
+ if (Array.isArray(levels)) {
1200
+ const filtered = levels.filter((v) => Number.isFinite(v));
1201
+ const unique = Array.from(new Set(filtered));
1202
+ unique.sort((a, b) => a - b);
1203
+ if (unique.length < 2) {
1204
+ throw new InvalidParameterError(
1205
+ "levels must contain at least two finite values",
1206
+ "levels",
1207
+ levels
1208
+ );
1209
+ }
1210
+ return unique;
1211
+ }
1212
+ const numLevels = typeof levels === "number" ? levels : 10;
1213
+ if (!Number.isFinite(numLevels) || Math.trunc(numLevels) !== numLevels || numLevels <= 0) {
1214
+ throw new InvalidParameterError(
1215
+ `levels must be a positive integer; received ${numLevels}`,
1216
+ "levels",
1217
+ numLevels
1218
+ );
1219
+ }
1220
+ if (min === max) {
1221
+ return [min, max];
1222
+ }
1223
+ const step = (max - min) / numLevels;
1224
+ const boundaries = new Array(numLevels + 1);
1225
+ for (let i = 0; i <= numLevels; i++) {
1226
+ boundaries[i] = min + i * step;
1227
+ }
1228
+ if (boundaries.length < 2) {
1229
+ return [min, max];
1230
+ }
1231
+ return boundaries;
1232
+ }
1233
+ function resolveBandColors(bandCount, options) {
1234
+ const colors = [];
1235
+ if (options.colors && options.colors.length > 0) {
1236
+ for (let i = 0; i < bandCount; i++) {
1237
+ const color = options.colors[i % options.colors.length];
1238
+ colors.push(normalizeColor(color, DEFAULT_COLORS2[i % DEFAULT_COLORS2.length] ?? "#1f77b4"));
1239
+ }
1240
+ } else if (options.color) {
1241
+ const normalized = normalizeColor(options.color, "#1f77b4");
1242
+ for (let i = 0; i < bandCount; i++) colors.push(normalized);
1243
+ } else if (options.colormap) {
1244
+ if (!["viridis", "plasma", "inferno", "magma", "grayscale"].includes(options.colormap)) {
1245
+ throw new InvalidParameterError(
1246
+ `colormap must be one of viridis, plasma, inferno, magma, grayscale; received ${options.colormap}`,
1247
+ "colormap",
1248
+ options.colormap
1249
+ );
1250
+ }
1251
+ const denom = Math.max(1, bandCount - 1);
1252
+ for (let i = 0; i < bandCount; i++) {
1253
+ const t = i / denom;
1254
+ const [r, g, b] = applyColormap(t, options.colormap);
1255
+ colors.push(`rgb(${r},${g},${b})`);
1256
+ }
1257
+ } else {
1258
+ const colormap = "viridis";
1259
+ const denom = Math.max(1, bandCount - 1);
1260
+ for (let i = 0; i < bandCount; i++) {
1261
+ const t = i / denom;
1262
+ const [r, g, b] = applyColormap(t, colormap);
1263
+ colors.push(`rgb(${r},${g},${b})`);
1264
+ }
1265
+ }
1266
+ const rgba = colors.map((c) => parseHexColorToRGBA(c));
1267
+ return { colors, rgba };
1268
+ }
1269
+ var ContourF2D = class {
1270
+ kind = "contourf";
1271
+ rows;
1272
+ cols;
1273
+ xCoords;
1274
+ yCoords;
1275
+ levels;
1276
+ bandColors;
1277
+ bandRGBA;
1278
+ triangles;
1279
+ xmin;
1280
+ xmax;
1281
+ ymin;
1282
+ ymax;
1283
+ label;
1284
+ constructor(grid, options = {}) {
1285
+ this.rows = grid.rows;
1286
+ this.cols = grid.cols;
1287
+ this.xCoords = grid.xCoords;
1288
+ this.yCoords = grid.yCoords;
1289
+ if (options.vmin !== void 0 && !Number.isFinite(options.vmin)) {
1290
+ throw new InvalidParameterError(
1291
+ `vmin must be finite; received ${options.vmin}`,
1292
+ "vmin",
1293
+ options.vmin
1294
+ );
1295
+ }
1296
+ if (options.vmax !== void 0 && !Number.isFinite(options.vmax)) {
1297
+ throw new InvalidParameterError(
1298
+ `vmax must be finite; received ${options.vmax}`,
1299
+ "vmax",
1300
+ options.vmax
1301
+ );
1302
+ }
1303
+ const min = options.vmin ?? grid.dataMin;
1304
+ const max = options.vmax ?? grid.dataMax;
1305
+ if (Number.isFinite(min) && Number.isFinite(max) && min > max) {
1306
+ throw new InvalidParameterError(
1307
+ `vmin must be <= vmax; received vmin=${min} vmax=${max}`,
1308
+ "vmin/vmax",
1309
+ { vmin: min, vmax: max }
1310
+ );
1311
+ }
1312
+ this.levels = resolveFillLevels(min, max, options.levels);
1313
+ const bandCount = this.levels.length - 1;
1314
+ if (bandCount <= 0) {
1315
+ throw new InvalidParameterError(
1316
+ "levels must define at least one fill band",
1317
+ "levels",
1318
+ this.levels
1319
+ );
1320
+ }
1321
+ const { colors, rgba } = resolveBandColors(bandCount, options);
1322
+ this.bandColors = colors;
1323
+ this.bandRGBA = rgba;
1324
+ this.label = normalizeLegendLabel(options.label);
1325
+ let xMin = Number.POSITIVE_INFINITY;
1326
+ let xMax = Number.NEGATIVE_INFINITY;
1327
+ let yMin = Number.POSITIVE_INFINITY;
1328
+ let yMax = Number.NEGATIVE_INFINITY;
1329
+ for (let i = 0; i < this.xCoords.length; i++) {
1330
+ const v = this.xCoords[i] ?? 0;
1331
+ if (v < xMin) xMin = v;
1332
+ if (v > xMax) xMax = v;
1333
+ }
1334
+ for (let i = 0; i < this.yCoords.length; i++) {
1335
+ const v = this.yCoords[i] ?? 0;
1336
+ if (v < yMin) yMin = v;
1337
+ if (v > yMax) yMax = v;
1338
+ }
1339
+ this.xmin = xMin;
1340
+ this.xmax = xMax;
1341
+ this.ymin = yMin;
1342
+ this.ymax = yMax;
1343
+ const cellRows = Math.max(0, this.rows - 1);
1344
+ const cellCols = Math.max(0, this.cols - 1);
1345
+ const triangles = [];
1346
+ if (cellRows > 0 && cellCols > 0) {
1347
+ const data = grid.data;
1348
+ for (let i = 0; i < cellRows; i++) {
1349
+ const rowOffset = i * this.cols;
1350
+ const rowOffsetNext = (i + 1) * this.cols;
1351
+ const y0 = this.yCoords[i] ?? 0;
1352
+ const y1 = this.yCoords[i + 1] ?? 0;
1353
+ for (let j = 0; j < cellCols; j++) {
1354
+ const x0 = this.xCoords[j] ?? 0;
1355
+ const x1 = this.xCoords[j + 1] ?? 0;
1356
+ const v00 = data[rowOffset + j] ?? 0;
1357
+ const v10 = data[rowOffset + j + 1] ?? 0;
1358
+ const v11 = data[rowOffsetNext + j + 1] ?? 0;
1359
+ const v01 = data[rowOffsetNext + j] ?? 0;
1360
+ if (!isFiniteNumber(v00) || !isFiniteNumber(v10) || !isFiniteNumber(v11) || !isFiniteNumber(v01)) {
1361
+ continue;
1362
+ }
1363
+ const t0 = { x: x0, y: y0, v: v00 };
1364
+ const t1 = { x: x1, y: y0, v: v10 };
1365
+ const t2 = { x: x1, y: y1, v: v11 };
1366
+ const t3 = { x: x0, y: y1, v: v01 };
1367
+ addBandTriangles([t0, t1, t2], this.levels, triangles);
1368
+ addBandTriangles([t0, t2, t3], this.levels, triangles);
1369
+ }
1370
+ }
1371
+ }
1372
+ this.triangles = triangles;
1373
+ }
1374
+ getDataRange() {
1375
+ if (!Number.isFinite(this.xmin) || !Number.isFinite(this.xmax)) return null;
1376
+ if (!Number.isFinite(this.ymin) || !Number.isFinite(this.ymax)) return null;
1377
+ return {
1378
+ xmin: this.xmin,
1379
+ xmax: this.xmax,
1380
+ ymin: this.ymin,
1381
+ ymax: this.ymax
1382
+ };
1383
+ }
1384
+ drawSVG(ctx) {
1385
+ if (this.triangles.length === 0) return;
1386
+ for (const tri of this.triangles) {
1387
+ const color = this.bandColors[tri.band] ?? "#1f77b4";
1388
+ const x1 = ctx.transform.xToPx(tri.x1);
1389
+ const y1 = ctx.transform.yToPx(tri.y1);
1390
+ const x2 = ctx.transform.xToPx(tri.x2);
1391
+ const y2 = ctx.transform.yToPx(tri.y2);
1392
+ const x3 = ctx.transform.xToPx(tri.x3);
1393
+ const y3 = ctx.transform.yToPx(tri.y3);
1394
+ ctx.push(
1395
+ `<path d="M ${x1.toFixed(2)} ${y1.toFixed(2)} L ${x2.toFixed(2)} ${y2.toFixed(
1396
+ 2
1397
+ )} L ${x3.toFixed(2)} ${y3.toFixed(2)} Z" fill="${escapeXml(color)}" />`
1398
+ );
1399
+ }
1400
+ }
1401
+ drawRaster(ctx) {
1402
+ if (this.triangles.length === 0) return;
1403
+ for (const tri of this.triangles) {
1404
+ const rgba = this.bandRGBA[tri.band] ?? { r: 0, g: 0, b: 0, a: 255 };
1405
+ ctx.canvas.fillTriangleRGBA(
1406
+ Math.round(ctx.transform.xToPx(tri.x1)),
1407
+ Math.round(ctx.transform.yToPx(tri.y1)),
1408
+ Math.round(ctx.transform.xToPx(tri.x2)),
1409
+ Math.round(ctx.transform.yToPx(tri.y2)),
1410
+ Math.round(ctx.transform.xToPx(tri.x3)),
1411
+ Math.round(ctx.transform.yToPx(tri.y3)),
1412
+ rgba.r,
1413
+ rgba.g,
1414
+ rgba.b,
1415
+ rgba.a
1416
+ );
1417
+ }
1418
+ }
1419
+ getLegendEntries() {
1420
+ const color = this.bandColors[Math.floor(this.bandColors.length / 2)] ?? "#1f77b4";
1421
+ const entry = buildLegendEntry(this.label, { color, shape: "box" });
1422
+ return entry ? [entry] : null;
1423
+ }
1424
+ };
1425
+
1426
+ // src/plot/plots/Heatmap2D.ts
1427
+ var Heatmap2D = class {
1428
+ kind = "heatmap";
1429
+ data;
1430
+ rows;
1431
+ cols;
1432
+ vmin;
1433
+ vmax;
1434
+ colormap;
1435
+ xMin;
1436
+ xMax;
1437
+ yMin;
1438
+ yMax;
1439
+ label;
1440
+ constructor(data, rows, cols, options) {
1441
+ this.data = data;
1442
+ this.rows = rows;
1443
+ this.cols = cols;
1444
+ const colormap = options.colormap ?? "viridis";
1445
+ if (!["viridis", "plasma", "inferno", "magma", "grayscale"].includes(colormap)) {
1446
+ throw new InvalidParameterError(
1447
+ `colormap must be one of viridis, plasma, inferno, magma, grayscale; received ${colormap}`,
1448
+ "colormap",
1449
+ colormap
1450
+ );
1451
+ }
1452
+ this.colormap = colormap;
1453
+ this.label = normalizeLegendLabel(options.label);
1454
+ const extent = options.extent;
1455
+ if (extent) {
1456
+ const { xmin, xmax, ymin, ymax } = extent;
1457
+ if (!Number.isFinite(xmin) || !Number.isFinite(xmax) || !Number.isFinite(ymin) || !Number.isFinite(ymax)) {
1458
+ throw new InvalidParameterError("extent values must be finite", "extent", extent);
1459
+ }
1460
+ if (xmax <= xmin || ymax <= ymin) {
1461
+ throw new InvalidParameterError("extent ranges must be positive", "extent", extent);
1462
+ }
1463
+ this.xMin = xmin;
1464
+ this.xMax = xmax;
1465
+ this.yMin = ymin;
1466
+ this.yMax = ymax;
1467
+ } else {
1468
+ this.xMin = 0;
1469
+ this.xMax = cols;
1470
+ this.yMin = 0;
1471
+ this.yMax = rows;
1472
+ }
1473
+ let min = Number.POSITIVE_INFINITY;
1474
+ let max = Number.NEGATIVE_INFINITY;
1475
+ for (let i = 0; i < data.length; i++) {
1476
+ const v = data[i] ?? 0;
1477
+ if (isFiniteNumber(v)) {
1478
+ min = Math.min(min, v);
1479
+ max = Math.max(max, v);
1480
+ }
1481
+ }
1482
+ if (!Number.isFinite(min) || !Number.isFinite(max)) {
1483
+ throw new InvalidParameterError(
1484
+ "heatmap data must contain at least one finite value",
1485
+ "data",
1486
+ data
1487
+ );
1488
+ }
1489
+ if (options.vmin !== void 0 && !Number.isFinite(options.vmin)) {
1490
+ throw new InvalidParameterError(
1491
+ `vmin must be finite; received ${options.vmin}`,
1492
+ "vmin",
1493
+ options.vmin
1494
+ );
1495
+ }
1496
+ if (options.vmax !== void 0 && !Number.isFinite(options.vmax)) {
1497
+ throw new InvalidParameterError(
1498
+ `vmax must be finite; received ${options.vmax}`,
1499
+ "vmax",
1500
+ options.vmax
1501
+ );
1502
+ }
1503
+ const vmin = options.vmin ?? min;
1504
+ const vmax = options.vmax ?? max;
1505
+ if (Number.isFinite(vmin) && Number.isFinite(vmax) && vmin > vmax) {
1506
+ throw new InvalidParameterError(
1507
+ `vmin must be <= vmax; received vmin=${vmin} vmax=${vmax}`,
1508
+ "vmin/vmax",
1509
+ { vmin, vmax }
1510
+ );
1511
+ }
1512
+ this.vmin = Number.isFinite(vmin) ? vmin : 0;
1513
+ this.vmax = Number.isFinite(vmax) ? vmax : 1;
1514
+ }
1515
+ getDataRange() {
1516
+ return { xmin: this.xMin, xmax: this.xMax, ymin: this.yMin, ymax: this.yMax };
1517
+ }
1518
+ drawSVG(ctx) {
1519
+ if (this.rows <= 0 || this.cols <= 0) return;
1520
+ const range = this.vmax - this.vmin;
1521
+ const xSpan = this.xMax - this.xMin;
1522
+ const ySpan = this.yMax - this.yMin;
1523
+ for (let i = 0; i < this.rows; i++) {
1524
+ for (let j = 0; j < this.cols; j++) {
1525
+ const v = this.data[i * this.cols + j] ?? 0;
1526
+ if (!isFiniteNumber(v)) continue;
1527
+ const normalized = range !== 0 ? (v - this.vmin) / range : 0;
1528
+ const intensity = Math.max(0, Math.min(1, normalized));
1529
+ const [r, g, b] = applyColormap(intensity, this.colormap);
1530
+ const color = `rgb(${r},${g},${b})`;
1531
+ const x0 = ctx.transform.xToPx(this.xMin + j / this.cols * xSpan);
1532
+ const x1 = ctx.transform.xToPx(this.xMin + (j + 1) / this.cols * xSpan);
1533
+ const y0 = ctx.transform.yToPx(this.yMin + i / this.rows * ySpan);
1534
+ const y1 = ctx.transform.yToPx(this.yMin + (i + 1) / this.rows * ySpan);
1535
+ const w = Math.abs(x1 - x0);
1536
+ const h = Math.abs(y1 - y0);
1537
+ const rx = Math.min(x0, x1);
1538
+ const ry = Math.min(y0, y1);
1539
+ ctx.push(
1540
+ `<rect x="${rx.toFixed(2)}" y="${ry.toFixed(2)}" width="${w.toFixed(2)}" height="${h.toFixed(2)}" fill="${color}" />`
1541
+ );
1542
+ }
1543
+ }
1544
+ }
1545
+ drawRaster(ctx) {
1546
+ if (this.rows <= 0 || this.cols <= 0) return;
1547
+ const range = this.vmax - this.vmin;
1548
+ const xSpan = this.xMax - this.xMin;
1549
+ const ySpan = this.yMax - this.yMin;
1550
+ for (let i = 0; i < this.rows; i++) {
1551
+ for (let j = 0; j < this.cols; j++) {
1552
+ const v = this.data[i * this.cols + j] ?? 0;
1553
+ if (!isFiniteNumber(v)) continue;
1554
+ const normalized = range !== 0 ? (v - this.vmin) / range : 0;
1555
+ const intensity = Math.max(0, Math.min(1, normalized));
1556
+ const [r, g, b] = applyColormap(intensity, this.colormap);
1557
+ const x0 = Math.round(ctx.transform.xToPx(this.xMin + j / this.cols * xSpan));
1558
+ const x1 = Math.round(ctx.transform.xToPx(this.xMin + (j + 1) / this.cols * xSpan));
1559
+ const y0 = Math.round(ctx.transform.yToPx(this.yMin + i / this.rows * ySpan));
1560
+ const y1 = Math.round(ctx.transform.yToPx(this.yMin + (i + 1) / this.rows * ySpan));
1561
+ const w = Math.abs(x1 - x0);
1562
+ const h = Math.abs(y1 - y0);
1563
+ const rx = Math.min(x0, x1);
1564
+ const ry = Math.min(y0, y1);
1565
+ ctx.canvas.fillRectRGBA(rx, ry, w, h, r, g, b, 255);
1566
+ }
1567
+ }
1568
+ }
1569
+ getLegendEntries() {
1570
+ const entry = buildLegendEntry(this.label, {
1571
+ color: normalizeColor(`rgb(${applyColormap(0.5, this.colormap).join(",")})`, "#1f77b4"),
1572
+ shape: "box"
1573
+ });
1574
+ return entry ? [entry] : null;
1575
+ }
1576
+ };
1577
+
1578
+ // src/plot/plots/Histogram.ts
1579
+ var Histogram = class {
1580
+ kind = "histogram";
1581
+ bins;
1582
+ counts;
1583
+ binWidth;
1584
+ color;
1585
+ edgecolor;
1586
+ label;
1587
+ constructor(data, numBins, options) {
1588
+ if (!Number.isFinite(numBins) || Math.trunc(numBins) !== numBins || numBins <= 0) {
1589
+ throw new InvalidParameterError(
1590
+ `bins must be a positive integer; received ${numBins}`,
1591
+ "bins",
1592
+ numBins
1593
+ );
1594
+ }
1595
+ this.color = normalizeColor(options.color, "#d62728");
1596
+ this.edgecolor = normalizeColor(options.edgecolor, "#000000");
1597
+ this.label = normalizeLegendLabel(options.label);
1598
+ let min = Number.POSITIVE_INFINITY;
1599
+ let max = Number.NEGATIVE_INFINITY;
1600
+ let hasFinite = false;
1601
+ let hasInfinity = false;
1602
+ for (let i = 0; i < data.length; i++) {
1603
+ const v = data[i] ?? 0;
1604
+ if (isFiniteNumber(v)) {
1605
+ hasFinite = true;
1606
+ min = Math.min(min, v);
1607
+ max = Math.max(max, v);
1608
+ } else if (v === Infinity || v === -Infinity) {
1609
+ hasInfinity = true;
1610
+ }
1611
+ }
1612
+ if (!hasFinite) {
1613
+ if (hasInfinity) {
1614
+ throw new InvalidParameterError(
1615
+ "histogram data must contain at least one finite value",
1616
+ "data",
1617
+ data
1618
+ );
1619
+ }
1620
+ this.bins = new Float64Array(0);
1621
+ this.counts = new Float64Array(0);
1622
+ this.binWidth = 1;
1623
+ return;
1624
+ }
1625
+ const span = max - min;
1626
+ const binWidth = span > 0 ? span / numBins : 1;
1627
+ const binStart = span > 0 ? min : min - numBins * binWidth / 2;
1628
+ this.bins = new Float64Array(numBins);
1629
+ this.counts = new Float64Array(numBins);
1630
+ this.binWidth = binWidth;
1631
+ for (let i = 0; i < numBins; i++) {
1632
+ this.bins[i] = binStart + i * binWidth;
1633
+ this.counts[i] = 0;
1634
+ }
1635
+ if (span === 0) {
1636
+ let finiteCount = 0;
1637
+ for (let i = 0; i < data.length; i++) {
1638
+ const v = data[i] ?? 0;
1639
+ if (isFiniteNumber(v)) finiteCount++;
1640
+ }
1641
+ const mid = Math.floor(numBins / 2);
1642
+ this.counts[mid] = finiteCount;
1643
+ return;
1644
+ }
1645
+ for (let i = 0; i < data.length; i++) {
1646
+ const v = data[i] ?? 0;
1647
+ if (!isFiniteNumber(v)) continue;
1648
+ const rawIdx = Math.floor((v - min) / binWidth);
1649
+ const binIdx = Math.min(Math.max(0, rawIdx), numBins - 1);
1650
+ this.counts[binIdx] = (this.counts[binIdx] ?? 0) + 1;
1651
+ }
1652
+ }
1653
+ getDataRange() {
1654
+ if (this.bins.length === 0) return null;
1655
+ const xmin = this.bins[0] ?? 0;
1656
+ const xmax = (this.bins[this.bins.length - 1] ?? 0) + this.binWidth;
1657
+ let ymax = 0;
1658
+ for (let i = 0; i < this.counts.length; i++) {
1659
+ ymax = Math.max(ymax, this.counts[i] ?? 0);
1660
+ }
1661
+ return { xmin, xmax, ymin: 0, ymax };
1662
+ }
1663
+ drawSVG(ctx) {
1664
+ if (this.bins.length === 0) return;
1665
+ const binWidth = this.binWidth;
1666
+ for (let i = 0; i < this.bins.length; i++) {
1667
+ const x0 = this.bins[i] ?? 0;
1668
+ const count = this.counts[i] ?? 0;
1669
+ const px0 = ctx.transform.xToPx(x0);
1670
+ const px1 = ctx.transform.xToPx(x0 + binWidth);
1671
+ const py0 = ctx.transform.yToPx(0);
1672
+ const py1 = ctx.transform.yToPx(count);
1673
+ const w = Math.abs(px1 - px0);
1674
+ const h = Math.abs(py1 - py0);
1675
+ const rx = Math.min(px0, px1);
1676
+ const ry = Math.min(py0, py1);
1677
+ ctx.push(
1678
+ `<rect x="${rx.toFixed(2)}" y="${ry.toFixed(2)}" width="${w.toFixed(2)}" height="${h.toFixed(2)}" fill="${escapeXml(this.color)}" stroke="${escapeXml(this.edgecolor)}" />`
1679
+ );
1680
+ }
1681
+ }
1682
+ drawRaster(ctx) {
1683
+ if (this.bins.length === 0) return;
1684
+ const rgba = parseHexColorToRGBA(this.color);
1685
+ const edge = parseHexColorToRGBA(this.edgecolor);
1686
+ const binWidth = this.binWidth;
1687
+ for (let i = 0; i < this.bins.length; i++) {
1688
+ const x0 = this.bins[i] ?? 0;
1689
+ const count = this.counts[i] ?? 0;
1690
+ const px0 = Math.round(ctx.transform.xToPx(x0));
1691
+ const px1 = Math.round(ctx.transform.xToPx(x0 + binWidth));
1692
+ const py0 = Math.round(ctx.transform.yToPx(0));
1693
+ const py1 = Math.round(ctx.transform.yToPx(count));
1694
+ const w = Math.abs(px1 - px0);
1695
+ const h = Math.abs(py1 - py0);
1696
+ const rx = Math.min(px0, px1);
1697
+ const ry = Math.min(py0, py1);
1698
+ ctx.canvas.fillRectRGBA(rx, ry, w, h, rgba.r, rgba.g, rgba.b, rgba.a);
1699
+ const r_e = edge.r;
1700
+ const g_e = edge.g;
1701
+ const b_e = edge.b;
1702
+ const a_e = edge.a;
1703
+ ctx.canvas.drawLineRGBA(rx, ry, rx + w, ry, r_e, g_e, b_e, a_e);
1704
+ ctx.canvas.drawLineRGBA(rx + w, ry, rx + w, ry + h, r_e, g_e, b_e, a_e);
1705
+ ctx.canvas.drawLineRGBA(rx + w, ry + h, rx, ry + h, r_e, g_e, b_e, a_e);
1706
+ ctx.canvas.drawLineRGBA(rx, ry + h, rx, ry, r_e, g_e, b_e, a_e);
1707
+ }
1708
+ }
1709
+ getLegendEntries() {
1710
+ const entry = buildLegendEntry(this.label, { color: this.color, shape: "box" });
1711
+ return entry ? [entry] : null;
1712
+ }
1713
+ };
1714
+
1715
+ // src/plot/plots/HorizontalBar2D.ts
1716
+ var HorizontalBar2D = class {
1717
+ kind = "barh";
1718
+ y;
1719
+ width;
1720
+ color;
1721
+ edgecolor;
1722
+ barHeight;
1723
+ label;
1724
+ constructor(y, width, options) {
1725
+ if (y.length !== width.length) throw new ShapeError("y and width must have the same length");
1726
+ this.y = y;
1727
+ this.width = width;
1728
+ this.color = normalizeColor(options.color, "#9467bd");
1729
+ this.edgecolor = normalizeColor(options.edgecolor, "#000000");
1730
+ this.barHeight = 0.8;
1731
+ this.label = normalizeLegendLabel(options.label);
1732
+ }
1733
+ getDataRange() {
1734
+ let ymin = Number.POSITIVE_INFINITY;
1735
+ let ymax = Number.NEGATIVE_INFINITY;
1736
+ let xmin = 0;
1737
+ let xmax = 0;
1738
+ let sawValue = false;
1739
+ for (let i = 0; i < this.y.length; i++) {
1740
+ const yi = this.y[i] ?? 0;
1741
+ const wi = this.width[i] ?? 0;
1742
+ if (!isFiniteNumber(yi) || !isFiniteNumber(wi)) continue;
1743
+ sawValue = true;
1744
+ ymin = Math.min(ymin, yi - this.barHeight / 2);
1745
+ ymax = Math.max(ymax, yi + this.barHeight / 2);
1746
+ xmin = Math.min(xmin, wi);
1747
+ xmax = Math.max(xmax, wi);
1748
+ }
1749
+ if (!sawValue || !Number.isFinite(ymin) || !Number.isFinite(ymax)) {
1750
+ return null;
1751
+ }
1752
+ return { xmin, xmax, ymin, ymax };
1753
+ }
1754
+ drawSVG(ctx) {
1755
+ for (let i = 0; i < this.y.length; i++) {
1756
+ const yi = this.y[i] ?? 0;
1757
+ const wi = this.width[i] ?? 0;
1758
+ if (!isFiniteNumber(yi) || !isFiniteNumber(wi)) continue;
1759
+ const x0 = ctx.transform.xToPx(0);
1760
+ const x1 = ctx.transform.xToPx(wi);
1761
+ const y0 = ctx.transform.yToPx(yi - this.barHeight / 2);
1762
+ const y1 = ctx.transform.yToPx(yi + this.barHeight / 2);
1763
+ const w = Math.abs(x1 - x0);
1764
+ const h = Math.abs(y1 - y0);
1765
+ const rx = Math.min(x0, x1);
1766
+ const ry = Math.min(y0, y1);
1767
+ ctx.push(
1768
+ `<rect x="${rx.toFixed(2)}" y="${ry.toFixed(2)}" width="${w.toFixed(2)}" height="${h.toFixed(2)}" fill="${escapeXml(this.color)}" stroke="${escapeXml(this.edgecolor)}" />`
1769
+ );
1770
+ }
1771
+ }
1772
+ drawRaster(ctx) {
1773
+ const rgba = parseHexColorToRGBA(this.color);
1774
+ const edge = parseHexColorToRGBA(this.edgecolor);
1775
+ for (let i = 0; i < this.y.length; i++) {
1776
+ const yi = this.y[i] ?? 0;
1777
+ const wi = this.width[i] ?? 0;
1778
+ if (!isFiniteNumber(yi) || !isFiniteNumber(wi)) continue;
1779
+ const x0 = Math.round(ctx.transform.xToPx(0));
1780
+ const x1 = Math.round(ctx.transform.xToPx(wi));
1781
+ const y0 = Math.round(ctx.transform.yToPx(yi - this.barHeight / 2));
1782
+ const y1 = Math.round(ctx.transform.yToPx(yi + this.barHeight / 2));
1783
+ const w = Math.abs(x1 - x0);
1784
+ const h = Math.abs(y1 - y0);
1785
+ const rx = Math.min(x0, x1);
1786
+ const ry = Math.min(y0, y1);
1787
+ ctx.canvas.fillRectRGBA(rx, ry, w, h, rgba.r, rgba.g, rgba.b, rgba.a);
1788
+ const r_e = edge.r;
1789
+ const g_e = edge.g;
1790
+ const b_e = edge.b;
1791
+ const a_e = edge.a;
1792
+ ctx.canvas.drawLineRGBA(rx, ry, rx + w, ry, r_e, g_e, b_e, a_e);
1793
+ ctx.canvas.drawLineRGBA(rx + w, ry, rx + w, ry + h, r_e, g_e, b_e, a_e);
1794
+ ctx.canvas.drawLineRGBA(rx + w, ry + h, rx, ry + h, r_e, g_e, b_e, a_e);
1795
+ ctx.canvas.drawLineRGBA(rx, ry + h, rx, ry, r_e, g_e, b_e, a_e);
1796
+ }
1797
+ }
1798
+ getLegendEntries() {
1799
+ const entry = buildLegendEntry(this.label, { color: this.color, shape: "box" });
1800
+ return entry ? [entry] : null;
1801
+ }
1802
+ };
1803
+
1804
+ // src/plot/plots/Line2D.ts
1805
+ var Line2D = class {
1806
+ kind = "line";
1807
+ x;
1808
+ y;
1809
+ color;
1810
+ linewidth;
1811
+ label;
1812
+ constructor(x, y, options) {
1813
+ if (x.length !== y.length) throw new ShapeError("x and y must have the same length");
1814
+ this.x = x;
1815
+ this.y = y;
1816
+ this.color = normalizeColor(options.color, "#1f77b4");
1817
+ const lw = options.linewidth ?? 2;
1818
+ if (!Number.isFinite(lw) || lw <= 0) {
1819
+ throw new InvalidParameterError(
1820
+ `linewidth must be a positive number; received ${lw}`,
1821
+ "linewidth",
1822
+ lw
1823
+ );
1824
+ }
1825
+ this.linewidth = lw;
1826
+ this.label = normalizeLegendLabel(options.label);
1827
+ }
1828
+ getDataRange() {
1829
+ let xmin = Number.POSITIVE_INFINITY;
1830
+ let xmax = Number.NEGATIVE_INFINITY;
1831
+ let ymin = Number.POSITIVE_INFINITY;
1832
+ let ymax = Number.NEGATIVE_INFINITY;
1833
+ for (let i = 0; i < this.x.length; i++) {
1834
+ const xi = this.x[i] ?? 0;
1835
+ const yi = this.y[i] ?? 0;
1836
+ if (!isFiniteNumber(xi) || !isFiniteNumber(yi)) continue;
1837
+ xmin = Math.min(xmin, xi);
1838
+ xmax = Math.max(xmax, xi);
1839
+ ymin = Math.min(ymin, yi);
1840
+ ymax = Math.max(ymax, yi);
1841
+ }
1842
+ if (!Number.isFinite(xmin) || !Number.isFinite(xmax) || !Number.isFinite(ymin) || !Number.isFinite(ymax)) {
1843
+ return null;
1844
+ }
1845
+ return { xmin, xmax, ymin, ymax };
1846
+ }
1847
+ drawSVG(ctx) {
1848
+ const pts = [];
1849
+ for (let i = 0; i < this.x.length; i++) {
1850
+ const xi = this.x[i] ?? 0;
1851
+ const yi = this.y[i] ?? 0;
1852
+ if (!isFiniteNumber(xi) || !isFiniteNumber(yi)) continue;
1853
+ pts.push(`${ctx.transform.xToPx(xi).toFixed(2)},${ctx.transform.yToPx(yi).toFixed(2)}`);
1854
+ }
1855
+ ctx.push(
1856
+ `<polyline fill="none" stroke="${escapeXml(this.color)}" stroke-width="${this.linewidth}" points="${pts.join(" ")}" />`
1857
+ );
1858
+ }
1859
+ drawRaster(ctx) {
1860
+ const rgba = parseHexColorToRGBA(this.color);
1861
+ for (let i = 1; i < this.x.length; i++) {
1862
+ const x0 = this.x[i - 1] ?? 0;
1863
+ const y0 = this.y[i - 1] ?? 0;
1864
+ const x1 = this.x[i] ?? 0;
1865
+ const y1 = this.y[i] ?? 0;
1866
+ if (!isFiniteNumber(x0) || !isFiniteNumber(y0) || !isFiniteNumber(x1) || !isFiniteNumber(y1))
1867
+ continue;
1868
+ ctx.canvas.drawLineRGBA(
1869
+ Math.round(ctx.transform.xToPx(x0)),
1870
+ Math.round(ctx.transform.yToPx(y0)),
1871
+ Math.round(ctx.transform.xToPx(x1)),
1872
+ Math.round(ctx.transform.yToPx(y1)),
1873
+ rgba.r,
1874
+ rgba.g,
1875
+ rgba.b,
1876
+ rgba.a
1877
+ );
1878
+ }
1879
+ }
1880
+ getLegendEntries() {
1881
+ const entry = buildLegendEntry(this.label, {
1882
+ color: this.color,
1883
+ shape: "line",
1884
+ lineWidth: this.linewidth
1885
+ });
1886
+ return entry ? [entry] : null;
1887
+ }
1888
+ };
1889
+
1890
+ // src/plot/plots/Pie.ts
1891
+ var Pie = class {
1892
+ kind = "pie";
1893
+ centerX;
1894
+ centerY;
1895
+ radius;
1896
+ values;
1897
+ angles;
1898
+ colors;
1899
+ labels;
1900
+ rangeOverride;
1901
+ label;
1902
+ constructor(centerX, centerY, radius, values, labels, options = {}, rangeOverride) {
1903
+ if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) {
1904
+ throw new InvalidParameterError("pie center must be finite", "center", { centerX, centerY });
1905
+ }
1906
+ if (!Number.isFinite(radius) || radius <= 0) {
1907
+ throw new InvalidParameterError(
1908
+ `pie radius must be positive; received ${radius}`,
1909
+ "radius",
1910
+ radius
1911
+ );
1912
+ }
1913
+ this.centerX = centerX;
1914
+ this.centerY = centerY;
1915
+ this.radius = radius;
1916
+ if (values.length === 0) {
1917
+ throw new InvalidParameterError("pie requires at least one value", "values", values.length);
1918
+ }
1919
+ if (labels && labels.length !== values.length) {
1920
+ throw new InvalidParameterError(
1921
+ `labels length (${labels.length}) must match values length (${values.length})`,
1922
+ "labels",
1923
+ labels
1924
+ );
1925
+ }
1926
+ this.labels = labels ?? [];
1927
+ this.label = normalizeLegendLabel(options.label);
1928
+ this.rangeOverride = rangeOverride;
1929
+ const validValues = [];
1930
+ for (let i = 0; i < values.length; i++) {
1931
+ const v = values[i] ?? 0;
1932
+ if (!isFiniteNumber(v)) {
1933
+ throw new InvalidParameterError("pie values must be finite", "values", v);
1934
+ }
1935
+ if (v < 0) {
1936
+ throw new InvalidParameterError("pie values must be non-negative", "values", v);
1937
+ }
1938
+ validValues.push(v);
1939
+ }
1940
+ const total = validValues.reduce((sum, val) => sum + val, 0);
1941
+ if (!Number.isFinite(total) || total <= 0) {
1942
+ throw new InvalidParameterError(
1943
+ "pie values must sum to a positive number",
1944
+ "values",
1945
+ validValues
1946
+ );
1947
+ }
1948
+ this.values = validValues;
1949
+ const angles = [0];
1950
+ let cumulative = 0;
1951
+ for (const value of validValues) {
1952
+ cumulative += value / total * 2 * Math.PI;
1953
+ angles.push(cumulative);
1954
+ }
1955
+ this.angles = angles;
1956
+ const defaultColors = [
1957
+ "#1f77b4",
1958
+ "#ff7f0e",
1959
+ "#2ca02c",
1960
+ "#d62728",
1961
+ "#9467bd",
1962
+ "#8c564b",
1963
+ "#e377c2",
1964
+ "#7f7f7f",
1965
+ "#bcbd22",
1966
+ "#17becf"
1967
+ ];
1968
+ const colors = [];
1969
+ if (options.colors && options.colors.length > 0) {
1970
+ for (let i = 0; i < validValues.length; i++) {
1971
+ const colorOption = options.colors[i % options.colors.length];
1972
+ const colorToUse = colorOption;
1973
+ const defaultColor = defaultColors[i % defaultColors.length] ?? "#1f77b4";
1974
+ colors.push(normalizeColor(colorToUse, defaultColor));
1975
+ }
1976
+ } else {
1977
+ for (let i = 0; i < validValues.length; i++) {
1978
+ const defaultColor = defaultColors[i % defaultColors.length] ?? "#1f77b4";
1979
+ colors.push(normalizeColor(void 0, defaultColor));
1980
+ }
1981
+ }
1982
+ this.colors = colors;
1983
+ }
1984
+ getDataRange() {
1985
+ if (this.rangeOverride) return this.rangeOverride;
1986
+ return {
1987
+ xmin: this.centerX - this.radius,
1988
+ xmax: this.centerX + this.radius,
1989
+ ymin: this.centerY - this.radius,
1990
+ ymax: this.centerY + this.radius
1991
+ };
1992
+ }
1993
+ drawSVG(ctx) {
1994
+ const cx = ctx.transform.xToPx(this.centerX);
1995
+ const cy = ctx.transform.yToPx(this.centerY);
1996
+ const rx = Math.abs(ctx.transform.xToPx(this.centerX + this.radius) - cx);
1997
+ const ry = Math.abs(ctx.transform.yToPx(this.centerY + this.radius) - cy);
1998
+ const r = Math.min(rx, ry);
1999
+ for (let i = 0; i < this.values.length; i++) {
2000
+ const startAngle = this.angles[i] ?? 0;
2001
+ const endAngle = this.angles[i + 1] ?? 2 * Math.PI;
2002
+ const x1 = cx + r * Math.cos(startAngle);
2003
+ const y1 = cy - r * Math.sin(startAngle);
2004
+ const x2 = cx + r * Math.cos(endAngle);
2005
+ const y2 = cy - r * Math.sin(endAngle);
2006
+ const largeArcFlag = endAngle - startAngle > Math.PI ? 1 : 0;
2007
+ const pathData = [
2008
+ `M ${cx.toFixed(2)} ${cy.toFixed(2)}`,
2009
+ `L ${x1.toFixed(2)} ${y1.toFixed(2)}`,
2010
+ `A ${r.toFixed(2)} ${r.toFixed(2)} 0 ${largeArcFlag} 0 ${x2.toFixed(2)} ${y2.toFixed(2)}`,
2011
+ "Z"
2012
+ ].join(" ");
2013
+ const fillColor = this.colors[i] ?? "#1f77b4";
2014
+ ctx.push(
2015
+ `<path d="${pathData}" fill="${escapeXml(fillColor)}" stroke="#ffffff" stroke-width="1" />`
2016
+ );
2017
+ const label = this.labels[i];
2018
+ if (i < this.labels.length && label) {
2019
+ const midAngle = (startAngle + endAngle) / 2;
2020
+ const labelRadius = r * 0.7;
2021
+ const labelX = cx + labelRadius * Math.cos(midAngle);
2022
+ const labelY = cy - labelRadius * Math.sin(midAngle);
2023
+ ctx.push(
2024
+ `<text x="${labelX.toFixed(2)}" y="${labelY.toFixed(2)}" text-anchor="middle" font-size="12" fill="#ffffff">${escapeXml(label)}</text>`
2025
+ );
2026
+ }
2027
+ }
2028
+ }
2029
+ drawRaster(ctx) {
2030
+ const cx = Math.round(ctx.transform.xToPx(this.centerX));
2031
+ const cy = Math.round(ctx.transform.yToPx(this.centerY));
2032
+ const rx = Math.abs(ctx.transform.xToPx(this.centerX + this.radius) - cx);
2033
+ const ry = Math.abs(ctx.transform.yToPx(this.centerY + this.radius) - cy);
2034
+ const r = Math.round(Math.min(rx, ry));
2035
+ const edge = parseHexColorToRGBA("#ffffff");
2036
+ for (let i = 0; i < this.values.length; i++) {
2037
+ const startAngle = this.angles[i] ?? 0;
2038
+ const endAngle = this.angles[i + 1] ?? 2 * Math.PI;
2039
+ const fillColor = this.colors[i] ?? "#1f77b4";
2040
+ const rgba = parseHexColorToRGBA(fillColor);
2041
+ const arcLength = (endAngle - startAngle) * r;
2042
+ const numSegments = Math.max(5, Math.ceil(arcLength / 5));
2043
+ for (let j = 0; j < numSegments; j++) {
2044
+ const angle1 = startAngle + j / numSegments * (endAngle - startAngle);
2045
+ const angle2 = startAngle + (j + 1) / numSegments * (endAngle - startAngle);
2046
+ const x1 = cx + r * Math.cos(angle1);
2047
+ const y1 = cy - r * Math.sin(angle1);
2048
+ const x2 = cx + r * Math.cos(angle2);
2049
+ const y2 = cy - r * Math.sin(angle2);
2050
+ ctx.canvas.fillTriangleRGBA(cx, cy, x1, y1, x2, y2, rgba.r, rgba.g, rgba.b, rgba.a);
2051
+ }
2052
+ const edgeX = Math.round(cx + r * Math.cos(startAngle));
2053
+ const edgeY = Math.round(cy - r * Math.sin(startAngle));
2054
+ ctx.canvas.drawLineRGBA(cx, cy, edgeX, edgeY, edge.r, edge.g, edge.b, edge.a);
2055
+ const label = this.labels[i];
2056
+ if (i < this.labels.length && label) {
2057
+ const midAngle = (startAngle + endAngle) / 2;
2058
+ const labelRadius = r * 0.7;
2059
+ const labelX = cx + labelRadius * Math.cos(midAngle);
2060
+ const labelY = cy - labelRadius * Math.sin(midAngle);
2061
+ const textColor = parseHexColorToRGBA("#ffffff");
2062
+ ctx.canvas.drawTextRGBA(
2063
+ label,
2064
+ Math.round(labelX),
2065
+ Math.round(labelY),
2066
+ textColor.r,
2067
+ textColor.g,
2068
+ textColor.b,
2069
+ textColor.a,
2070
+ { fontSize: 12, align: "middle", baseline: "middle" }
2071
+ );
2072
+ }
2073
+ }
2074
+ }
2075
+ getLegendEntries() {
2076
+ const entries = [];
2077
+ if (this.labels.length > 0) {
2078
+ for (let i = 0; i < this.labels.length; i++) {
2079
+ const label = normalizeLegendLabel(this.labels[i]);
2080
+ if (!label) continue;
2081
+ const color = this.colors[i] ?? "#1f77b4";
2082
+ entries.push({ label, color, shape: "box" });
2083
+ }
2084
+ return entries.length > 0 ? entries : null;
2085
+ }
2086
+ if (!this.label) {
2087
+ return null;
2088
+ }
2089
+ const entry = {
2090
+ label: this.label,
2091
+ color: this.colors[0] ?? "#1f77b4",
2092
+ shape: "box"
2093
+ };
2094
+ return [entry];
2095
+ }
2096
+ };
2097
+
2098
+ // src/plot/plots/Scatter2D.ts
2099
+ var Scatter2D = class {
2100
+ kind = "scatter";
2101
+ x;
2102
+ y;
2103
+ color;
2104
+ size;
2105
+ label;
2106
+ constructor(x, y, options) {
2107
+ if (x.length !== y.length) throw new ShapeError("x and y must have the same length");
2108
+ this.x = x;
2109
+ this.y = y;
2110
+ this.color = normalizeColor(options.color, "#ff7f0e");
2111
+ const size = options.size ?? 5;
2112
+ if (!Number.isFinite(size) || size <= 0) {
2113
+ throw new InvalidParameterError(
2114
+ `size must be a positive number; received ${size}`,
2115
+ "size",
2116
+ size
2117
+ );
2118
+ }
2119
+ this.size = size;
2120
+ this.label = normalizeLegendLabel(options.label);
2121
+ }
2122
+ getDataRange() {
2123
+ let xmin = Number.POSITIVE_INFINITY;
2124
+ let xmax = Number.NEGATIVE_INFINITY;
2125
+ let ymin = Number.POSITIVE_INFINITY;
2126
+ let ymax = Number.NEGATIVE_INFINITY;
2127
+ for (let i = 0; i < this.x.length; i++) {
2128
+ const xi = this.x[i] ?? 0;
2129
+ const yi = this.y[i] ?? 0;
2130
+ if (!isFiniteNumber(xi) || !isFiniteNumber(yi)) continue;
2131
+ xmin = Math.min(xmin, xi);
2132
+ xmax = Math.max(xmax, xi);
2133
+ ymin = Math.min(ymin, yi);
2134
+ ymax = Math.max(ymax, yi);
2135
+ }
2136
+ if (!Number.isFinite(xmin) || !Number.isFinite(xmax) || !Number.isFinite(ymin) || !Number.isFinite(ymax)) {
2137
+ return null;
2138
+ }
2139
+ return { xmin, xmax, ymin, ymax };
2140
+ }
2141
+ drawSVG(ctx) {
2142
+ for (let i = 0; i < this.x.length; i++) {
2143
+ const xi = this.x[i] ?? 0;
2144
+ const yi = this.y[i] ?? 0;
2145
+ if (!isFiniteNumber(xi) || !isFiniteNumber(yi)) continue;
2146
+ const px = ctx.transform.xToPx(xi);
2147
+ const py = ctx.transform.yToPx(yi);
2148
+ ctx.push(
2149
+ `<circle cx="${px.toFixed(2)}" cy="${py.toFixed(2)}" r="${this.size}" fill="${escapeXml(this.color)}" />`
2150
+ );
2151
+ }
2152
+ }
2153
+ drawRaster(ctx) {
2154
+ const rgba = parseHexColorToRGBA(this.color);
2155
+ for (let i = 0; i < this.x.length; i++) {
2156
+ const xi = this.x[i] ?? 0;
2157
+ const yi = this.y[i] ?? 0;
2158
+ if (!isFiniteNumber(xi) || !isFiniteNumber(yi)) continue;
2159
+ const px = Math.round(ctx.transform.xToPx(xi));
2160
+ const py = Math.round(ctx.transform.yToPx(yi));
2161
+ ctx.canvas.drawCircleRGBA(px, py, this.size, rgba.r, rgba.g, rgba.b, rgba.a);
2162
+ }
2163
+ }
2164
+ getLegendEntries() {
2165
+ const entry = buildLegendEntry(this.label, {
2166
+ color: this.color,
2167
+ shape: "marker",
2168
+ markerSize: this.size
2169
+ });
2170
+ return entry ? [entry] : null;
2171
+ }
2172
+ };
2173
+
2174
+ // src/plot/plots/Violinplot.ts
2175
+ var Violinplot = class {
2176
+ kind = "violinplot";
2177
+ position;
2178
+ q1;
2179
+ median;
2180
+ q3;
2181
+ kdePoints;
2182
+ kdeValues;
2183
+ color;
2184
+ edgecolor;
2185
+ violinWidth;
2186
+ label;
2187
+ hasData;
2188
+ constructor(position, data, options) {
2189
+ this.position = position;
2190
+ this.color = normalizeColor(options.color, "#8c564b");
2191
+ this.edgecolor = normalizeColor(options.edgecolor, "#000000");
2192
+ this.violinWidth = 0.8;
2193
+ this.label = normalizeLegendLabel(options.label);
2194
+ const sorted = Array.from(data).filter(isFiniteNumber).sort((a, b) => a - b);
2195
+ const n = sorted.length;
2196
+ if (n === 0) {
2197
+ if (data.length > 0) {
2198
+ throw new InvalidParameterError(
2199
+ "violinplot data must contain at least one finite value",
2200
+ "data",
2201
+ data
2202
+ );
2203
+ }
2204
+ this.hasData = false;
2205
+ this.q1 = 0;
2206
+ this.median = 0;
2207
+ this.q3 = 0;
2208
+ this.kdeValues = [];
2209
+ this.kdePoints = [];
2210
+ return;
2211
+ }
2212
+ this.hasData = true;
2213
+ const { q1, median, q3 } = calculateQuartiles(sorted);
2214
+ this.q1 = q1;
2215
+ this.median = median;
2216
+ this.q3 = q3;
2217
+ const firstVal = sorted[0] ?? 0;
2218
+ const lastVal = sorted[sorted.length - 1] ?? 0;
2219
+ const dataRange = lastVal - firstVal;
2220
+ const padding = dataRange * 0.1;
2221
+ const min = firstVal - padding;
2222
+ const max = lastVal + padding;
2223
+ const numPoints = 100;
2224
+ const kdePoints = [];
2225
+ for (let i = 0; i < numPoints; i++) {
2226
+ kdePoints.push(min + i / (numPoints - 1) * (max - min));
2227
+ }
2228
+ this.kdeValues = kernelDensityEstimation(sorted, kdePoints, 0);
2229
+ this.kdePoints = kdePoints;
2230
+ }
2231
+ getDataRange() {
2232
+ if (!this.hasData) return null;
2233
+ if (this.kdePoints.length === 0) return null;
2234
+ const minY = this.kdePoints[0] ?? 0;
2235
+ const maxY = this.kdePoints[this.kdePoints.length - 1] ?? 0;
2236
+ return {
2237
+ xmin: this.position - this.violinWidth / 2,
2238
+ xmax: this.position + this.violinWidth / 2,
2239
+ ymin: minY,
2240
+ ymax: maxY
2241
+ };
2242
+ }
2243
+ drawSVG(ctx) {
2244
+ if (!this.hasData) return;
2245
+ const x = this.position;
2246
+ let maxKDE = 0;
2247
+ for (let i = 0; i < this.kdeValues.length; i++) {
2248
+ const v = this.kdeValues[i] ?? 0;
2249
+ if (v > maxKDE) maxKDE = v;
2250
+ }
2251
+ if (maxKDE === 0) return;
2252
+ const pathPoints = [];
2253
+ for (let i = 0; i < this.kdePoints.length; i++) {
2254
+ const y = this.kdePoints[i] ?? 0;
2255
+ const kde = this.kdeValues[i] ?? 0;
2256
+ const width = kde / maxKDE * this.violinWidth;
2257
+ const xLeft2 = x - width / 2;
2258
+ const px = ctx.transform.xToPx(xLeft2);
2259
+ const py = ctx.transform.yToPx(y);
2260
+ pathPoints.push(`${px.toFixed(2)},${py.toFixed(2)}`);
2261
+ }
2262
+ for (let i = this.kdePoints.length - 1; i >= 0; i--) {
2263
+ const y = this.kdePoints[i] ?? 0;
2264
+ const kde = this.kdeValues[i] ?? 0;
2265
+ const width = kde / maxKDE * this.violinWidth;
2266
+ const xRight2 = x + width / 2;
2267
+ const px = ctx.transform.xToPx(xRight2);
2268
+ const py = ctx.transform.yToPx(y);
2269
+ pathPoints.push(`${px.toFixed(2)},${py.toFixed(2)}`);
2270
+ }
2271
+ if (pathPoints.length > 0) {
2272
+ ctx.push(
2273
+ `<path d="M ${pathPoints.join(" L ")} Z" fill="${escapeXml(this.color)}" stroke="${escapeXml(this.edgecolor)}" stroke-width="1" />`
2274
+ );
2275
+ }
2276
+ const yq1 = ctx.transform.yToPx(this.q1);
2277
+ const ymed = ctx.transform.yToPx(this.median);
2278
+ const yq3 = ctx.transform.yToPx(this.q3);
2279
+ const indicatorWidth = this.violinWidth * 0.8;
2280
+ const xLeft = ctx.transform.xToPx(x - indicatorWidth / 2);
2281
+ const xRight = ctx.transform.xToPx(x + indicatorWidth / 2);
2282
+ ctx.push(
2283
+ `<line x1="${xLeft.toFixed(2)}" y1="${yq1.toFixed(2)}" x2="${xRight.toFixed(2)}" y2="${yq1.toFixed(2)}" stroke="${escapeXml(this.edgecolor)}" stroke-width="2" />`
2284
+ );
2285
+ ctx.push(
2286
+ `<line x1="${xLeft.toFixed(2)}" y1="${ymed.toFixed(2)}" x2="${xRight.toFixed(2)}" y2="${ymed.toFixed(2)}" stroke="${escapeXml(this.edgecolor)}" stroke-width="3" />`
2287
+ );
2288
+ ctx.push(
2289
+ `<line x1="${xLeft.toFixed(2)}" y1="${yq3.toFixed(2)}" x2="${xRight.toFixed(2)}" y2="${yq3.toFixed(2)}" stroke="${escapeXml(this.edgecolor)}" stroke-width="2" />`
2290
+ );
2291
+ }
2292
+ drawRaster(ctx) {
2293
+ if (!this.hasData) return;
2294
+ const rgba = parseHexColorToRGBA(this.color);
2295
+ const edge = parseHexColorToRGBA(this.edgecolor);
2296
+ const x = this.position;
2297
+ let maxKDE = 0;
2298
+ for (let i = 0; i < this.kdeValues.length; i++) {
2299
+ const v = this.kdeValues[i] ?? 0;
2300
+ if (v > maxKDE) maxKDE = v;
2301
+ }
2302
+ if (maxKDE === 0) return;
2303
+ for (let i = 0; i < this.kdePoints.length - 1; i++) {
2304
+ const y1 = this.kdePoints[i] ?? 0;
2305
+ const y2 = this.kdePoints[i + 1] ?? 0;
2306
+ const kde1 = this.kdeValues[i] ?? 0;
2307
+ const kde2 = this.kdeValues[i + 1] ?? 0;
2308
+ const width1 = kde1 / maxKDE * this.violinWidth;
2309
+ const width2 = kde2 / maxKDE * this.violinWidth;
2310
+ const xLeft1 = x - width1 / 2;
2311
+ const xRight1 = x + width1 / 2;
2312
+ const xLeft2 = x - width2 / 2;
2313
+ const xRight2 = x + width2 / 2;
2314
+ const pxLeft1 = Math.round(ctx.transform.xToPx(xLeft1));
2315
+ const pxRight1 = Math.round(ctx.transform.xToPx(xRight1));
2316
+ const pxLeft2 = Math.round(ctx.transform.xToPx(xLeft2));
2317
+ const pxRight2 = Math.round(ctx.transform.xToPx(xRight2));
2318
+ const py1 = Math.round(ctx.transform.yToPx(y1));
2319
+ const py2 = Math.round(ctx.transform.yToPx(y2));
2320
+ ctx.canvas.drawLineRGBA(pxLeft1, py1, pxRight1, py1, rgba.r, rgba.g, rgba.b, rgba.a);
2321
+ ctx.canvas.drawLineRGBA(pxRight1, py1, pxRight2, py2, rgba.r, rgba.g, rgba.b, rgba.a);
2322
+ ctx.canvas.drawLineRGBA(pxRight2, py2, pxLeft2, py2, rgba.r, rgba.g, rgba.b, rgba.a);
2323
+ ctx.canvas.drawLineRGBA(pxLeft2, py2, pxLeft1, py1, rgba.r, rgba.g, rgba.b, rgba.a);
2324
+ const minY = Math.min(py1, py2);
2325
+ const maxY = Math.max(py1, py2);
2326
+ if (py1 === py2) {
2327
+ const leftX = Math.min(pxLeft1, pxLeft2);
2328
+ const rightX = Math.max(pxRight1, pxRight2);
2329
+ ctx.canvas.drawLineRGBA(leftX, py1, rightX, py1, rgba.r, rgba.g, rgba.b, rgba.a);
2330
+ } else {
2331
+ for (let y = minY; y <= maxY; y++) {
2332
+ const t = (y - py1) / (py2 - py1);
2333
+ const leftX = pxLeft1 + t * (pxLeft2 - pxLeft1);
2334
+ const rightX = pxRight1 + t * (pxRight2 - pxRight1);
2335
+ ctx.canvas.drawLineRGBA(
2336
+ Math.round(leftX),
2337
+ y,
2338
+ Math.round(rightX),
2339
+ y,
2340
+ rgba.r,
2341
+ rgba.g,
2342
+ rgba.b,
2343
+ rgba.a
2344
+ );
2345
+ }
2346
+ }
2347
+ }
2348
+ const yq1 = Math.round(ctx.transform.yToPx(this.q1));
2349
+ const ymed = Math.round(ctx.transform.yToPx(this.median));
2350
+ const yq3 = Math.round(ctx.transform.yToPx(this.q3));
2351
+ const indicatorWidth = this.violinWidth * 0.8;
2352
+ const xLeft = Math.round(ctx.transform.xToPx(x - indicatorWidth / 2));
2353
+ const xRight = Math.round(ctx.transform.xToPx(x + indicatorWidth / 2));
2354
+ ctx.canvas.drawLineRGBA(xLeft, yq1, xRight, yq1, edge.r, edge.g, edge.b, edge.a);
2355
+ ctx.canvas.drawLineRGBA(xLeft, ymed, xRight, ymed, edge.r, edge.g, edge.b, edge.a);
2356
+ ctx.canvas.drawLineRGBA(xLeft, yq3, xRight, yq3, edge.r, edge.g, edge.b, edge.a);
2357
+ }
2358
+ getLegendEntries() {
2359
+ const entry = buildLegendEntry(this.label, {
2360
+ color: this.color,
2361
+ shape: "box"
2362
+ });
2363
+ return entry ? [entry] : null;
2364
+ }
2365
+ };
2366
+
2367
+ // src/plot/utils/tensor.ts
2368
+ function tensorToFloat64Vector1D(t) {
2369
+ const tensor2 = "tensor" in t ? t.tensor : t;
2370
+ if (tensor2.ndim !== 1) throw new ShapeError("Expected a 1D tensor");
2371
+ if (tensor2.dtype === "string") throw new DTypeError("Plotting does not support string tensors");
2372
+ const n = tensor2.shape[0] ?? 0;
2373
+ const stride = tensor2.strides[0] ?? 0;
2374
+ const out = new Float64Array(n);
2375
+ const base = tensor2.offset;
2376
+ for (let i = 0; i < n; i++) {
2377
+ const v = tensor2.data[base + i * stride];
2378
+ out[i] = safeConvertToNumber(v);
2379
+ }
2380
+ return out;
2381
+ }
2382
+ function tensorToFloat64Matrix2D(t) {
2383
+ const tensor2 = "tensor" in t ? t.tensor : t;
2384
+ if (tensor2.ndim !== 2) throw new ShapeError("Expected a 2D tensor");
2385
+ if (tensor2.dtype === "string") throw new DTypeError("Plotting does not support string tensors");
2386
+ const rows = tensor2.shape[0] ?? 0;
2387
+ const cols = tensor2.shape[1] ?? 0;
2388
+ const strideRow = tensor2.strides[0] ?? 0;
2389
+ const strideCol = tensor2.strides[1] ?? 0;
2390
+ const out = new Float64Array(rows * cols);
2391
+ if (strideCol === 1 && strideRow === cols) {
2392
+ const start = tensor2.offset;
2393
+ const end = start + rows * cols;
2394
+ for (let i = 0, j = start; j < end; i++, j++) {
2395
+ const v = tensor2.data[j];
2396
+ out[i] = safeConvertToNumber(v);
2397
+ }
2398
+ return { rows, cols, data: out };
2399
+ }
2400
+ const base = tensor2.offset;
2401
+ for (let i = 0; i < rows; i++) {
2402
+ const rowBase = base + i * strideRow;
2403
+ for (let j = 0; j < cols; j++) {
2404
+ const v = tensor2.data[rowBase + j * strideCol];
2405
+ out[i * cols + j] = safeConvertToNumber(v);
2406
+ }
2407
+ }
2408
+ return { rows, cols, data: out };
2409
+ }
2410
+ function safeConvertToNumber(value) {
2411
+ if (typeof value === "number") {
2412
+ return value;
2413
+ }
2414
+ if (typeof value === "bigint") {
2415
+ return Number(value);
2416
+ }
2417
+ throw new DTypeError(`Cannot convert ${typeof value} to number for plotting`);
2418
+ }
2419
+
2420
+ // src/plot/utils/contours.ts
2421
+ function tensorSize(t) {
2422
+ const tensor2 = "tensor" in t ? t.tensor : t;
2423
+ if (tensor2.ndim === 0) return 1;
2424
+ let size = 1;
2425
+ for (const dim of tensor2.shape) {
2426
+ const d = dim ?? 0;
2427
+ if (d === 0) return 0;
2428
+ size *= d;
2429
+ }
2430
+ return size;
2431
+ }
2432
+ function assertFiniteCoords(values, name) {
2433
+ for (let i = 0; i < values.length; i++) {
2434
+ const v = values[i] ?? 0;
2435
+ if (!isFiniteNumber(v)) {
2436
+ throw new InvalidParameterError(`${name} coordinates must be finite`, name, v);
2437
+ }
2438
+ }
2439
+ }
2440
+ function computeDataRange(data) {
2441
+ let min = Number.POSITIVE_INFINITY;
2442
+ let max = Number.NEGATIVE_INFINITY;
2443
+ let hasNaN = false;
2444
+ for (let i = 0; i < data.length; i++) {
2445
+ const v = data[i] ?? 0;
2446
+ if (!isFiniteNumber(v)) {
2447
+ if (Number.isNaN(v)) {
2448
+ hasNaN = true;
2449
+ }
2450
+ continue;
2451
+ }
2452
+ if (v < min) min = v;
2453
+ if (v > max) max = v;
2454
+ }
2455
+ if (!Number.isFinite(min) || !Number.isFinite(max)) {
2456
+ throw new InvalidParameterError("Contour requires at least one finite Z value", "Z", data);
2457
+ }
2458
+ return { min, max, hasNaN };
2459
+ }
2460
+ function meshgridToCoords(X, Y, rows, cols) {
2461
+ const xMat = tensorToFloat64Matrix2D(X);
2462
+ const yMat = tensorToFloat64Matrix2D(Y);
2463
+ if (xMat.rows !== rows || xMat.cols !== cols) {
2464
+ throw new ShapeError("X must match Z shape for meshgrid input");
2465
+ }
2466
+ if (yMat.rows !== rows || yMat.cols !== cols) {
2467
+ throw new ShapeError("Y must match Z shape for meshgrid input");
2468
+ }
2469
+ const xCoords = new Float64Array(cols);
2470
+ const yCoords = new Float64Array(rows);
2471
+ for (let j = 0; j < cols; j++) {
2472
+ xCoords[j] = xMat.data[j] ?? 0;
2473
+ }
2474
+ for (let i = 0; i < rows; i++) {
2475
+ yCoords[i] = xMat.cols > 0 ? yMat.data[i * cols] ?? 0 : 0;
2476
+ }
2477
+ const tol = 1e-12;
2478
+ for (let i = 0; i < rows; i++) {
2479
+ const rowOffset = i * cols;
2480
+ for (let j = 0; j < cols; j++) {
2481
+ const expectedX = xCoords[j] ?? 0;
2482
+ const expectedY = yCoords[i] ?? 0;
2483
+ const xv = xMat.data[rowOffset + j] ?? 0;
2484
+ const yv = yMat.data[rowOffset + j] ?? 0;
2485
+ if (!isFiniteNumber(xv) || !isFiniteNumber(yv)) {
2486
+ throw new InvalidParameterError("X/Y meshgrid values must be finite", "X/Y", {
2487
+ x: xv,
2488
+ y: yv
2489
+ });
2490
+ }
2491
+ if (Math.abs(xv - expectedX) > tol || Math.abs(yv - expectedY) > tol) {
2492
+ throw new InvalidParameterError(
2493
+ "Contour supports only rectilinear grids (use 1D X/Y or meshgrid)",
2494
+ "X/Y",
2495
+ { x: xv, y: yv }
2496
+ );
2497
+ }
2498
+ }
2499
+ }
2500
+ assertFiniteCoords(xCoords, "X");
2501
+ assertFiniteCoords(yCoords, "Y");
2502
+ return { xCoords, yCoords };
2503
+ }
2504
+ function buildContourGrid(X, Y, Z, extent) {
2505
+ const zMat = tensorToFloat64Matrix2D(Z);
2506
+ const { rows, cols, data } = zMat;
2507
+ const { min, max, hasNaN } = computeDataRange(data);
2508
+ const xSize = tensorSize(X);
2509
+ const ySize = tensorSize(Y);
2510
+ let xCoords;
2511
+ let yCoords;
2512
+ if (xSize === 0 && ySize === 0) {
2513
+ xCoords = new Float64Array(cols);
2514
+ yCoords = new Float64Array(rows);
2515
+ if (extent) {
2516
+ const { xmin, xmax, ymin, ymax } = extent;
2517
+ if (!Number.isFinite(xmin) || !Number.isFinite(xmax) || !Number.isFinite(ymin) || !Number.isFinite(ymax)) {
2518
+ throw new InvalidParameterError("extent values must be finite", "extent", extent);
2519
+ }
2520
+ if (xmax <= xmin || ymax <= ymin) {
2521
+ throw new InvalidParameterError("extent ranges must be positive", "extent", extent);
2522
+ }
2523
+ const xDenom = Math.max(1, cols - 1);
2524
+ const yDenom = Math.max(1, rows - 1);
2525
+ for (let j = 0; j < cols; j++) {
2526
+ xCoords[j] = xmin + (xmax - xmin) * j / xDenom;
2527
+ }
2528
+ for (let i = 0; i < rows; i++) {
2529
+ yCoords[i] = ymin + (ymax - ymin) * i / yDenom;
2530
+ }
2531
+ } else {
2532
+ for (let j = 0; j < cols; j++) xCoords[j] = j;
2533
+ for (let i = 0; i < rows; i++) yCoords[i] = i;
2534
+ }
2535
+ } else if (xSize > 0 && ySize > 0) {
2536
+ if (extent) {
2537
+ throw new InvalidParameterError(
2538
+ "extent cannot be combined with explicit X/Y coordinates",
2539
+ "extent",
2540
+ extent
2541
+ );
2542
+ }
2543
+ const rawX = "tensor" in X ? X.tensor : X;
2544
+ const rawY = "tensor" in Y ? Y.tensor : Y;
2545
+ if (rawX.ndim === 1 && rawY.ndim === 1) {
2546
+ xCoords = tensorToFloat64Vector1D(X);
2547
+ yCoords = tensorToFloat64Vector1D(Y);
2548
+ if (xCoords.length !== cols) {
2549
+ throw new ShapeError(`X must have length ${cols}; received ${xCoords.length}`);
2550
+ }
2551
+ if (yCoords.length !== rows) {
2552
+ throw new ShapeError(`Y must have length ${rows}; received ${yCoords.length}`);
2553
+ }
2554
+ assertFiniteCoords(xCoords, "X");
2555
+ assertFiniteCoords(yCoords, "Y");
2556
+ } else if (rawX.ndim === 2 && rawY.ndim === 2) {
2557
+ ({ xCoords, yCoords } = meshgridToCoords(X, Y, rows, cols));
2558
+ } else {
2559
+ throw new ShapeError("X and Y must be 1D vectors or 2D meshgrids matching Z");
2560
+ }
2561
+ } else {
2562
+ throw new InvalidParameterError("Both X and Y must be provided or both empty", "X/Y", {
2563
+ xSize,
2564
+ ySize
2565
+ });
2566
+ }
2567
+ return {
2568
+ rows,
2569
+ cols,
2570
+ data,
2571
+ xCoords,
2572
+ yCoords,
2573
+ dataMin: min,
2574
+ dataMax: max + (hasNaN ? Number.EPSILON : 0)
2575
+ };
2576
+ }
2577
+
2578
+ // src/plot/utils/text.ts
2579
+ function estimateTextWidth(text, fontSize) {
2580
+ if (text.length === 0) return 0;
2581
+ return text.length * fontSize * 0.6;
2582
+ }
2583
+
2584
+ // src/plot/utils/ticks.ts
2585
+ function niceNumber(range, round) {
2586
+ if (!Number.isFinite(range) || range <= 0) return 1;
2587
+ const exponent = Math.floor(Math.log10(range));
2588
+ const fraction = range / 10 ** exponent;
2589
+ let niceFraction;
2590
+ if (round) {
2591
+ if (fraction < 1.5) niceFraction = 1;
2592
+ else if (fraction < 3) niceFraction = 2;
2593
+ else if (fraction < 4.5) niceFraction = 2.5;
2594
+ else if (fraction < 7) niceFraction = 5;
2595
+ else niceFraction = 10;
2596
+ } else {
2597
+ if (fraction <= 1) niceFraction = 1;
2598
+ else if (fraction <= 2) niceFraction = 2;
2599
+ else if (fraction <= 2.5) niceFraction = 2.5;
2600
+ else if (fraction <= 5) niceFraction = 5;
2601
+ else niceFraction = 10;
2602
+ }
2603
+ return niceFraction * 10 ** exponent;
2604
+ }
2605
+ function formatTick(value, step) {
2606
+ if (!Number.isFinite(value)) return "";
2607
+ const abs = Math.abs(value);
2608
+ if (abs < 1e-12) return "0";
2609
+ if (abs > 0 && abs < 1e-4 || abs >= 1e6) {
2610
+ return value.toExponential(2);
2611
+ }
2612
+ const absStep = Math.abs(step);
2613
+ let decimals = 0;
2614
+ if (absStep > 0 && absStep < 1) {
2615
+ decimals = Math.min(6, Math.ceil(-Math.log10(absStep)));
2616
+ }
2617
+ let text = value.toFixed(decimals);
2618
+ if (decimals > 0) {
2619
+ text = text.replace(/\.?0+$/, "");
2620
+ }
2621
+ return text;
2622
+ }
2623
+ function generateTicks(min, max, maxTicks = 5) {
2624
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return [];
2625
+ if (maxTicks <= 0 || !Number.isFinite(maxTicks)) return [];
2626
+ const m = Math.min(min, max);
2627
+ const M = Math.max(min, max);
2628
+ if (m === M) {
2629
+ const span = Math.max(1, Math.abs(m) * 0.05);
2630
+ return generateTicks(m - span, M + span, maxTicks);
2631
+ }
2632
+ const range = niceNumber(M - m, false);
2633
+ const step = niceNumber(range / Math.max(1, maxTicks - 1), true);
2634
+ if (!Number.isFinite(step) || step <= 0) return [];
2635
+ const niceMin = Math.floor(m / step) * step;
2636
+ const niceMax = Math.ceil(M / step) * step;
2637
+ const ticks = [];
2638
+ const epsilon = step * 1e-9;
2639
+ for (let v = niceMin; v <= niceMax + epsilon; v += step) {
2640
+ if (v + epsilon < m || v - epsilon > M) continue;
2641
+ ticks.push({ value: v, label: formatTick(v, step) });
2642
+ }
2643
+ return ticks;
2644
+ }
2645
+
2646
+ // src/plot/utils/transforms.ts
2647
+ function computeAutoRange(drawables) {
2648
+ let xmin = Number.POSITIVE_INFINITY;
2649
+ let xmax = Number.NEGATIVE_INFINITY;
2650
+ let ymin = Number.POSITIVE_INFINITY;
2651
+ let ymax = Number.NEGATIVE_INFINITY;
2652
+ for (const d of drawables) {
2653
+ const r = d.getDataRange();
2654
+ if (!r) continue;
2655
+ xmin = Math.min(xmin, r.xmin);
2656
+ xmax = Math.max(xmax, r.xmax);
2657
+ ymin = Math.min(ymin, r.ymin);
2658
+ ymax = Math.max(ymax, r.ymax);
2659
+ }
2660
+ if (!Number.isFinite(xmin) || !Number.isFinite(xmax)) {
2661
+ xmin = 0;
2662
+ xmax = 1;
2663
+ } else if (xmin === xmax) {
2664
+ const span = Math.max(1, Math.abs(xmin) * 0.05);
2665
+ xmin -= span;
2666
+ xmax += span;
2667
+ }
2668
+ if (!Number.isFinite(ymin) || !Number.isFinite(ymax)) {
2669
+ ymin = 0;
2670
+ ymax = 1;
2671
+ } else if (ymin === ymax) {
2672
+ const span = Math.max(1, Math.abs(ymin) * 0.05);
2673
+ ymin -= span;
2674
+ ymax += span;
2675
+ }
2676
+ const xPad = (xmax - xmin) * 0.05;
2677
+ const yPad = (ymax - ymin) * 0.05;
2678
+ return {
2679
+ xmin: xmin - xPad,
2680
+ xmax: xmax + xPad,
2681
+ ymin: ymin - yPad,
2682
+ ymax: ymax + yPad
2683
+ };
2684
+ }
2685
+ function makeTransform(range, viewport) {
2686
+ const dx = range.xmax - range.xmin;
2687
+ const dy = range.ymax - range.ymin;
2688
+ const sx = dx !== 0 ? viewport.width / dx : 0;
2689
+ const sy = dy !== 0 ? viewport.height / dy : 0;
2690
+ return {
2691
+ xToPx: (x) => viewport.x + (x - range.xmin) * sx,
2692
+ yToPx: (y) => viewport.y + viewport.height - (y - range.ymin) * sy
2693
+ };
2694
+ }
2695
+
2696
+ // src/plot/figure/Axes.ts
2697
+ var Axes = class {
2698
+ fig;
2699
+ padding;
2700
+ paddingProvided;
2701
+ facecolor;
2702
+ drawables;
2703
+ title;
2704
+ xlabel;
2705
+ ylabel;
2706
+ legendOptions;
2707
+ xTicksOverride;
2708
+ yTicksOverride;
2709
+ baseViewport;
2710
+ constructor(fig, options) {
2711
+ this.fig = fig;
2712
+ this.paddingProvided = options.padding !== void 0;
2713
+ const base = options.viewport ?? {
2714
+ x: 0,
2715
+ y: 0,
2716
+ width: this.fig.width,
2717
+ height: this.fig.height
2718
+ };
2719
+ if (!Number.isFinite(base.x) || !Number.isFinite(base.y) || !Number.isFinite(base.width) || !Number.isFinite(base.height) || base.width <= 0 || base.height <= 0) {
2720
+ throw new InvalidParameterError(
2721
+ "viewport must have positive finite width/height",
2722
+ "viewport",
2723
+ base
2724
+ );
2725
+ }
2726
+ const p = options.padding ?? Math.min(50, Math.max(0, Math.floor(Math.min(base.width, base.height) / 4)));
2727
+ if (!Number.isFinite(p) || p < 0) {
2728
+ throw new InvalidParameterError(`padding must be non-negative; received ${p}`, "padding", p);
2729
+ }
2730
+ if (2 * p >= base.width || 2 * p >= base.height) {
2731
+ if (this.paddingProvided) {
2732
+ throw new InvalidParameterError("padding is too large", "padding", p);
2733
+ }
2734
+ const maxSafe = Math.max(0, Math.floor((Math.min(base.width, base.height) - 1) / 2));
2735
+ this.padding = maxSafe;
2736
+ } else {
2737
+ this.padding = p;
2738
+ }
2739
+ this.facecolor = normalizeColor(options.facecolor, "#ffffff");
2740
+ this.baseViewport = options.viewport;
2741
+ this.drawables = [];
2742
+ this.title = "";
2743
+ this.xlabel = "";
2744
+ this.ylabel = "";
2745
+ this.legendOptions = null;
2746
+ this.xTicksOverride = null;
2747
+ this.yTicksOverride = null;
2748
+ }
2749
+ /** Set the axes title text. */
2750
+ setTitle(title) {
2751
+ this.title = title;
2752
+ }
2753
+ /** Set the x-axis label text. */
2754
+ setXLabel(label) {
2755
+ this.xlabel = label;
2756
+ }
2757
+ /** Set the y-axis label text. */
2758
+ setYLabel(label) {
2759
+ this.ylabel = label;
2760
+ }
2761
+ /**
2762
+ * Set custom x-axis tick positions and labels.
2763
+ * @param values - Tick positions in data coordinates
2764
+ * @param labels - Optional tick labels
2765
+ */
2766
+ setXTicks(values, labels) {
2767
+ this.xTicksOverride = this.buildTickOverride(values, labels, "xTicks");
2768
+ }
2769
+ /**
2770
+ * Set custom y-axis tick positions and labels.
2771
+ * @param values - Tick positions in data coordinates
2772
+ * @param labels - Optional tick labels
2773
+ */
2774
+ setYTicks(values, labels) {
2775
+ this.yTicksOverride = this.buildTickOverride(values, labels, "yTicks");
2776
+ }
2777
+ /**
2778
+ * Show or configure the legend for this axes.
2779
+ * @param options - Legend display options
2780
+ */
2781
+ legend(options = {}) {
2782
+ if (options.location !== void 0 && !["upper-right", "upper-left", "lower-right", "lower-left"].includes(options.location)) {
2783
+ throw new InvalidParameterError(
2784
+ `legend location must be one of upper-right, upper-left, lower-right, lower-left; received ${options.location}`,
2785
+ "location",
2786
+ options.location
2787
+ );
2788
+ }
2789
+ if (options.fontSize !== void 0) {
2790
+ if (!Number.isFinite(options.fontSize) || options.fontSize <= 0) {
2791
+ throw new InvalidParameterError(
2792
+ `legend fontSize must be positive; received ${options.fontSize}`,
2793
+ "fontSize",
2794
+ options.fontSize
2795
+ );
2796
+ }
2797
+ }
2798
+ if (options.padding !== void 0) {
2799
+ if (!Number.isFinite(options.padding) || options.padding < 0) {
2800
+ throw new InvalidParameterError(
2801
+ `legend padding must be non-negative; received ${options.padding}`,
2802
+ "padding",
2803
+ options.padding
2804
+ );
2805
+ }
2806
+ }
2807
+ this.legendOptions = options;
2808
+ }
2809
+ /**
2810
+ * Plot a connected line series.
2811
+ * @param x - 1D tensor of x coordinates
2812
+ * @param y - 1D tensor of y coordinates
2813
+ * @param options - Styling options
2814
+ */
2815
+ plot(x, y, options = {}) {
2816
+ const xv = tensorToFloat64Vector1D(x);
2817
+ const yv = tensorToFloat64Vector1D(y);
2818
+ const d = new Line2D(xv, yv, options);
2819
+ this.drawables.push(d);
2820
+ return d;
2821
+ }
2822
+ /**
2823
+ * Plot unconnected points.
2824
+ * @param x - 1D tensor of x coordinates
2825
+ * @param y - 1D tensor of y coordinates
2826
+ * @param options - Styling options
2827
+ */
2828
+ scatter(x, y, options = {}) {
2829
+ const xv = tensorToFloat64Vector1D(x);
2830
+ const yv = tensorToFloat64Vector1D(y);
2831
+ const d = new Scatter2D(xv, yv, options);
2832
+ this.drawables.push(d);
2833
+ return d;
2834
+ }
2835
+ /**
2836
+ * Plot vertical bars.
2837
+ * @param x - 1D tensor of bar centers
2838
+ * @param height - 1D tensor of bar heights
2839
+ * @param options - Styling options
2840
+ */
2841
+ bar(x, height, options = {}) {
2842
+ const xv = tensorToFloat64Vector1D(x);
2843
+ const hv = tensorToFloat64Vector1D(height);
2844
+ const d = new Bar2D(xv, hv, options);
2845
+ this.drawables.push(d);
2846
+ return d;
2847
+ }
2848
+ /**
2849
+ * Plot a histogram.
2850
+ * @param x - 1D tensor of sample values
2851
+ * @param bins - Number of bins
2852
+ * @param options - Styling options
2853
+ */
2854
+ hist(x, bins = 10, options = {}) {
2855
+ const xv = tensorToFloat64Vector1D(x);
2856
+ const resolvedBins = options.bins ?? bins;
2857
+ const d = new Histogram(xv, resolvedBins, options);
2858
+ this.drawables.push(d);
2859
+ return d;
2860
+ }
2861
+ /**
2862
+ * Plot horizontal bars.
2863
+ * @param y - 1D tensor of bar centers
2864
+ * @param width - 1D tensor of bar widths
2865
+ * @param options - Styling options
2866
+ */
2867
+ barh(y, width, options = {}) {
2868
+ const yv = tensorToFloat64Vector1D(y);
2869
+ const wv = tensorToFloat64Vector1D(width);
2870
+ const d = new HorizontalBar2D(yv, wv, options);
2871
+ this.drawables.push(d);
2872
+ return d;
2873
+ }
2874
+ /**
2875
+ * Plot a heatmap for 2D data.
2876
+ * @param data - 2D tensor of values
2877
+ * @param options - Styling and scale options
2878
+ */
2879
+ heatmap(data, options = {}) {
2880
+ const mat = tensorToFloat64Matrix2D(data);
2881
+ const d = new Heatmap2D(mat.data, mat.rows, mat.cols, options);
2882
+ this.drawables.push(d);
2883
+ return d;
2884
+ }
2885
+ /**
2886
+ * Display a matrix as an image (alias of heatmap).
2887
+ * @param data - 2D tensor of values
2888
+ * @param options - Styling and scale options
2889
+ */
2890
+ imshow(data, options = {}) {
2891
+ const mat = tensorToFloat64Matrix2D(data);
2892
+ const d = new Heatmap2D(mat.data, mat.rows, mat.cols, options);
2893
+ this.drawables.push(d);
2894
+ return d;
2895
+ }
2896
+ /**
2897
+ * Plot contour lines for a 2D grid.
2898
+ * @param X - 1D/2D tensor of x coordinates (or empty)
2899
+ * @param Y - 1D/2D tensor of y coordinates (or empty)
2900
+ * @param Z - 2D tensor of values
2901
+ * @param options - Styling and level options
2902
+ */
2903
+ contour(X, Y, Z, options = {}) {
2904
+ const grid = buildContourGrid(X, Y, Z, options.extent);
2905
+ const d = new Contour2D(grid, options);
2906
+ this.drawables.push(d);
2907
+ return d;
2908
+ }
2909
+ /**
2910
+ * Plot filled contours for a 2D grid.
2911
+ * @param X - 1D/2D tensor of x coordinates (or empty)
2912
+ * @param Y - 1D/2D tensor of y coordinates (or empty)
2913
+ * @param Z - 2D tensor of values
2914
+ * @param options - Styling and level options
2915
+ */
2916
+ contourf(X, Y, Z, options = {}) {
2917
+ const grid = buildContourGrid(X, Y, Z, options.extent);
2918
+ const d = new ContourF2D(grid, options);
2919
+ this.drawables.push(d);
2920
+ return d;
2921
+ }
2922
+ /**
2923
+ * Plot a box-and-whisker summary for a 1D dataset.
2924
+ * @param data - 1D tensor of values
2925
+ * @param options - Styling options
2926
+ */
2927
+ boxplot(data, options = {}) {
2928
+ const values = tensorToFloat64Vector1D(data);
2929
+ const d = new Boxplot(1, values, options);
2930
+ this.drawables.push(d);
2931
+ return d;
2932
+ }
2933
+ /**
2934
+ * Plot a violin distribution summary for a 1D dataset.
2935
+ * @param data - 1D tensor of values
2936
+ * @param options - Styling options
2937
+ */
2938
+ violinplot(data, options = {}) {
2939
+ const values = tensorToFloat64Vector1D(data);
2940
+ const d = new Violinplot(1, values, options);
2941
+ this.drawables.push(d);
2942
+ return d;
2943
+ }
2944
+ /**
2945
+ * Plot a pie chart.
2946
+ * @param values - 1D tensor of non-negative values
2947
+ * @param labels - Optional labels (must match values length)
2948
+ * @param options - Styling options
2949
+ */
2950
+ pie(values, labels, options = {}) {
2951
+ const data = tensorToFloat64Vector1D(values);
2952
+ const range = { xmin: 0, xmax: 1, ymin: 0, ymax: 1 };
2953
+ const d = new Pie(0.5, 0.5, 0.35, data, labels, options, range);
2954
+ this.drawables.push(d);
2955
+ return d;
2956
+ }
2957
+ viewport() {
2958
+ const p = this.padding;
2959
+ const base = this.baseViewport ?? {
2960
+ x: 0,
2961
+ y: 0,
2962
+ width: this.fig.width,
2963
+ height: this.fig.height
2964
+ };
2965
+ return {
2966
+ x: base.x + p,
2967
+ y: base.y + p,
2968
+ width: base.width - 2 * p,
2969
+ height: base.height - 2 * p
2970
+ };
2971
+ }
2972
+ collectLegendEntries() {
2973
+ const entries = [];
2974
+ const seen = /* @__PURE__ */ new Set();
2975
+ for (const drawable of this.drawables) {
2976
+ const list = drawable.getLegendEntries?.();
2977
+ if (!list) continue;
2978
+ for (const entry of list) {
2979
+ const trimmed = entry.label.trim();
2980
+ if (!trimmed) continue;
2981
+ if (seen.has(trimmed)) continue;
2982
+ seen.add(trimmed);
2983
+ entries.push({ ...entry, label: trimmed });
2984
+ }
2985
+ }
2986
+ return entries;
2987
+ }
2988
+ resolveLegendOptions() {
2989
+ const options = this.legendOptions ?? {};
2990
+ return {
2991
+ visible: options.visible ?? true,
2992
+ location: options.location ?? "upper-right",
2993
+ fontSize: options.fontSize ?? 12,
2994
+ padding: options.padding ?? 6,
2995
+ background: normalizeColor(options.background, "#ffffff"),
2996
+ borderColor: normalizeColor(options.borderColor, "#000000")
2997
+ };
2998
+ }
2999
+ buildTickOverride(values, labels, name) {
3000
+ if (values.length === 0) return null;
3001
+ if (labels && labels.length !== values.length) {
3002
+ throw new InvalidParameterError(
3003
+ `${name} labels length must match values length (${values.length}); received ${labels.length}`,
3004
+ name,
3005
+ labels
3006
+ );
3007
+ }
3008
+ const ticks = [];
3009
+ for (const [i, value] of values.entries()) {
3010
+ if (!Number.isFinite(value)) {
3011
+ throw new InvalidParameterError(`${name} values must be finite`, name, value);
3012
+ }
3013
+ const label = labels ? labels[i] ?? "" : String(value);
3014
+ ticks.push({ value, label: label.trim() });
3015
+ }
3016
+ return ticks;
3017
+ }
3018
+ renderTicksSVG(elements, vp, range, transform) {
3019
+ const maxXTicks = Math.max(2, Math.floor(vp.width / 80));
3020
+ const maxYTicks = Math.max(2, Math.floor(vp.height / 60));
3021
+ const xTicks = (this.xTicksOverride ?? generateTicks(range.xmin, range.xmax, maxXTicks)).filter(
3022
+ (tick) => tick.value >= range.xmin && tick.value <= range.xmax
3023
+ );
3024
+ const yTicks = (this.yTicksOverride ?? generateTicks(range.ymin, range.ymax, maxYTicks)).filter(
3025
+ (tick) => tick.value >= range.ymin && tick.value <= range.ymax
3026
+ );
3027
+ const tickLength = 5;
3028
+ const labelOffset = 2;
3029
+ for (const tick of xTicks) {
3030
+ const px = transform.xToPx(tick.value);
3031
+ elements.push(
3032
+ `<line class="x-tick" x1="${px.toFixed(2)}" y1="${(vp.y + vp.height).toFixed(
3033
+ 2
3034
+ )}" x2="${px.toFixed(2)}" y2="${(vp.y + vp.height + tickLength).toFixed(
3035
+ 2
3036
+ )}" stroke="#000" />`
3037
+ );
3038
+ elements.push(
3039
+ `<text class="tick-label tick-label-x" x="${px.toFixed(2)}" y="${(vp.y + vp.height + tickLength + labelOffset).toFixed(
3040
+ 2
3041
+ )}" text-anchor="middle" dominant-baseline="hanging" font-size="10" fill="#000">${escapeXml(
3042
+ tick.label
3043
+ )}</text>`
3044
+ );
3045
+ }
3046
+ for (const tick of yTicks) {
3047
+ const py = transform.yToPx(tick.value);
3048
+ elements.push(
3049
+ `<line class="y-tick" x1="${vp.x.toFixed(2)}" y1="${py.toFixed(2)}" x2="${(vp.x - tickLength).toFixed(2)}" y2="${py.toFixed(2)}" stroke="#000" />`
3050
+ );
3051
+ elements.push(
3052
+ `<text class="tick-label tick-label-y" x="${(vp.x - tickLength - labelOffset).toFixed(
3053
+ 2
3054
+ )}" y="${py.toFixed(2)}" text-anchor="end" dominant-baseline="middle" font-size="10" fill="#000">${escapeXml(
3055
+ tick.label
3056
+ )}</text>`
3057
+ );
3058
+ }
3059
+ }
3060
+ renderLegendSVG(elements, vp) {
3061
+ if (!this.legendOptions) return;
3062
+ const entries = this.collectLegendEntries();
3063
+ if (entries.length === 0) return;
3064
+ const options = this.resolveLegendOptions();
3065
+ if (!options.visible) return;
3066
+ const fontSize = options.fontSize;
3067
+ const padding = options.padding;
3068
+ const symbolSize = Math.max(8, Math.round(fontSize * 0.9));
3069
+ const gap = 6;
3070
+ let maxLabelWidth = 0;
3071
+ for (const entry of entries) {
3072
+ const width = estimateTextWidth(entry.label, fontSize);
3073
+ maxLabelWidth = Math.max(maxLabelWidth, width);
3074
+ }
3075
+ const lineHeight = fontSize + 4;
3076
+ const boxWidth = padding * 2 + symbolSize + gap + maxLabelWidth;
3077
+ const boxHeight = padding * 2 + entries.length * lineHeight;
3078
+ const margin = 6;
3079
+ const isRight = options.location.includes("right");
3080
+ const isUpper = options.location.includes("upper");
3081
+ const boxX = isRight ? vp.x + vp.width - boxWidth - margin : vp.x + margin;
3082
+ const boxY = isUpper ? vp.y + margin : vp.y + vp.height - boxHeight - margin;
3083
+ elements.push(`<g class="legend">`);
3084
+ elements.push(
3085
+ `<rect class="legend-box" x="${boxX.toFixed(2)}" y="${boxY.toFixed(
3086
+ 2
3087
+ )}" width="${boxWidth.toFixed(2)}" height="${boxHeight.toFixed(
3088
+ 2
3089
+ )}" fill="${escapeXml(options.background)}" stroke="${escapeXml(options.borderColor)}" />`
3090
+ );
3091
+ for (let i = 0; i < entries.length; i++) {
3092
+ const entry = entries[i];
3093
+ if (!entry) continue;
3094
+ const rowY = boxY + padding + i * lineHeight + lineHeight / 2;
3095
+ const symbolX = boxX + padding;
3096
+ const symbolY = rowY - symbolSize / 2;
3097
+ if (entry.shape === "line") {
3098
+ const lineY = rowY;
3099
+ const lineWidth = entry.lineWidth ?? 2;
3100
+ elements.push(
3101
+ `<line class="legend-line" x1="${symbolX.toFixed(2)}" y1="${lineY.toFixed(
3102
+ 2
3103
+ )}" x2="${(symbolX + symbolSize).toFixed(2)}" y2="${lineY.toFixed(
3104
+ 2
3105
+ )}" stroke="${escapeXml(entry.color)}" stroke-width="${lineWidth}" />`
3106
+ );
3107
+ } else if (entry.shape === "marker") {
3108
+ const radius = Math.max(2, Math.min(symbolSize / 2, entry.markerSize ?? symbolSize / 2));
3109
+ const cx = symbolX + symbolSize / 2;
3110
+ const cy = rowY;
3111
+ elements.push(
3112
+ `<circle class="legend-marker" cx="${cx.toFixed(2)}" cy="${cy.toFixed(
3113
+ 2
3114
+ )}" r="${radius.toFixed(2)}" fill="${escapeXml(entry.color)}" />`
3115
+ );
3116
+ } else {
3117
+ elements.push(
3118
+ `<rect class="legend-swatch" x="${symbolX.toFixed(2)}" y="${symbolY.toFixed(
3119
+ 2
3120
+ )}" width="${symbolSize.toFixed(2)}" height="${symbolSize.toFixed(
3121
+ 2
3122
+ )}" fill="${escapeXml(entry.color)}" stroke="#000" />`
3123
+ );
3124
+ }
3125
+ const textX = symbolX + symbolSize + gap;
3126
+ elements.push(
3127
+ `<text class="legend-label" x="${textX.toFixed(2)}" y="${rowY.toFixed(
3128
+ 2
3129
+ )}" text-anchor="start" dominant-baseline="middle" font-size="${fontSize}" fill="#000">${escapeXml(
3130
+ entry.label
3131
+ )}</text>`
3132
+ );
3133
+ }
3134
+ elements.push(`</g>`);
3135
+ }
3136
+ renderTicksRaster(canvas, vp, range, transform) {
3137
+ const maxXTicks = Math.max(2, Math.floor(vp.width / 80));
3138
+ const maxYTicks = Math.max(2, Math.floor(vp.height / 60));
3139
+ const xTicks = (this.xTicksOverride ?? generateTicks(range.xmin, range.xmax, maxXTicks)).filter(
3140
+ (tick) => tick.value >= range.xmin && tick.value <= range.xmax
3141
+ );
3142
+ const yTicks = (this.yTicksOverride ?? generateTicks(range.ymin, range.ymax, maxYTicks)).filter(
3143
+ (tick) => tick.value >= range.ymin && tick.value <= range.ymax
3144
+ );
3145
+ const tickLength = 5;
3146
+ const labelOffset = 2;
3147
+ const text = parseHexColorToRGBA("#000000");
3148
+ for (const tick of xTicks) {
3149
+ const px = Math.round(transform.xToPx(tick.value));
3150
+ const y0 = Math.round(vp.y + vp.height);
3151
+ const y1 = y0 + tickLength;
3152
+ canvas.drawLineRGBA(px, y0, px, y1, text.r, text.g, text.b, text.a);
3153
+ canvas.drawTextRGBA(tick.label, px, y1 + labelOffset, text.r, text.g, text.b, text.a, {
3154
+ fontSize: 10,
3155
+ align: "middle",
3156
+ baseline: "top"
3157
+ });
3158
+ }
3159
+ for (const tick of yTicks) {
3160
+ const py = Math.round(transform.yToPx(tick.value));
3161
+ const x0 = Math.round(vp.x);
3162
+ const x1 = x0 - tickLength;
3163
+ canvas.drawLineRGBA(x0, py, x1, py, text.r, text.g, text.b, text.a);
3164
+ canvas.drawTextRGBA(tick.label, x1 - labelOffset, py, text.r, text.g, text.b, text.a, {
3165
+ fontSize: 10,
3166
+ align: "end",
3167
+ baseline: "middle"
3168
+ });
3169
+ }
3170
+ }
3171
+ renderLegendRaster(canvas, vp) {
3172
+ if (!this.legendOptions) return;
3173
+ const entries = this.collectLegendEntries();
3174
+ if (entries.length === 0) return;
3175
+ const options = this.resolveLegendOptions();
3176
+ if (!options.visible) return;
3177
+ const fontSize = options.fontSize;
3178
+ const padding = options.padding;
3179
+ const symbolSize = Math.max(8, Math.round(fontSize * 0.9));
3180
+ const gap = 6;
3181
+ let maxLabelWidth = 0;
3182
+ for (const entry of entries) {
3183
+ maxLabelWidth = Math.max(maxLabelWidth, estimateTextWidth(entry.label, fontSize));
3184
+ }
3185
+ const lineHeight = fontSize + 4;
3186
+ const boxWidth = padding * 2 + symbolSize + gap + maxLabelWidth;
3187
+ const boxHeight = padding * 2 + entries.length * lineHeight;
3188
+ const margin = 6;
3189
+ const isRight = options.location.includes("right");
3190
+ const isUpper = options.location.includes("upper");
3191
+ const boxX = isRight ? vp.x + vp.width - boxWidth - margin : vp.x + margin;
3192
+ const boxY = isUpper ? vp.y + margin : vp.y + vp.height - boxHeight - margin;
3193
+ const bg = parseHexColorToRGBA(options.background);
3194
+ const border = parseHexColorToRGBA(options.borderColor);
3195
+ const bx = Math.round(boxX);
3196
+ const by = Math.round(boxY);
3197
+ const bw = Math.round(boxWidth);
3198
+ const bh = Math.round(boxHeight);
3199
+ canvas.fillRectRGBA(bx, by, bw, bh, bg.r, bg.g, bg.b, bg.a);
3200
+ canvas.drawLineRGBA(bx, by, bx + bw, by, border.r, border.g, border.b, border.a);
3201
+ canvas.drawLineRGBA(bx, by + bh, bx + bw, by + bh, border.r, border.g, border.b, border.a);
3202
+ canvas.drawLineRGBA(bx, by, bx, by + bh, border.r, border.g, border.b, border.a);
3203
+ canvas.drawLineRGBA(bx + bw, by, bx + bw, by + bh, border.r, border.g, border.b, border.a);
3204
+ const text = parseHexColorToRGBA("#000000");
3205
+ for (let i = 0; i < entries.length; i++) {
3206
+ const entry = entries[i];
3207
+ if (!entry) continue;
3208
+ const rowY = boxY + padding + i * lineHeight + lineHeight / 2;
3209
+ const symbolX = boxX + padding;
3210
+ const symbolY = rowY - symbolSize / 2;
3211
+ const color = parseHexColorToRGBA(entry.color);
3212
+ if (entry.shape === "line") {
3213
+ const y = Math.round(rowY);
3214
+ const x0 = Math.round(symbolX);
3215
+ const x1 = Math.round(symbolX + symbolSize);
3216
+ canvas.drawLineRGBA(x0, y, x1, y, color.r, color.g, color.b, color.a);
3217
+ } else if (entry.shape === "marker") {
3218
+ const radius = Math.max(2, Math.min(symbolSize / 2, entry.markerSize ?? symbolSize / 2));
3219
+ const cx = Math.round(symbolX + symbolSize / 2);
3220
+ const cy = Math.round(rowY);
3221
+ canvas.drawCircleRGBA(cx, cy, Math.round(radius), color.r, color.g, color.b, color.a);
3222
+ } else {
3223
+ canvas.fillRectRGBA(
3224
+ Math.round(symbolX),
3225
+ Math.round(symbolY),
3226
+ Math.round(symbolSize),
3227
+ Math.round(symbolSize),
3228
+ color.r,
3229
+ color.g,
3230
+ color.b,
3231
+ color.a
3232
+ );
3233
+ }
3234
+ const textX = symbolX + symbolSize + gap;
3235
+ canvas.drawTextRGBA(
3236
+ entry.label,
3237
+ Math.round(textX),
3238
+ Math.round(rowY),
3239
+ text.r,
3240
+ text.g,
3241
+ text.b,
3242
+ text.a,
3243
+ { fontSize, align: "start", baseline: "middle" }
3244
+ );
3245
+ }
3246
+ }
3247
+ /**
3248
+ * Render axes to SVG elements.
3249
+ * @internal
3250
+ */
3251
+ renderSVGInto(elements) {
3252
+ const vp = this.viewport();
3253
+ const range = computeAutoRange(this.drawables);
3254
+ const transform = makeTransform(range, vp);
3255
+ elements.push(
3256
+ `<rect x="${vp.x}" y="${vp.y}" width="${vp.width}" height="${vp.height}" fill="${escapeXml(this.facecolor)}" stroke="#000" />`
3257
+ );
3258
+ const ctx = {
3259
+ transform,
3260
+ push: (el) => {
3261
+ elements.push(el);
3262
+ }
3263
+ };
3264
+ for (const d of this.drawables) d.drawSVG(ctx);
3265
+ const shouldRenderTicks = this.drawables.length > 0;
3266
+ if (shouldRenderTicks) {
3267
+ this.renderTicksSVG(elements, vp, range, transform);
3268
+ }
3269
+ if (this.title) {
3270
+ const titleX = vp.x + vp.width / 2;
3271
+ const titleY = vp.y - 10;
3272
+ elements.push(
3273
+ `<text x="${titleX}" y="${titleY}" text-anchor="middle" font-size="14" font-weight="bold" fill="#000">${escapeXml(this.title)}</text>`
3274
+ );
3275
+ }
3276
+ if (this.xlabel) {
3277
+ const xlabelX = vp.x + vp.width / 2;
3278
+ const xlabelY = vp.y + vp.height + 35;
3279
+ elements.push(
3280
+ `<text x="${xlabelX}" y="${xlabelY}" text-anchor="middle" font-size="12" fill="#000">${escapeXml(this.xlabel)}</text>`
3281
+ );
3282
+ }
3283
+ if (this.ylabel) {
3284
+ const ylabelX = vp.x - 35;
3285
+ const ylabelY = vp.y + vp.height / 2;
3286
+ elements.push(
3287
+ `<text x="${ylabelX}" y="${ylabelY}" text-anchor="middle" font-size="12" fill="#000" transform="rotate(-90 ${ylabelX} ${ylabelY})">${escapeXml(this.ylabel)}</text>`
3288
+ );
3289
+ }
3290
+ this.renderLegendSVG(elements, vp);
3291
+ }
3292
+ /**
3293
+ * Render axes to raster canvas.
3294
+ * @internal
3295
+ */
3296
+ renderRasterInto(canvas) {
3297
+ const vp = this.viewport();
3298
+ const range = computeAutoRange(this.drawables);
3299
+ const transform = makeTransform(range, vp);
3300
+ const ctx = { transform, canvas };
3301
+ const bg = parseHexColorToRGBA(this.facecolor);
3302
+ const x0 = Math.round(vp.x);
3303
+ const y0 = Math.round(vp.y);
3304
+ const w = Math.max(0, Math.round(vp.width));
3305
+ const h = Math.max(0, Math.round(vp.height));
3306
+ if (w > 0 && h > 0) {
3307
+ canvas.fillRectRGBA(x0, y0, w, h, bg.r, bg.g, bg.b, bg.a);
3308
+ const edge = parseHexColorToRGBA("#000000");
3309
+ const x1 = x0 + w;
3310
+ const y1 = y0 + h;
3311
+ canvas.drawLineRGBA(x0, y0, x1, y0, edge.r, edge.g, edge.b, edge.a);
3312
+ canvas.drawLineRGBA(x0, y1, x1, y1, edge.r, edge.g, edge.b, edge.a);
3313
+ canvas.drawLineRGBA(x0, y0, x0, y1, edge.r, edge.g, edge.b, edge.a);
3314
+ canvas.drawLineRGBA(x1, y0, x1, y1, edge.r, edge.g, edge.b, edge.a);
3315
+ }
3316
+ for (const d of this.drawables) d.drawRaster(ctx);
3317
+ const shouldRenderTicks = this.drawables.length > 0;
3318
+ if (shouldRenderTicks) {
3319
+ this.renderTicksRaster(canvas, vp, range, transform);
3320
+ }
3321
+ const text = parseHexColorToRGBA("#000000");
3322
+ if (this.title) {
3323
+ const titleX = vp.x + vp.width / 2;
3324
+ const titleY = vp.y - 10;
3325
+ canvas.drawTextRGBA(this.title, titleX, titleY, text.r, text.g, text.b, text.a, {
3326
+ fontSize: 14,
3327
+ align: "middle",
3328
+ baseline: "bottom"
3329
+ });
3330
+ }
3331
+ if (this.xlabel) {
3332
+ const xlabelX = vp.x + vp.width / 2;
3333
+ const xlabelY = vp.y + vp.height + 35;
3334
+ canvas.drawTextRGBA(this.xlabel, xlabelX, xlabelY, text.r, text.g, text.b, text.a, {
3335
+ fontSize: 12,
3336
+ align: "middle",
3337
+ baseline: "top"
3338
+ });
3339
+ }
3340
+ if (this.ylabel) {
3341
+ const ylabelX = vp.x - 35;
3342
+ const ylabelY = vp.y + vp.height / 2;
3343
+ canvas.drawTextRGBA(this.ylabel, ylabelX, ylabelY, text.r, text.g, text.b, text.a, {
3344
+ fontSize: 12,
3345
+ align: "middle",
3346
+ baseline: "middle",
3347
+ rotation: -90
3348
+ });
3349
+ }
3350
+ this.renderLegendRaster(canvas, vp);
3351
+ }
3352
+ };
3353
+
3354
+ // src/plot/canvas/RasterCanvas.ts
3355
+ var RasterCanvas = class {
3356
+ width;
3357
+ height;
3358
+ data;
3359
+ constructor(width, height) {
3360
+ assertPositiveInt("width", width);
3361
+ assertPositiveInt("height", height);
3362
+ this.width = width;
3363
+ this.height = height;
3364
+ this.data = new Uint8ClampedArray(width * height * 4);
3365
+ }
3366
+ clearRGBA(r, g, b, a) {
3367
+ const n = this.width * this.height;
3368
+ for (let i = 0; i < n; i++) {
3369
+ const k = i * 4;
3370
+ this.data[k] = r;
3371
+ this.data[k + 1] = g;
3372
+ this.data[k + 2] = b;
3373
+ this.data[k + 3] = a;
3374
+ }
3375
+ }
3376
+ setPixelRGBA(x, y, r, g, b, a) {
3377
+ if (x < 0 || y < 0 || x >= this.width || y >= this.height) return;
3378
+ const idx = (y * this.width + x) * 4;
3379
+ this.data[idx] = r;
3380
+ this.data[idx + 1] = g;
3381
+ this.data[idx + 2] = b;
3382
+ this.data[idx + 3] = a;
3383
+ }
3384
+ fillRectRGBA(x0, y0, w, h, r, g, b, a) {
3385
+ const x1 = x0 + w;
3386
+ const y1 = y0 + h;
3387
+ const cx0 = clampInt(x0, 0, this.width);
3388
+ const cy0 = clampInt(y0, 0, this.height);
3389
+ const cx1 = clampInt(x1, 0, this.width);
3390
+ const cy1 = clampInt(y1, 0, this.height);
3391
+ for (let y = cy0; y < cy1; y++) {
3392
+ for (let x = cx0; x < cx1; x++) this.setPixelRGBA(x, y, r, g, b, a);
3393
+ }
3394
+ }
3395
+ drawLineRGBA(x0, y0, x1, y1, r, g, b, a) {
3396
+ let x = clampInt(x0, -1e6, 1e6);
3397
+ let y = clampInt(y0, -1e6, 1e6);
3398
+ const xEnd = clampInt(x1, -1e6, 1e6);
3399
+ const yEnd = clampInt(y1, -1e6, 1e6);
3400
+ const dx = Math.abs(xEnd - x);
3401
+ const sx = x < xEnd ? 1 : -1;
3402
+ const dy = -Math.abs(yEnd - y);
3403
+ const sy = y < yEnd ? 1 : -1;
3404
+ let err = dx + dy;
3405
+ for (; ; ) {
3406
+ this.setPixelRGBA(x, y, r, g, b, a);
3407
+ if (x === xEnd && y === yEnd) break;
3408
+ const e2 = 2 * err;
3409
+ if (e2 >= dy) {
3410
+ err += dy;
3411
+ x += sx;
3412
+ }
3413
+ if (e2 <= dx) {
3414
+ err += dx;
3415
+ y += sy;
3416
+ }
3417
+ }
3418
+ }
3419
+ fillTriangleRGBA(x0, y0, x1, y1, x2, y2, r, g, b, a) {
3420
+ if (y0 > y1) {
3421
+ [x0, x1] = [x1, x0];
3422
+ [y0, y1] = [y1, y0];
3423
+ }
3424
+ if (y0 > y2) {
3425
+ [x0, x2] = [x2, x0];
3426
+ [y0, y2] = [y2, y0];
3427
+ }
3428
+ if (y1 > y2) {
3429
+ [x1, x2] = [x2, x1];
3430
+ [y1, y2] = [y2, y1];
3431
+ }
3432
+ const totalHeight = y2 - y0;
3433
+ if (totalHeight === 0) return;
3434
+ for (let i = 0; i < totalHeight; i++) {
3435
+ const secondHalf = i > y1 - y0 || y1 === y0;
3436
+ const segmentHeight = secondHalf ? y2 - y1 : y1 - y0;
3437
+ const alpha = i / totalHeight;
3438
+ const beta = (i - (secondHalf ? y1 - y0 : 0)) / segmentHeight;
3439
+ let ax = x0 + (x2 - x0) * alpha;
3440
+ let bx = secondHalf ? x1 + (x2 - x1) * beta : x0 + (x1 - x0) * beta;
3441
+ if (ax > bx) {
3442
+ [ax, bx] = [bx, ax];
3443
+ }
3444
+ const y = Math.floor(y0 + i);
3445
+ const startX = Math.floor(ax);
3446
+ const endX = Math.ceil(bx);
3447
+ for (let x = startX; x < endX; x++) {
3448
+ this.setPixelRGBA(x, y, r, g, b, a);
3449
+ }
3450
+ }
3451
+ }
3452
+ drawCircleRGBA(cx, cy, radius, r, g, b, a) {
3453
+ const rr = Math.max(0, radius);
3454
+ const r2 = rr * rr;
3455
+ const x0 = clampInt(cx - rr, 0, this.width);
3456
+ const x1 = clampInt(cx + rr + 1, 0, this.width);
3457
+ const y0 = clampInt(cy - rr, 0, this.height);
3458
+ const y1 = clampInt(cy + rr + 1, 0, this.height);
3459
+ for (let y = y0; y < y1; y++) {
3460
+ const dy = y - cy;
3461
+ for (let x = x0; x < x1; x++) {
3462
+ const dx = x - cx;
3463
+ if (dx * dx + dy * dy <= r2) this.setPixelRGBA(x, y, r, g, b, a);
3464
+ }
3465
+ }
3466
+ }
3467
+ measureText(text, fontSize = 12) {
3468
+ const scale = Math.max(1, Math.round(fontSize / FONT_HEIGHT));
3469
+ const charWidth = FONT_WIDTH * scale;
3470
+ const charHeight = FONT_HEIGHT * scale;
3471
+ const spacing = scale;
3472
+ if (text.length === 0) return { width: 0, height: charHeight };
3473
+ return { width: text.length * charWidth + (text.length - 1) * spacing, height: charHeight };
3474
+ }
3475
+ drawTextRGBA(text, x, y, r, g, b, a, options = {}) {
3476
+ if (text.length === 0) return;
3477
+ const fontSize = options.fontSize ?? 12;
3478
+ const align = options.align ?? "start";
3479
+ const baseline = options.baseline ?? "top";
3480
+ const rotation = options.rotation ?? 0;
3481
+ const scale = Math.max(1, Math.round(fontSize / FONT_HEIGHT));
3482
+ const charWidth = FONT_WIDTH * scale;
3483
+ const charHeight = FONT_HEIGHT * scale;
3484
+ const spacing = scale;
3485
+ const textWidth = text.length * charWidth + (text.length - 1) * spacing;
3486
+ const textHeight = charHeight;
3487
+ const boxWidth = rotation === 0 ? textWidth : textHeight;
3488
+ const boxHeight = rotation === 0 ? textHeight : textWidth;
3489
+ let originX = Math.round(x);
3490
+ let originY = Math.round(y);
3491
+ if (align === "middle") originX -= Math.round(boxWidth / 2);
3492
+ else if (align === "end") originX -= boxWidth;
3493
+ if (baseline === "middle") originY -= Math.round(boxHeight / 2);
3494
+ else if (baseline === "bottom") originY -= boxHeight;
3495
+ for (let i = 0; i < text.length; i++) {
3496
+ const ch = text[i] ?? "";
3497
+ const rows = glyphRows(ch);
3498
+ const xOffset = i * (charWidth + spacing);
3499
+ for (let row = 0; row < rows.length; row++) {
3500
+ const mask = rows[row] ?? 0;
3501
+ for (let col = 0; col < FONT_WIDTH; col++) {
3502
+ if ((mask & 1 << FONT_WIDTH - 1 - col) === 0) continue;
3503
+ const baseX = xOffset + col * scale;
3504
+ const baseY = row * scale;
3505
+ for (let sy = 0; sy < scale; sy++) {
3506
+ for (let sx = 0; sx < scale; sx++) {
3507
+ const px = baseX + sx;
3508
+ const py = baseY + sy;
3509
+ let rx = px;
3510
+ let ry = py;
3511
+ if (rotation === -90) {
3512
+ rx = py;
3513
+ ry = textWidth - 1 - px;
3514
+ } else if (rotation === 90) {
3515
+ rx = textHeight - 1 - py;
3516
+ ry = px;
3517
+ }
3518
+ this.setPixelRGBA(originX + rx, originY + ry, r, g, b, a);
3519
+ }
3520
+ }
3521
+ }
3522
+ }
3523
+ }
3524
+ }
3525
+ };
3526
+ var FONT_WIDTH = 5;
3527
+ var FONT_HEIGHT = 7;
3528
+ var FONT = {
3529
+ " ": [0, 0, 0, 0, 0, 0, 0],
3530
+ "!": [4, 4, 4, 4, 4, 0, 4],
3531
+ '"': [10, 10, 0, 0, 0, 0, 0],
3532
+ "'": [4, 4, 0, 0, 0, 0, 0],
3533
+ "(": [2, 4, 8, 8, 8, 4, 2],
3534
+ ")": [8, 4, 2, 2, 2, 4, 8],
3535
+ "*": [0, 10, 4, 31, 4, 10, 0],
3536
+ "+": [0, 4, 4, 31, 4, 4, 0],
3537
+ ",": [0, 0, 0, 0, 0, 4, 8],
3538
+ "-": [0, 0, 0, 31, 0, 0, 0],
3539
+ ".": [0, 0, 0, 0, 0, 0, 4],
3540
+ "/": [1, 2, 4, 8, 16, 0, 0],
3541
+ "0": [14, 17, 19, 21, 25, 17, 14],
3542
+ "1": [4, 12, 4, 4, 4, 4, 14],
3543
+ "2": [14, 17, 1, 2, 4, 8, 31],
3544
+ "3": [31, 2, 4, 2, 1, 17, 14],
3545
+ "4": [2, 6, 10, 18, 31, 2, 2],
3546
+ "5": [31, 16, 30, 1, 1, 17, 14],
3547
+ "6": [6, 8, 16, 30, 17, 17, 14],
3548
+ "7": [31, 1, 2, 4, 8, 8, 8],
3549
+ "8": [14, 17, 17, 14, 17, 17, 14],
3550
+ "9": [14, 17, 17, 15, 1, 2, 12],
3551
+ ":": [0, 4, 0, 0, 0, 4, 0],
3552
+ ";": [0, 4, 0, 0, 0, 4, 8],
3553
+ "<": [2, 4, 8, 16, 8, 4, 2],
3554
+ "=": [0, 31, 0, 31, 0, 0, 0],
3555
+ ">": [16, 8, 4, 2, 4, 8, 16],
3556
+ "?": [14, 17, 1, 2, 4, 0, 4],
3557
+ "%": [25, 25, 2, 4, 8, 19, 19],
3558
+ _: [0, 0, 0, 0, 0, 0, 31],
3559
+ A: [14, 17, 17, 31, 17, 17, 17],
3560
+ B: [30, 17, 17, 30, 17, 17, 30],
3561
+ C: [14, 17, 16, 16, 16, 17, 14],
3562
+ D: [30, 17, 17, 17, 17, 17, 30],
3563
+ E: [31, 16, 16, 30, 16, 16, 31],
3564
+ F: [31, 16, 16, 30, 16, 16, 16],
3565
+ G: [14, 17, 16, 23, 17, 17, 14],
3566
+ H: [17, 17, 17, 31, 17, 17, 17],
3567
+ I: [14, 4, 4, 4, 4, 4, 14],
3568
+ J: [7, 2, 2, 2, 18, 18, 12],
3569
+ K: [17, 18, 20, 24, 20, 18, 17],
3570
+ L: [16, 16, 16, 16, 16, 16, 31],
3571
+ M: [17, 27, 21, 17, 17, 17, 17],
3572
+ N: [17, 25, 21, 19, 17, 17, 17],
3573
+ O: [14, 17, 17, 17, 17, 17, 14],
3574
+ P: [30, 17, 17, 30, 16, 16, 16],
3575
+ Q: [14, 17, 17, 17, 21, 18, 13],
3576
+ R: [30, 17, 17, 30, 20, 18, 17],
3577
+ S: [15, 16, 16, 14, 1, 1, 30],
3578
+ T: [31, 4, 4, 4, 4, 4, 4],
3579
+ U: [17, 17, 17, 17, 17, 17, 14],
3580
+ V: [17, 17, 17, 17, 17, 10, 4],
3581
+ W: [17, 17, 17, 17, 21, 27, 17],
3582
+ X: [17, 17, 10, 4, 10, 17, 17],
3583
+ Y: [17, 17, 10, 4, 4, 4, 4],
3584
+ Z: [31, 1, 2, 4, 8, 16, 31],
3585
+ "[": [14, 8, 8, 8, 8, 8, 14],
3586
+ "]": [14, 2, 2, 2, 2, 2, 14]
3587
+ };
3588
+ function glyphRows(ch) {
3589
+ const direct = FONT[ch];
3590
+ if (direct) return direct;
3591
+ const upper = ch.toUpperCase();
3592
+ return FONT[upper] ?? FONT["?"] ?? [0, 0, 0, 0, 0, 0, 0];
3593
+ }
3594
+
3595
+ // src/plot/renderers/png.ts
3596
+ function isNodeEnvironment() {
3597
+ return typeof process !== "undefined" && typeof process.versions !== "undefined" && typeof process.versions.node !== "undefined";
3598
+ }
3599
+ function u32be(n) {
3600
+ return Uint8Array.of(n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255);
3601
+ }
3602
+ function ascii4(s) {
3603
+ return Uint8Array.of(
3604
+ s.charCodeAt(0) & 255,
3605
+ s.charCodeAt(1) & 255,
3606
+ s.charCodeAt(2) & 255,
3607
+ s.charCodeAt(3) & 255
3608
+ );
3609
+ }
3610
+ function concatBytes(chunks) {
3611
+ let total = 0;
3612
+ for (const c of chunks) total += c.length;
3613
+ const out = new Uint8Array(total);
3614
+ let o = 0;
3615
+ for (const c of chunks) {
3616
+ out.set(c, o);
3617
+ o += c.length;
3618
+ }
3619
+ return out;
3620
+ }
3621
+ function crc32(buf) {
3622
+ let crc = 4294967295;
3623
+ for (let i = 0; i < buf.length; i++) {
3624
+ let x = (crc ^ (buf[i] ?? 0)) & 255;
3625
+ for (let k = 0; k < 8; k++) {
3626
+ x = (x & 1) !== 0 ? 3988292384 ^ x >>> 1 : x >>> 1;
3627
+ }
3628
+ crc = crc >>> 8 ^ x;
3629
+ }
3630
+ return (crc ^ 4294967295) >>> 0;
3631
+ }
3632
+ function pngChunk(type, data) {
3633
+ const t = ascii4(type);
3634
+ const len = u32be(data.length);
3635
+ const crc = u32be(crc32(concatBytes([t, data])));
3636
+ return concatBytes([len, t, data, crc]);
3637
+ }
3638
+ function deflateUncompressed(data) {
3639
+ const maxBlockSize = 65535;
3640
+ const numBlocks = Math.ceil(data.length / maxBlockSize);
3641
+ let totalSize = 0;
3642
+ for (let i = 0; i < numBlocks; i++) {
3643
+ const blockStart = i * maxBlockSize;
3644
+ const blockSize = Math.min(maxBlockSize, data.length - blockStart);
3645
+ totalSize += 5 + blockSize;
3646
+ }
3647
+ const output = new Uint8Array(2 + totalSize + 4);
3648
+ let outPos = 0;
3649
+ output[outPos++] = 120;
3650
+ output[outPos++] = 1;
3651
+ for (let i = 0; i < numBlocks; i++) {
3652
+ const blockStart = i * maxBlockSize;
3653
+ const blockSize = Math.min(maxBlockSize, data.length - blockStart);
3654
+ const isLast = i === numBlocks - 1;
3655
+ output[outPos++] = isLast ? 1 : 0;
3656
+ output[outPos++] = blockSize & 255;
3657
+ output[outPos++] = blockSize >> 8 & 255;
3658
+ const nlen = ~blockSize & 65535;
3659
+ output[outPos++] = nlen & 255;
3660
+ output[outPos++] = nlen >> 8 & 255;
3661
+ output.set(data.subarray(blockStart, blockStart + blockSize), outPos);
3662
+ outPos += blockSize;
3663
+ }
3664
+ let a = 1;
3665
+ let b = 0;
3666
+ for (let i = 0; i < data.length; i++) {
3667
+ a = (a + (data[i] ?? 0)) % 65521;
3668
+ b = (b + a) % 65521;
3669
+ }
3670
+ const adler32 = (b << 16 | a) >>> 0;
3671
+ output[outPos++] = adler32 >>> 24 & 255;
3672
+ output[outPos++] = adler32 >>> 16 & 255;
3673
+ output[outPos++] = adler32 >>> 8 & 255;
3674
+ output[outPos++] = adler32 & 255;
3675
+ return output;
3676
+ }
3677
+ async function pngEncodeRGBA(width, height, rgba) {
3678
+ assertPositiveInt("width", width);
3679
+ assertPositiveInt("height", height);
3680
+ if (rgba.length !== width * height * 4)
3681
+ throw new InvalidParameterError("RGBA buffer has incorrect length", "rgba", rgba.length);
3682
+ const signature = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10);
3683
+ const ihdrData = concatBytes([u32be(width), u32be(height), Uint8Array.of(8, 6, 0, 0, 0)]);
3684
+ const ihdr = pngChunk("IHDR", ihdrData);
3685
+ const stride = width * 4;
3686
+ const raw = new Uint8Array(height * (1 + stride));
3687
+ for (let y = 0; y < height; y++) {
3688
+ const rowOff = y * (1 + stride);
3689
+ raw[rowOff] = 0;
3690
+ raw.set(rgba.subarray(y * stride, y * stride + stride), rowOff + 1);
3691
+ }
3692
+ let compressed;
3693
+ if (isNodeEnvironment()) {
3694
+ try {
3695
+ const zlib = await import('zlib');
3696
+ compressed = typeof zlib.deflateSync === "function" ? zlib.deflateSync(raw, { level: 9 }) : deflateUncompressed(raw);
3697
+ } catch {
3698
+ compressed = deflateUncompressed(raw);
3699
+ }
3700
+ } else {
3701
+ compressed = deflateUncompressed(raw);
3702
+ }
3703
+ const idat = pngChunk("IDAT", compressed);
3704
+ const iend = pngChunk("IEND", new Uint8Array(0));
3705
+ return concatBytes([signature, ihdr, idat, iend]);
3706
+ }
3707
+ function isNodeEnvironment_export() {
3708
+ return isNodeEnvironment();
3709
+ }
3710
+
3711
+ // src/plot/figure/Figure.ts
3712
+ var Figure = class {
3713
+ width;
3714
+ height;
3715
+ background;
3716
+ axesList;
3717
+ constructor(options = {}) {
3718
+ this.width = options.width ?? 640;
3719
+ this.height = options.height ?? 480;
3720
+ assertPositiveInt("Figure.width", this.width);
3721
+ assertPositiveInt("Figure.height", this.height);
3722
+ if (this.width > 32768 || this.height > 32768) {
3723
+ throw new InvalidParameterError(
3724
+ "Figure dimensions too large. Maximum is 32,768 pixels.",
3725
+ "width/height",
3726
+ { width: this.width, height: this.height }
3727
+ );
3728
+ }
3729
+ this.background = normalizeColor(options.background, "#ffffff");
3730
+ this.axesList = [];
3731
+ }
3732
+ /**
3733
+ * Add a new axes to the figure.
3734
+ * @param options - Axes layout options
3735
+ */
3736
+ addAxes(options = {}) {
3737
+ const ax = new Axes(this, options);
3738
+ this.axesList.push(ax);
3739
+ return ax;
3740
+ }
3741
+ /**
3742
+ * Render this figure to SVG.
3743
+ */
3744
+ renderSVG() {
3745
+ const elements = [];
3746
+ elements.push(
3747
+ `<rect x="0" y="0" width="${this.width}" height="${this.height}" fill="${escapeXml(this.background)}" />`
3748
+ );
3749
+ for (const ax of this.axesList) ax.renderSVGInto(elements);
3750
+ const svg = `<?xml version="1.0" encoding="UTF-8"?>
3751
+ <svg xmlns="http://www.w3.org/2000/svg" width="${this.width}" height="${this.height}" viewBox="0 0 ${this.width} ${this.height}">
3752
+ ${elements.join("\n")}
3753
+ </svg>`;
3754
+ return { kind: "svg", svg };
3755
+ }
3756
+ /**
3757
+ * Render this figure to PNG (Node.js only).
3758
+ *
3759
+ * Note: PNG text rendering uses a built-in bitmap font for basic ASCII.
3760
+ * Unsupported characters are rendered as "?".
3761
+ */
3762
+ async renderPNG() {
3763
+ if (!isNodeEnvironment_export()) {
3764
+ throw new NotImplementedError(
3765
+ "PNG rendering is only available in Node.js environments. Use renderSVG() for browser compatibility, or run in Node.js to generate PNG files."
3766
+ );
3767
+ }
3768
+ const canvas = new RasterCanvas(this.width, this.height);
3769
+ const bg = parseHexColorToRGBA(this.background);
3770
+ canvas.clearRGBA(bg.r, bg.g, bg.b, bg.a);
3771
+ for (const ax of this.axesList) ax.renderRasterInto(canvas);
3772
+ const bytes = await pngEncodeRGBA(this.width, this.height, canvas.data);
3773
+ return { kind: "png", width: this.width, height: this.height, bytes };
3774
+ }
3775
+ };
3776
+
3777
+ // src/plot/figure/state.ts
3778
+ var _currentFigure = null;
3779
+ var _currentAxes = null;
3780
+ function gcf() {
3781
+ if (!_currentFigure) {
3782
+ _currentFigure = new Figure({ width: 320, height: 240 });
3783
+ }
3784
+ return _currentFigure;
3785
+ }
3786
+ function gca() {
3787
+ const fig = gcf();
3788
+ if (_currentAxes && fig.axesList.includes(_currentAxes)) return _currentAxes;
3789
+ if (fig.axesList.length === 0) {
3790
+ _currentAxes = fig.addAxes();
3791
+ return _currentAxes;
3792
+ }
3793
+ const firstAxes = fig.axesList[0];
3794
+ if (!firstAxes) {
3795
+ _currentAxes = fig.addAxes();
3796
+ return _currentAxes;
3797
+ }
3798
+ _currentAxes = firstAxes;
3799
+ return _currentAxes;
3800
+ }
3801
+ function figure(options = {}) {
3802
+ _currentFigure = new Figure({
3803
+ width: options.width ?? 320,
3804
+ height: options.height ?? 240,
3805
+ ...options.background !== void 0 && { background: options.background }
3806
+ });
3807
+ _currentAxes = _currentFigure.addAxes();
3808
+ return _currentFigure;
3809
+ }
3810
+ function subplot(rows, cols, index, options = {}) {
3811
+ if (!Number.isFinite(rows) || Math.trunc(rows) !== rows || rows <= 0) {
3812
+ throw new InvalidParameterError(
3813
+ `rows must be a positive integer; received ${rows}`,
3814
+ "rows",
3815
+ rows
3816
+ );
3817
+ }
3818
+ if (!Number.isFinite(cols) || Math.trunc(cols) !== cols || cols <= 0) {
3819
+ throw new InvalidParameterError(
3820
+ `cols must be a positive integer; received ${cols}`,
3821
+ "cols",
3822
+ cols
3823
+ );
3824
+ }
3825
+ const total = rows * cols;
3826
+ if (total > 1e4) {
3827
+ throw new InvalidParameterError(
3828
+ `Subplot grid too large (${rows}\xD7${cols}=${total}). Maximum is 10,000 subplots.`,
3829
+ "rows*cols",
3830
+ total
3831
+ );
3832
+ }
3833
+ if (!Number.isFinite(index) || Math.trunc(index) !== index || index < 1 || index > total) {
3834
+ throw new InvalidParameterError(
3835
+ `index must be in [1, ${total}]; received ${index}`,
3836
+ "index",
3837
+ index
3838
+ );
3839
+ }
3840
+ const fig = gcf();
3841
+ const idx0 = index - 1;
3842
+ const row = Math.floor(idx0 / cols);
3843
+ const col = idx0 % cols;
3844
+ const cellW = fig.width / cols;
3845
+ const cellH = fig.height / rows;
3846
+ const viewport = {
3847
+ x: col * cellW,
3848
+ y: row * cellH,
3849
+ width: cellW,
3850
+ height: cellH
3851
+ };
3852
+ const ax = fig.addAxes({ ...options, viewport });
3853
+ _currentAxes = ax;
3854
+ return ax;
3855
+ }
3856
+
3857
+ // src/plot/index.ts
3858
+ function show(options = {}) {
3859
+ const fig = options.figure ?? gca().fig;
3860
+ if (options.format === "png") return fig.renderPNG();
3861
+ return fig.renderSVG();
3862
+ }
3863
+ async function saveFig(path, options = {}) {
3864
+ if (!path || path.trim().length === 0) {
3865
+ throw new InvalidParameterError("path must be a non-empty string", "path", path);
3866
+ }
3867
+ const dotIndex = path.lastIndexOf(".");
3868
+ const ext = dotIndex > 0 ? path.slice(dotIndex + 1).toLowerCase() : void 0;
3869
+ const fmt = options.format ?? (ext === "png" ? "png" : "svg");
3870
+ if (ext && ext !== fmt) {
3871
+ throw new InvalidParameterError(
3872
+ `File extension .${ext} does not match format ${fmt}`,
3873
+ "path",
3874
+ path
3875
+ );
3876
+ }
3877
+ const fig = options.figure ?? gca().fig;
3878
+ if (fmt === "png") {
3879
+ const { writeFile } = await import('fs/promises');
3880
+ const png = await fig.renderPNG();
3881
+ await writeFile(path, png.bytes);
3882
+ } else {
3883
+ const { writeFile } = await import('fs/promises');
3884
+ const svg = fig.renderSVG();
3885
+ await writeFile(path, svg.svg, "utf-8");
3886
+ }
3887
+ }
3888
+ function plot(x, y, options = {}) {
3889
+ gca().plot(x, y, options);
3890
+ }
3891
+ function scatter(x, y, options = {}) {
3892
+ gca().scatter(x, y, options);
3893
+ }
3894
+ function bar(x, height, options = {}) {
3895
+ gca().bar(x, height, options);
3896
+ }
3897
+ function barh(y, width, options = {}) {
3898
+ gca().barh(y, width, options);
3899
+ }
3900
+ function hist(x, bins = 10, options = {}) {
3901
+ gca().hist(x, bins, options);
3902
+ }
3903
+ function boxplot(data, options = {}) {
3904
+ gca().boxplot(data, options);
3905
+ }
3906
+ function violinplot(data, options = {}) {
3907
+ gca().violinplot(data, options);
3908
+ }
3909
+ function pie(values, labels, options = {}) {
3910
+ gca().pie(values, labels, options);
3911
+ }
3912
+ function legend(options = {}) {
3913
+ gca().legend(options);
3914
+ }
3915
+ function heatmap(data, options = {}) {
3916
+ gca().heatmap(data, options);
3917
+ }
3918
+ function imshow(data, options = {}) {
3919
+ gca().imshow(data, options);
3920
+ }
3921
+ function contour(X, Y, Z, options = {}) {
3922
+ gca().contour(X, Y, Z, options);
3923
+ }
3924
+ function contourf(X, Y, Z, options = {}) {
3925
+ gca().contourf(X, Y, Z, options);
3926
+ }
3927
+ function plotConfusionMatrix(cm, labels, options = {}) {
3928
+ const ax = gca();
3929
+ ax.heatmap(cm, options);
3930
+ if (cm.ndim === 2) {
3931
+ const rows = cm.shape[0] ?? 0;
3932
+ const cols = cm.shape[1] ?? 0;
3933
+ ax.setTitle("Confusion Matrix");
3934
+ ax.setXLabel("Predicted");
3935
+ ax.setYLabel("Actual");
3936
+ if (labels && labels.length < Math.max(rows, cols)) {
3937
+ throw new InvalidParameterError(
3938
+ `labels length must be >= ${Math.max(rows, cols)}; received ${labels.length}`,
3939
+ "labels",
3940
+ labels
3941
+ );
3942
+ }
3943
+ if (labels && rows > 0 && cols > 0) {
3944
+ const xLabels = labels.slice(0, cols);
3945
+ const yLabels = labels.slice(0, rows);
3946
+ const xValues = xLabels.map((_, i) => i + 0.5);
3947
+ const yValues = yLabels.map((_, i) => i + 0.5);
3948
+ ax.setXTicks(xValues, xLabels);
3949
+ ax.setYTicks(yValues, yLabels);
3950
+ }
3951
+ }
3952
+ }
3953
+ function plotRocCurve(fpr, tpr, auc, options = {}) {
3954
+ const ax = gca();
3955
+ ax.plot(fpr, tpr, {
3956
+ ...options,
3957
+ color: options.color ?? "#1f77b4",
3958
+ label: options.label ?? "ROC"
3959
+ });
3960
+ ax.plot(tensor([0, 1]), tensor([0, 1]), {
3961
+ color: "#999999",
3962
+ linewidth: 1,
3963
+ label: "Chance"
3964
+ });
3965
+ if (auc !== void 0) {
3966
+ ax.setTitle(`ROC Curve (AUC = ${auc.toFixed(3)})`);
3967
+ } else {
3968
+ ax.setTitle("ROC Curve");
3969
+ }
3970
+ ax.setXLabel("False Positive Rate");
3971
+ ax.setYLabel("True Positive Rate");
3972
+ }
3973
+ function plotPrecisionRecallCurve(precision, recall, averagePrecision, options = {}) {
3974
+ const ax = gca();
3975
+ ax.plot(recall, precision, {
3976
+ ...options,
3977
+ color: options.color ?? "#1f77b4",
3978
+ label: options.label ?? "Precision-Recall"
3979
+ });
3980
+ if (averagePrecision !== void 0) {
3981
+ ax.setTitle(`Precision-Recall Curve (AP = ${averagePrecision.toFixed(3)})`);
3982
+ } else {
3983
+ ax.setTitle("Precision-Recall Curve");
3984
+ }
3985
+ ax.setXLabel("Recall");
3986
+ ax.setYLabel("Precision");
3987
+ }
3988
+ function plotLearningCurve(trainSizes, trainScores, valScores, options = {}) {
3989
+ const ax = gca();
3990
+ const trainColor = options.colors?.[0] ?? options.color ?? "#1f77b4";
3991
+ const valColor = options.colors?.[1] ?? options.color ?? "#ff7f0e";
3992
+ ax.plot(trainSizes, trainScores, {
3993
+ color: trainColor,
3994
+ label: "Training Score"
3995
+ });
3996
+ ax.plot(trainSizes, valScores, {
3997
+ color: valColor,
3998
+ label: "Validation Score"
3999
+ });
4000
+ ax.setTitle("Learning Curve");
4001
+ ax.setXLabel("Training Set Size");
4002
+ ax.setYLabel("Score");
4003
+ }
4004
+ function plotValidationCurve(paramRange, trainScores, valScores, options = {}) {
4005
+ const ax = gca();
4006
+ const trainColor = options.colors?.[0] ?? options.color ?? "#1f77b4";
4007
+ const valColor = options.colors?.[1] ?? options.color ?? "#ff7f0e";
4008
+ ax.plot(paramRange, trainScores, {
4009
+ color: trainColor,
4010
+ label: "Training Score"
4011
+ });
4012
+ ax.plot(paramRange, valScores, {
4013
+ color: valColor,
4014
+ label: "Validation Score"
4015
+ });
4016
+ ax.setTitle("Validation Curve");
4017
+ ax.setXLabel("Parameter Value");
4018
+ ax.setYLabel("Score");
4019
+ }
4020
+ function plotDecisionBoundary(X, y, model, options = {}) {
4021
+ if (X.dtype === "string") {
4022
+ throw new InvalidParameterError("plotDecisionBoundary: X must be numeric", "X", X.dtype);
4023
+ }
4024
+ if (X.ndim !== 2 || (X.shape[1] ?? 0) !== 2) {
4025
+ throw new ShapeError("plotDecisionBoundary: X must be shape [n, 2]");
4026
+ }
4027
+ const n = X.shape[0] ?? 0;
4028
+ if (n === 0) {
4029
+ throw new InvalidParameterError("plotDecisionBoundary: X must have at least one row", "X", n);
4030
+ }
4031
+ if (y.ndim !== 1 || (y.shape[0] ?? -1) !== n) {
4032
+ throw new ShapeError("plotDecisionBoundary: y must be shape [n]");
4033
+ }
4034
+ const { data: xData } = tensorToFloat64Matrix2D(X);
4035
+ const x0 = new Float64Array(n);
4036
+ const x1 = new Float64Array(n);
4037
+ for (let i = 0; i < n; i++) {
4038
+ const vx = xData[i * 2] ?? NaN;
4039
+ const vy = xData[i * 2 + 1] ?? NaN;
4040
+ if (!Number.isFinite(vx) || !Number.isFinite(vy)) {
4041
+ throw new InvalidParameterError("plotDecisionBoundary: X must be finite", "X", {
4042
+ index: i,
4043
+ x: vx,
4044
+ y: vy
4045
+ });
4046
+ }
4047
+ x0[i] = vx;
4048
+ x1[i] = vy;
4049
+ }
4050
+ const readLabel = (labelTensor, index) => {
4051
+ const value = labelTensor.at(index);
4052
+ if (typeof value === "string") return value;
4053
+ if (typeof value === "number") {
4054
+ if (!Number.isFinite(value)) {
4055
+ throw new InvalidParameterError("plotDecisionBoundary: invalid label value", "y", value);
4056
+ }
4057
+ return value;
4058
+ }
4059
+ if (typeof value === "bigint") return value;
4060
+ throw new InvalidParameterError("plotDecisionBoundary: invalid label value", "y", value);
4061
+ };
4062
+ const toFiniteNumber = (value) => {
4063
+ if (typeof value === "number") {
4064
+ if (!Number.isFinite(value)) {
4065
+ throw new InvalidParameterError(
4066
+ "plotDecisionBoundary: model.predict must return finite numeric scores",
4067
+ "predict",
4068
+ value
4069
+ );
4070
+ }
4071
+ return value;
4072
+ }
4073
+ if (typeof value === "bigint") {
4074
+ const n2 = Number(value);
4075
+ if (!Number.isFinite(n2)) {
4076
+ throw new InvalidParameterError(
4077
+ "plotDecisionBoundary: model.predict must return finite numeric scores",
4078
+ "predict",
4079
+ value
4080
+ );
4081
+ }
4082
+ return n2;
4083
+ }
4084
+ throw new InvalidParameterError(
4085
+ "plotDecisionBoundary: model.predict must return numeric scores",
4086
+ "predict",
4087
+ value
4088
+ );
4089
+ };
4090
+ const labelKey = (value) => `${typeof value}:${String(value)}`;
4091
+ let xMin = x0[0] ?? 0;
4092
+ let xMax = x0[0] ?? 0;
4093
+ let yMin = x1[0] ?? 0;
4094
+ let yMax = x1[0] ?? 0;
4095
+ for (let i = 1; i < n; i++) {
4096
+ const vx = x0[i] ?? 0;
4097
+ const vy = x1[i] ?? 0;
4098
+ if (vx < xMin) xMin = vx;
4099
+ if (vx > xMax) xMax = vx;
4100
+ if (vy < yMin) yMin = vy;
4101
+ if (vy > yMax) yMax = vy;
4102
+ }
4103
+ const marginX = xMax > xMin ? (xMax - xMin) * 0.05 : 1;
4104
+ const marginY = yMax > yMin ? (yMax - yMin) * 0.05 : 1;
4105
+ xMin -= marginX;
4106
+ xMax += marginX;
4107
+ yMin -= marginY;
4108
+ yMax += marginY;
4109
+ const gridResolution = 100;
4110
+ const gridCount = gridResolution * gridResolution;
4111
+ const gridFlat = new Float64Array(gridCount * 2);
4112
+ for (let gy = 0; gy < gridResolution; gy++) {
4113
+ const fy = yMin + (yMax - yMin) * gy / Math.max(1, gridResolution - 1);
4114
+ for (let gx = 0; gx < gridResolution; gx++) {
4115
+ const fx = xMin + (xMax - xMin) * gx / Math.max(1, gridResolution - 1);
4116
+ const idx = (gy * gridResolution + gx) * 2;
4117
+ gridFlat[idx] = fx;
4118
+ gridFlat[idx + 1] = fy;
4119
+ }
4120
+ }
4121
+ const gridTensor = tensor(gridFlat).reshape([gridCount, 2]);
4122
+ const predictions = model.predict(gridTensor);
4123
+ const predictedLabels = new Array(gridCount);
4124
+ if (predictions.ndim === 1) {
4125
+ if ((predictions.shape[0] ?? -1) !== gridCount) {
4126
+ throw new ShapeError("plotDecisionBoundary: model.predict must return [gridSize] labels");
4127
+ }
4128
+ for (let i = 0; i < gridCount; i++) {
4129
+ predictedLabels[i] = readLabel(predictions, i);
4130
+ }
4131
+ } else if (predictions.ndim === 2) {
4132
+ const rows = predictions.shape[0] ?? -1;
4133
+ const cols = predictions.shape[1] ?? -1;
4134
+ if (rows !== gridCount || cols <= 0) {
4135
+ throw new ShapeError("plotDecisionBoundary: model.predict must return [gridSize, k]");
4136
+ }
4137
+ for (let i = 0; i < rows; i++) {
4138
+ let bestCol = 0;
4139
+ let bestValue = toFiniteNumber(predictions.at(i, 0));
4140
+ for (let c = 1; c < cols; c++) {
4141
+ const value = toFiniteNumber(predictions.at(i, c));
4142
+ if (value > bestValue) {
4143
+ bestValue = value;
4144
+ bestCol = c;
4145
+ }
4146
+ }
4147
+ predictedLabels[i] = bestCol;
4148
+ }
4149
+ } else {
4150
+ throw new ShapeError("plotDecisionBoundary: model.predict output must be 1D or 2D");
4151
+ }
4152
+ const classIndex = /* @__PURE__ */ new Map();
4153
+ const classValues = [];
4154
+ for (let i = 0; i < n; i++) {
4155
+ const label = readLabel(y, i);
4156
+ const key = labelKey(label);
4157
+ if (!classIndex.has(key)) {
4158
+ classIndex.set(key, classValues.length);
4159
+ classValues.push(label);
4160
+ }
4161
+ }
4162
+ for (const label of predictedLabels) {
4163
+ const key = labelKey(label);
4164
+ if (!classIndex.has(key)) {
4165
+ classIndex.set(key, classValues.length);
4166
+ classValues.push(label);
4167
+ }
4168
+ }
4169
+ const boundaryMatrix = Array.from(
4170
+ { length: gridResolution },
4171
+ () => Array(gridResolution).fill(0)
4172
+ );
4173
+ for (let gy = 0; gy < gridResolution; gy++) {
4174
+ const row = boundaryMatrix[gy];
4175
+ if (!row) {
4176
+ throw new InvalidParameterError("plotDecisionBoundary: grid row access failed", "gy", gy);
4177
+ }
4178
+ for (let gx = 0; gx < gridResolution; gx++) {
4179
+ const label = predictedLabels[gy * gridResolution + gx];
4180
+ const mapped = label === void 0 ? 0 : classIndex.get(labelKey(label)) ?? 0;
4181
+ row[gx] = mapped;
4182
+ }
4183
+ }
4184
+ const ax = gca();
4185
+ ax.imshow(tensor(boundaryMatrix), {
4186
+ colormap: options.colormap ?? "grayscale",
4187
+ vmin: options.vmin ?? 0,
4188
+ vmax: options.vmax ?? Math.max(1, classValues.length - 1),
4189
+ extent: { xmin: xMin, xmax: xMax, ymin: yMin, ymax: yMax }
4190
+ });
4191
+ const defaultClassColors = [
4192
+ "#1f77b4",
4193
+ "#ff7f0e",
4194
+ "#2ca02c",
4195
+ "#d62728",
4196
+ "#9467bd",
4197
+ "#8c564b",
4198
+ "#e377c2",
4199
+ "#7f7f7f",
4200
+ "#bcbd22",
4201
+ "#17becf"
4202
+ ];
4203
+ const classColors = options.colors !== void 0 && options.colors.length > 0 ? options.colors : defaultClassColors;
4204
+ const byClass = /* @__PURE__ */ new Map();
4205
+ for (let i = 0; i < n; i++) {
4206
+ const label = readLabel(y, i);
4207
+ const key = labelKey(label);
4208
+ const labelText = String(label);
4209
+ const mapped = classIndex.get(key) ?? 0;
4210
+ const current = byClass.get(key) ?? {
4211
+ x: [],
4212
+ y: [],
4213
+ index: mapped,
4214
+ label: labelText
4215
+ };
4216
+ current.x.push(x0[i] ?? 0);
4217
+ current.y.push(x1[i] ?? 0);
4218
+ byClass.set(key, current);
4219
+ }
4220
+ for (const group of byClass.values()) {
4221
+ const color = classColors[group.index % classColors.length] ?? "#000000";
4222
+ ax.scatter(tensor(group.x), tensor(group.y), {
4223
+ color,
4224
+ size: options.size ?? 4,
4225
+ label: group.label
4226
+ });
4227
+ }
4228
+ ax.setTitle("Decision Boundary");
4229
+ ax.setXLabel("Feature 1");
4230
+ ax.setYLabel("Feature 2");
4231
+ }
4232
+
4233
+ export { Axes, Figure, bar, barh, boxplot, contour, contourf, figure, gca, heatmap, hist, imshow, legend, pie, plot, plotConfusionMatrix, plotDecisionBoundary, plotLearningCurve, plotPrecisionRecallCurve, plotRocCurve, plotValidationCurve, plot_exports, saveFig, scatter, show, subplot, violinplot };
4234
+ //# sourceMappingURL=chunk-ZXKBDFP3.js.map
4235
+ //# sourceMappingURL=chunk-ZXKBDFP3.js.map