@sliiu/present-renderer 0.0.0-stage → 0.1.1

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 (126) hide show
  1. package/COPYRIGHT +5 -0
  2. package/LICENSE +661 -0
  3. package/README.md +8 -2
  4. package/THIRD-PARTY-NOTICES.md +221 -0
  5. package/dist/cjs/arrow-geom.js +38 -0
  6. package/dist/cjs/canvas-backend.js +1368 -0
  7. package/dist/cjs/cell-borders.js +25 -0
  8. package/dist/cjs/chart-canvas.js +1059 -0
  9. package/dist/cjs/chart-ex-render.js +275 -0
  10. package/dist/cjs/chart-geom.js +313 -0
  11. package/dist/cjs/chart-svg.js +1110 -0
  12. package/dist/cjs/compose.js +74 -0
  13. package/dist/cjs/effect-clip.js +652 -0
  14. package/dist/cjs/entrance-hide.js +63 -0
  15. package/dist/cjs/font-stack.js +103 -0
  16. package/dist/cjs/hit-geom.js +50 -0
  17. package/dist/cjs/hybrid-backend.js +93 -0
  18. package/dist/cjs/hybrid-segments.js +54 -0
  19. package/dist/cjs/index.js +70 -0
  20. package/dist/cjs/linked-src.js +116 -0
  21. package/dist/cjs/metafile-preview.js +142 -0
  22. package/dist/cjs/model3d-frame.js +199 -0
  23. package/dist/cjs/model3d-render.js +78 -0
  24. package/dist/cjs/morph-transition.js +57 -0
  25. package/dist/cjs/node-affine.js +79 -0
  26. package/dist/cjs/ole-render.js +291 -0
  27. package/dist/cjs/package.json +1 -0
  28. package/dist/cjs/paint-canvas.js +701 -0
  29. package/dist/cjs/paint-svg.js +674 -0
  30. package/dist/cjs/scene-node.js +762 -0
  31. package/dist/cjs/selection.js +130 -0
  32. package/dist/cjs/shape-effects.js +66 -0
  33. package/dist/cjs/shape-front.js +38 -0
  34. package/dist/cjs/stage-chrome.js +15 -0
  35. package/dist/cjs/svg-backend.js +2172 -0
  36. package/dist/cjs/text-autofit.js +48 -0
  37. package/dist/cjs/text-hit.js +284 -0
  38. package/dist/cjs/text-layout.js +408 -0
  39. package/dist/cjs/text-shared.js +238 -0
  40. package/dist/cjs/text-stacked.js +82 -0
  41. package/dist/cjs/text-warp.js +377 -0
  42. package/dist/cjs/text-wordart.js +33 -0
  43. package/dist/cjs/video-frame.js +113 -0
  44. package/dist/cjs/webgl-model3d-backend.js +285 -0
  45. package/dist/cjs/webgl-shape-backend.js +360 -0
  46. package/dist/esm/arrow-geom.js +33 -0
  47. package/dist/esm/canvas-backend.js +1363 -0
  48. package/dist/esm/cell-borders.js +21 -0
  49. package/dist/esm/chart-canvas.js +1056 -0
  50. package/dist/esm/chart-ex-render.js +261 -0
  51. package/dist/esm/chart-geom.js +296 -0
  52. package/dist/esm/chart-svg.js +1107 -0
  53. package/dist/esm/compose.js +71 -0
  54. package/dist/esm/effect-clip.js +648 -0
  55. package/dist/esm/entrance-hide.js +59 -0
  56. package/dist/esm/font-stack.js +100 -0
  57. package/dist/esm/hit-geom.js +45 -0
  58. package/dist/esm/hybrid-backend.js +89 -0
  59. package/dist/esm/hybrid-segments.js +51 -0
  60. package/dist/esm/index.js +24 -0
  61. package/dist/esm/linked-src.js +109 -0
  62. package/dist/esm/metafile-preview.js +135 -0
  63. package/dist/esm/model3d-frame.js +158 -0
  64. package/dist/esm/model3d-render.js +73 -0
  65. package/dist/esm/morph-transition.js +54 -0
  66. package/dist/esm/node-affine.js +71 -0
  67. package/dist/esm/ole-render.js +286 -0
  68. package/dist/esm/paint-canvas.js +693 -0
  69. package/dist/esm/paint-svg.js +670 -0
  70. package/dist/esm/scene-node.js +750 -0
  71. package/dist/esm/selection.js +122 -0
  72. package/dist/esm/shape-effects.js +60 -0
  73. package/dist/esm/shape-front.js +35 -0
  74. package/dist/esm/stage-chrome.js +12 -0
  75. package/dist/esm/svg-backend.js +2168 -0
  76. package/dist/esm/text-autofit.js +44 -0
  77. package/dist/esm/text-hit.js +280 -0
  78. package/dist/esm/text-layout.js +400 -0
  79. package/dist/esm/text-shared.js +221 -0
  80. package/dist/esm/text-stacked.js +78 -0
  81. package/dist/esm/text-warp.js +371 -0
  82. package/dist/esm/text-wordart.js +30 -0
  83. package/dist/esm/video-frame.js +109 -0
  84. package/dist/esm/webgl-model3d-backend.js +248 -0
  85. package/dist/esm/webgl-shape-backend.js +323 -0
  86. package/dist/types/arrow-geom.d.ts +10 -0
  87. package/dist/types/canvas-backend.d.ts +55 -0
  88. package/dist/types/cell-borders.d.ts +9 -0
  89. package/dist/types/chart-canvas.d.ts +2 -0
  90. package/dist/types/chart-ex-render.d.ts +79 -0
  91. package/dist/types/chart-geom.d.ts +112 -0
  92. package/dist/types/chart-svg.d.ts +2 -0
  93. package/dist/types/compose.d.ts +2 -0
  94. package/dist/types/effect-clip.d.ts +10 -0
  95. package/dist/types/entrance-hide.d.ts +16 -0
  96. package/dist/types/font-stack.d.ts +9 -0
  97. package/dist/types/hit-geom.d.ts +15 -0
  98. package/dist/types/hybrid-backend.d.ts +21 -0
  99. package/dist/types/hybrid-segments.d.ts +11 -0
  100. package/dist/types/index.d.ts +24 -0
  101. package/dist/types/linked-src.d.ts +13 -0
  102. package/dist/types/metafile-preview.d.ts +11 -0
  103. package/dist/types/model3d-frame.d.ts +8 -0
  104. package/dist/types/model3d-render.d.ts +4 -0
  105. package/dist/types/morph-transition.d.ts +15 -0
  106. package/dist/types/node-affine.d.ts +22 -0
  107. package/dist/types/ole-render.d.ts +7 -0
  108. package/dist/types/paint-canvas.d.ts +22 -0
  109. package/dist/types/paint-svg.d.ts +19 -0
  110. package/dist/types/scene-node.d.ts +128 -0
  111. package/dist/types/selection.d.ts +22 -0
  112. package/dist/types/shape-effects.d.ts +13 -0
  113. package/dist/types/shape-front.d.ts +4 -0
  114. package/dist/types/stage-chrome.d.ts +8 -0
  115. package/dist/types/svg-backend.d.ts +88 -0
  116. package/dist/types/text-autofit.d.ts +7 -0
  117. package/dist/types/text-hit.d.ts +7 -0
  118. package/dist/types/text-layout.d.ts +35 -0
  119. package/dist/types/text-shared.d.ts +86 -0
  120. package/dist/types/text-stacked.d.ts +11 -0
  121. package/dist/types/text-warp.d.ts +37 -0
  122. package/dist/types/text-wordart.d.ts +18 -0
  123. package/dist/types/video-frame.d.ts +2 -0
  124. package/dist/types/webgl-model3d-backend.d.ts +25 -0
  125. package/dist/types/webgl-shape-backend.d.ts +33 -0
  126. package/package.json +43 -4
@@ -0,0 +1,1059 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.drawChartCanvas = drawChartCanvas;
4
+ const chart_ex_render_1 = require("./chart-ex-render");
5
+ const chart_geom_1 = require("./chart-geom");
6
+ /** Text / line colours of the chart being drawn; set once per `drawChartCanvas` (drawing is synchronous). */
7
+ let ts = (0, chart_geom_1.chartTextStyle)({});
8
+ function drawChartCanvas(ctx, shape) {
9
+ const { x, y, w, h } = shape.bbox;
10
+ const def = shape.chart;
11
+ ts = (0, chart_geom_1.chartTextStyle)(def);
12
+ const padTop = def.title ? 28 : 8;
13
+ const pads = (0, chart_geom_1.chartPadding)(def, ts.size);
14
+ const padBottom = pads.bottom;
15
+ const padLeft = 40;
16
+ const padRight = 12;
17
+ ctx.save();
18
+ ctx.fillStyle = ts.text;
19
+ if (def.title) {
20
+ ctx.font = `600 ${ts.size + 1}px sans-serif`;
21
+ ctx.textBaseline = "top";
22
+ ctx.textAlign = "center";
23
+ ctx.fillText(def.title, x + w / 2, y + 6);
24
+ }
25
+ const plot = {
26
+ x: x + padLeft,
27
+ y: y + padTop,
28
+ w: w - padLeft - padRight,
29
+ h: h - padTop - padBottom,
30
+ };
31
+ if (hasCombo(def)) {
32
+ drawCombo(ctx, def, plot);
33
+ }
34
+ else {
35
+ switch (def.type) {
36
+ case "bar":
37
+ case "column":
38
+ drawBar(ctx, def, plot);
39
+ break;
40
+ case "line":
41
+ drawLine(ctx, def, plot);
42
+ break;
43
+ case "pie":
44
+ case "doughnut":
45
+ drawPie(ctx, def, plot);
46
+ break;
47
+ case "area":
48
+ drawArea(ctx, def, plot);
49
+ break;
50
+ case "scatter":
51
+ drawScatter(ctx, def, plot);
52
+ break;
53
+ case "bubble":
54
+ drawBubble(ctx, def, plot);
55
+ break;
56
+ case "radar":
57
+ drawRadar(ctx, def, plot);
58
+ break;
59
+ case "stock":
60
+ drawStock(ctx, def, plot);
61
+ break;
62
+ case "surface":
63
+ drawSurface(ctx, def, plot);
64
+ break;
65
+ case "sunburst":
66
+ drawSunburst(ctx, def, plot);
67
+ break;
68
+ case "treemap":
69
+ drawTreemap(ctx, def, plot);
70
+ break;
71
+ case "waterfall":
72
+ drawWaterfall(ctx, def, plot);
73
+ break;
74
+ case "funnel":
75
+ drawFunnel(ctx, def, plot);
76
+ break;
77
+ case "pareto":
78
+ drawPareto(ctx, def, plot);
79
+ break;
80
+ case "histogram":
81
+ drawHistogram(ctx, def, plot);
82
+ break;
83
+ case "boxWhisker":
84
+ drawBoxWhisker(ctx, def, plot);
85
+ break;
86
+ case "region":
87
+ drawRegion(ctx, def, plot);
88
+ break;
89
+ default:
90
+ drawChartExFallback(ctx, def, plot);
91
+ break;
92
+ }
93
+ }
94
+ if (!hasCombo(def))
95
+ overlayGuides(ctx, def, plot);
96
+ overlayDataLabels(ctx, def, plot);
97
+ if (def.legend)
98
+ drawLegend(ctx, def, { x, y: y + h - pads.legend, w, h: pads.legend });
99
+ ctx.restore();
100
+ }
101
+ function drawAxes(ctx, plot, ticks, categories, isBar) {
102
+ const min = ticks[0];
103
+ const max = ticks[ticks.length - 1];
104
+ ctx.strokeStyle = ts.axis;
105
+ ctx.lineWidth = 1;
106
+ ctx.beginPath();
107
+ ctx.moveTo(plot.x, plot.y + plot.h);
108
+ ctx.lineTo(plot.x + plot.w, plot.y + plot.h);
109
+ ctx.moveTo(plot.x, plot.y);
110
+ ctx.lineTo(plot.x, plot.y + plot.h);
111
+ ctx.stroke();
112
+ ctx.font = `${ts.size}px sans-serif`;
113
+ ctx.fillStyle = ts.muted;
114
+ // barDir="bar" (§21.2.2.17) turns the value axis along the bottom: ticks run left→right with
115
+ // vertical gridlines, and the left edge belongs to the category labels.
116
+ ctx.textBaseline = isBar ? "top" : "middle";
117
+ ctx.textAlign = isBar ? "center" : "right";
118
+ for (const t of ticks) {
119
+ const f = (t - min) / (max - min);
120
+ ctx.strokeStyle = ts.grid;
121
+ ctx.beginPath();
122
+ if (isBar) {
123
+ const nx = plot.x + f * plot.w;
124
+ ctx.moveTo(nx, plot.y);
125
+ ctx.lineTo(nx, plot.y + plot.h);
126
+ ctx.stroke();
127
+ ctx.fillText(String(t), nx, plot.y + plot.h + 4);
128
+ }
129
+ else {
130
+ const ny = plot.y + plot.h - f * plot.h;
131
+ ctx.moveTo(plot.x, ny);
132
+ ctx.lineTo(plot.x + plot.w, ny);
133
+ ctx.stroke();
134
+ ctx.fillText(String(t), plot.x - 6, ny);
135
+ }
136
+ }
137
+ if (!isBar && categories.length > 0) {
138
+ ctx.textAlign = "center";
139
+ ctx.textBaseline = "top";
140
+ const step = plot.w / categories.length;
141
+ categories.forEach((c, i) => {
142
+ ctx.fillText(c, plot.x + step * (i + 0.5), plot.y + plot.h + 4);
143
+ });
144
+ }
145
+ else if (isBar && categories.length > 0) {
146
+ ctx.textAlign = "right";
147
+ ctx.textBaseline = "middle";
148
+ const step = plot.h / categories.length;
149
+ categories.forEach((c, i) => {
150
+ ctx.fillText(c, plot.x - 6, plot.y + step * (i + 0.5));
151
+ });
152
+ }
153
+ }
154
+ function drawChartExFallback(ctx, def, plot) {
155
+ const categories = def.categories?.length
156
+ ? def.categories
157
+ : Array.from({ length: Math.max(1, def.series[0]?.values.length ?? 1) }, (_, i) => `Cat ${i + 1}`);
158
+ const series = def.series.length > 0
159
+ ? def.series
160
+ : [{ name: "Series 1", values: categories.map(() => 0) }];
161
+ const fallback = {
162
+ type: "column",
163
+ title: def.title,
164
+ categories,
165
+ series,
166
+ legend: def.legend,
167
+ colors: def.colors,
168
+ varyColors: def.varyColors ?? true,
169
+ };
170
+ drawBar(ctx, fallback, plot);
171
+ }
172
+ function drawTreemap(ctx, def, plot) {
173
+ for (const cell of (0, chart_ex_render_1.layoutTreemap)((0, chart_ex_render_1.normalizeChartExSlices)(def), plot)) {
174
+ ctx.fillStyle = cell.color;
175
+ ctx.fillRect(cell.x, cell.y, cell.w, cell.h);
176
+ ctx.strokeStyle = "#ffffff";
177
+ ctx.lineWidth = 1.5;
178
+ ctx.strokeRect(cell.x, cell.y, cell.w, cell.h);
179
+ if (cell.w > 36 && cell.h > 18) {
180
+ ctx.fillStyle = "#f8fafc";
181
+ ctx.font = `${ts.size}px sans-serif`;
182
+ ctx.textAlign = "left";
183
+ ctx.textBaseline = "top";
184
+ ctx.fillText(cell.label, cell.x + 6, cell.y + 4);
185
+ }
186
+ }
187
+ }
188
+ function drawSunburst(ctx, def, plot) {
189
+ for (const arc of (0, chart_ex_render_1.layoutSunburst)((0, chart_ex_render_1.normalizeChartExSlices)(def), plot)) {
190
+ ctx.beginPath();
191
+ ctx.arc(arc.cx, arc.cy, arc.r1, arc.a0, arc.a1);
192
+ ctx.arc(arc.cx, arc.cy, arc.r0, arc.a1, arc.a0, true);
193
+ ctx.closePath();
194
+ ctx.fillStyle = arc.color;
195
+ ctx.fill();
196
+ ctx.strokeStyle = "#ffffff";
197
+ ctx.lineWidth = 1;
198
+ ctx.stroke();
199
+ }
200
+ }
201
+ function drawWaterfall(ctx, def, plot) {
202
+ const bars = (0, chart_ex_render_1.layoutWaterfall)((0, chart_ex_render_1.normalizeChartExSlices)(def, { preserveSign: true }), plot);
203
+ for (const bar of bars) {
204
+ ctx.fillStyle = bar.kind === "decrease" ? "#E15759" : bar.color;
205
+ ctx.fillRect(bar.x, bar.y, bar.w, bar.h);
206
+ }
207
+ ctx.strokeStyle = ts.axis;
208
+ ctx.setLineDash([3, 2]);
209
+ for (let i = 0; i < bars.length - 1; i++) {
210
+ const a = bars[i];
211
+ const b = bars[i + 1];
212
+ ctx.beginPath();
213
+ ctx.moveTo(a.x + a.w, a.y);
214
+ ctx.lineTo(b.x, b.y);
215
+ ctx.stroke();
216
+ }
217
+ ctx.setLineDash([]);
218
+ }
219
+ function drawFunnel(ctx, def, plot) {
220
+ for (const seg of (0, chart_ex_render_1.layoutFunnel)((0, chart_ex_render_1.normalizeChartExSlices)(def), plot)) {
221
+ ctx.beginPath();
222
+ const [first, ...rest] = seg.points;
223
+ ctx.moveTo(first[0], first[1]);
224
+ for (const p of rest)
225
+ ctx.lineTo(p[0], p[1]);
226
+ ctx.closePath();
227
+ ctx.fillStyle = seg.color;
228
+ ctx.fill();
229
+ ctx.strokeStyle = "#ffffff";
230
+ ctx.lineWidth = 1.5;
231
+ ctx.stroke();
232
+ const cx = seg.points.reduce((s, p) => s + p[0], 0) / seg.points.length;
233
+ const cy = seg.points.reduce((s, p) => s + p[1], 0) / seg.points.length;
234
+ ctx.fillStyle = "#f8fafc";
235
+ ctx.font = `${ts.size}px sans-serif`;
236
+ ctx.textAlign = "center";
237
+ ctx.textBaseline = "middle";
238
+ ctx.fillText(seg.label, cx, cy);
239
+ }
240
+ }
241
+ function fillExtrudedRect(ctx, x, y, w, h, color, off) {
242
+ if (off && (off.dx || off.dy) && w > 0 && h > 0) {
243
+ const { dx, dy } = off;
244
+ ctx.beginPath();
245
+ ctx.moveTo(x, y);
246
+ ctx.lineTo(x + dx, y + dy);
247
+ ctx.lineTo(x + w + dx, y + dy);
248
+ ctx.lineTo(x + w, y);
249
+ ctx.closePath();
250
+ ctx.fillStyle = (0, chart_geom_1.shadeHex)(color, 1.18);
251
+ ctx.fill();
252
+ ctx.beginPath();
253
+ ctx.moveTo(x + w, y);
254
+ ctx.lineTo(x + w + dx, y + dy);
255
+ ctx.lineTo(x + w + dx, y + h + dy);
256
+ ctx.lineTo(x + w, y + h);
257
+ ctx.closePath();
258
+ ctx.fillStyle = (0, chart_geom_1.shadeHex)(color, 0.72);
259
+ ctx.fill();
260
+ }
261
+ ctx.fillStyle = color;
262
+ ctx.fillRect(x, y, w, h);
263
+ }
264
+ function drawColumnBarsCanvas(ctx, bars) {
265
+ for (const bar of bars) {
266
+ ctx.fillStyle = bar.color;
267
+ ctx.fillRect(bar.x, bar.y, bar.w, bar.h);
268
+ }
269
+ }
270
+ function drawPareto(ctx, def, plot) {
271
+ const { bars, line } = (0, chart_ex_render_1.layoutPareto)((0, chart_ex_render_1.normalizeChartExSlices)(def), plot);
272
+ drawColumnBarsCanvas(ctx, bars);
273
+ if (line.length >= 2) {
274
+ ctx.strokeStyle = "#E15759";
275
+ ctx.lineWidth = 2;
276
+ ctx.beginPath();
277
+ ctx.moveTo(line[0][0], line[0][1]);
278
+ for (let i = 1; i < line.length; i++)
279
+ ctx.lineTo(line[i][0], line[i][1]);
280
+ ctx.stroke();
281
+ ctx.fillStyle = "#E15759";
282
+ for (const [x, y] of line) {
283
+ ctx.beginPath();
284
+ ctx.arc(x, y, 3, 0, Math.PI * 2);
285
+ ctx.fill();
286
+ }
287
+ }
288
+ }
289
+ function drawHistogram(ctx, def, plot) {
290
+ drawColumnBarsCanvas(ctx, (0, chart_ex_render_1.layoutHistogram)((0, chart_ex_render_1.normalizeChartExSlices)(def), plot));
291
+ }
292
+ function drawBoxWhisker(ctx, def, plot) {
293
+ for (const box of (0, chart_ex_render_1.layoutBoxWhisker)((0, chart_ex_render_1.normalizeChartExSlices)(def), plot)) {
294
+ ctx.strokeStyle = "#374151";
295
+ ctx.lineWidth = 1;
296
+ ctx.beginPath();
297
+ ctx.moveTo(box.cx, box.whiskerLow);
298
+ ctx.lineTo(box.cx, box.whiskerHigh);
299
+ ctx.stroke();
300
+ const capW = box.boxW * 0.5;
301
+ for (const y of [box.whiskerLow, box.whiskerHigh]) {
302
+ ctx.beginPath();
303
+ ctx.moveTo(box.cx - capW / 2, y);
304
+ ctx.lineTo(box.cx + capW / 2, y);
305
+ ctx.stroke();
306
+ }
307
+ ctx.fillStyle = box.color;
308
+ ctx.globalAlpha = 0.75;
309
+ ctx.fillRect(box.cx - box.boxW / 2, Math.min(box.q1, box.q3), box.boxW, Math.abs(box.q3 - box.q1));
310
+ ctx.globalAlpha = 1;
311
+ ctx.strokeStyle = "#1f2937";
312
+ ctx.lineWidth = 2;
313
+ ctx.beginPath();
314
+ ctx.moveTo(box.cx - box.boxW / 2, box.median);
315
+ ctx.lineTo(box.cx + box.boxW / 2, box.median);
316
+ ctx.stroke();
317
+ }
318
+ }
319
+ function drawRegion(ctx, def, plot) {
320
+ for (const row of (0, chart_ex_render_1.layoutRegion)((0, chart_ex_render_1.normalizeChartExSlices)(def), plot)) {
321
+ ctx.fillStyle = row.color;
322
+ ctx.fillRect(row.x, row.y, row.w, row.h);
323
+ if (row.h > 14) {
324
+ ctx.fillStyle = "#f8fafc";
325
+ ctx.font = `${ts.size}px sans-serif`;
326
+ ctx.textAlign = "left";
327
+ ctx.textBaseline = "middle";
328
+ ctx.fillText(row.label, row.x + 6, row.y + row.h / 2);
329
+ }
330
+ }
331
+ }
332
+ function drawBar(ctx, def, plot, shared) {
333
+ const cats = def.categories ?? [];
334
+ const { isHoriz, stacked, ticks, tmin, tmax, groupStep, inner, barW, totals } = (0, chart_geom_1.barGeometry)(def, plot, shared?.ticks);
335
+ if (!shared?.skipAxes)
336
+ drawAxes(ctx, plot, ticks, cats, isHoriz);
337
+ const extrude = (0, chart_geom_1.chart3DOffset)(def);
338
+ for (let ci = 0; ci < cats.length; ci++) {
339
+ let stackPos = 0;
340
+ let stackNeg = 0;
341
+ for (let si = 0; si < def.series.length; si++) {
342
+ let v = def.series[si].values[ci] ?? 0;
343
+ if (def.percentStacked)
344
+ v = (totals[ci] ?? 0) > 0 ? v / totals[ci] : 0;
345
+ const color = def.series[si].color ?? "#3b82f6";
346
+ if (isHoriz) {
347
+ const x0 = plot.x + ((Math.min(0, tmin) - tmin) / (tmax - tmin)) * plot.w;
348
+ const len = (Math.abs(v) / (tmax - tmin)) * plot.w;
349
+ const yCat = plot.y + groupStep * ci + (groupStep - inner) / 2;
350
+ const ySub = stacked ? yCat : yCat + si * barW;
351
+ const left = stacked
352
+ ? plot.x + ((v >= 0 ? stackPos : stackNeg) - tmin) / (tmax - tmin) * plot.w
353
+ : x0 - (v < 0 ? len : 0);
354
+ fillExtrudedRect(ctx, v >= 0 ? left : left - len, ySub, len, barW, color, extrude);
355
+ }
356
+ else {
357
+ const xCat = plot.x + groupStep * ci + (groupStep - inner) / 2;
358
+ const xSub = stacked ? xCat : xCat + si * barW;
359
+ const baseY = plot.y + plot.h - ((0 - tmin) / (tmax - tmin)) * plot.h;
360
+ const hBar = (Math.abs(v) / (tmax - tmin)) * plot.h;
361
+ const yBar = stacked
362
+ ? plot.y + plot.h - (((v >= 0 ? stackPos : stackNeg) + (v >= 0 ? v : 0) - tmin) / (tmax - tmin)) * plot.h
363
+ : v >= 0
364
+ ? baseY - hBar
365
+ : baseY;
366
+ fillExtrudedRect(ctx, xSub, yBar, barW, hBar, color, extrude);
367
+ }
368
+ if (v >= 0)
369
+ stackPos += v;
370
+ else
371
+ stackNeg += v;
372
+ }
373
+ }
374
+ }
375
+ function drawLine(ctx, def, plot, shared) {
376
+ const cats = def.categories ?? [];
377
+ let yMin = 0;
378
+ let yMax = 1;
379
+ for (const s of def.series)
380
+ for (const v of s.values) {
381
+ if (v > yMax)
382
+ yMax = v;
383
+ if (v < yMin)
384
+ yMin = v;
385
+ }
386
+ ({ yMin, yMax } = (0, chart_geom_1.applyValueAxisScale)(yMin, yMax, def));
387
+ const ticks = shared?.ticks ?? (0, chart_geom_1.niceTicks)(yMin, yMax, 5);
388
+ if (!shared?.skipAxes)
389
+ drawAxes(ctx, plot, ticks, cats, false);
390
+ const tmin = ticks[0];
391
+ const tmax = ticks[ticks.length - 1];
392
+ const step = cats.length > 1 ? plot.w / (cats.length - 1) : plot.w;
393
+ const extrude = (0, chart_geom_1.chart3DOffset)(def);
394
+ const layers = extrude && (extrude.dx || extrude.dy) ? 5 : 1;
395
+ for (const s of def.series) {
396
+ const pts = [];
397
+ s.values.forEach((v, i) => {
398
+ const xx = plot.x + step * (cats.length > 1 ? i : 0.5);
399
+ const yy = plot.y + plot.h - ((v - tmin) / (tmax - tmin)) * plot.h;
400
+ pts.push({ x: xx, y: yy });
401
+ });
402
+ const color = s.color ?? "#3b82f6";
403
+ for (let layer = layers - 1; layer >= 0; layer--) {
404
+ const t = layers === 1 ? 0 : layer / (layers - 1);
405
+ const ox = extrude ? extrude.dx * t : 0;
406
+ const oy = extrude ? extrude.dy * t : 0;
407
+ ctx.strokeStyle = layer === 0 ? color : (0, chart_geom_1.shadeHex)(color, 0.55 + 0.08 * (1 - t));
408
+ ctx.lineWidth = layer === 0 ? 2 : 3;
409
+ ctx.beginPath();
410
+ pts.forEach((p, i) => {
411
+ if (i === 0)
412
+ ctx.moveTo(p.x + ox, p.y + oy);
413
+ else
414
+ ctx.lineTo(p.x + ox, p.y + oy);
415
+ });
416
+ ctx.stroke();
417
+ }
418
+ if (def.markers) {
419
+ ctx.fillStyle = color;
420
+ for (const p of pts) {
421
+ ctx.beginPath();
422
+ ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
423
+ ctx.fill();
424
+ }
425
+ }
426
+ }
427
+ }
428
+ function fillPieSlice(ctx, cx, cy, r, ir, start, end, color) {
429
+ ctx.fillStyle = color;
430
+ ctx.beginPath();
431
+ if (ir <= 0)
432
+ ctx.moveTo(cx, cy);
433
+ ctx.arc(cx, cy, r, start, end);
434
+ if (ir > 0)
435
+ ctx.arc(cx, cy, ir, end, start, true);
436
+ ctx.closePath();
437
+ ctx.fill();
438
+ }
439
+ function drawPie(ctx, def, plot) {
440
+ const s0 = def.series[0];
441
+ if (!s0)
442
+ return;
443
+ const total = s0.values.reduce((a, b) => a + Math.abs(b), 0);
444
+ if (total === 0)
445
+ return;
446
+ const cx0 = plot.x + plot.w / 2;
447
+ const cy0 = plot.y + plot.h / 2;
448
+ const r = Math.min(plot.w, plot.h) / 2 - 4;
449
+ const ir = def.type === "doughnut" ? r * (def.holeRatio ?? 0.5) : 0;
450
+ const palette = s0.perSliceColors ?? def.colors ?? [];
451
+ const explode = ((s0.explosion ?? 0) / 100) * r;
452
+ const off = (0, chart_geom_1.chart3DOffset)(def);
453
+ const layers = off && (off.dx || off.dy) ? 6 : 1;
454
+ for (let layer = layers - 1; layer >= 0; layer--) {
455
+ const t = layers === 1 ? 0 : layer / (layers - 1);
456
+ const ox = off ? off.dx * t : 0;
457
+ const oy = off ? off.dy * t : 0;
458
+ const shade = layer === 0 ? 1 : 0.55 + 0.08 * (1 - t);
459
+ let start = -Math.PI / 2 + ((def.startAngle ?? 0) * Math.PI) / 180;
460
+ for (let i = 0; i < s0.values.length; i++) {
461
+ const v = Math.abs(s0.values[i]);
462
+ const frac = v / total;
463
+ if (frac <= 0)
464
+ continue;
465
+ const end = start + frac * Math.PI * 2;
466
+ const mid = (start + end) / 2;
467
+ const cx = cx0 + ox + Math.cos(mid) * explode;
468
+ const cy = cy0 + oy + Math.sin(mid) * explode;
469
+ const color = palette[i] ?? s0.color ?? "#3b82f6";
470
+ fillPieSlice(ctx, cx, cy, r, ir, start, end, shade === 1 ? color : (0, chart_geom_1.shadeHex)(color, shade));
471
+ start = end;
472
+ }
473
+ }
474
+ }
475
+ function drawArea(ctx, def, plot, shared) {
476
+ const cats = def.categories ?? [];
477
+ let yMin = 0;
478
+ let yMax = 1;
479
+ for (const s of def.series)
480
+ for (const v of s.values) {
481
+ if (v > yMax)
482
+ yMax = v;
483
+ if (v < yMin)
484
+ yMin = v;
485
+ }
486
+ ({ yMin, yMax } = (0, chart_geom_1.applyValueAxisScale)(yMin, yMax, def));
487
+ const ticks = shared?.ticks ?? (0, chart_geom_1.niceTicks)(yMin, yMax, 5);
488
+ if (!shared?.skipAxes)
489
+ drawAxes(ctx, plot, ticks, cats, false);
490
+ const tmin = ticks[0];
491
+ const tmax = ticks[ticks.length - 1];
492
+ const step = cats.length > 1 ? plot.w / (cats.length - 1) : plot.w;
493
+ const baseY = plot.y + plot.h - ((0 - tmin) / (tmax - tmin)) * plot.h;
494
+ const extrude = (0, chart_geom_1.chart3DOffset)(def);
495
+ const layers = extrude && (extrude.dx || extrude.dy) ? 5 : 1;
496
+ for (const s of def.series) {
497
+ if (s.values.length < 2)
498
+ continue;
499
+ const color = s.color ?? "#3b82f6";
500
+ const pts = s.values.map((v, i) => {
501
+ const xx = plot.x + step * i;
502
+ const yy = plot.y + plot.h - ((v - tmin) / (tmax - tmin)) * plot.h;
503
+ return { x: xx, y: yy };
504
+ });
505
+ for (let layer = layers - 1; layer >= 0; layer--) {
506
+ const t = layers === 1 ? 0 : layer / (layers - 1);
507
+ const ox = extrude ? extrude.dx * t : 0;
508
+ const oy = extrude ? extrude.dy * t : 0;
509
+ ctx.fillStyle = layer === 0 ? color : (0, chart_geom_1.shadeHex)(color, 0.55 + 0.08 * (1 - t));
510
+ ctx.globalAlpha = layer === 0 ? 0.5 : 0.35;
511
+ ctx.beginPath();
512
+ ctx.moveTo(pts[0].x + ox, baseY + oy);
513
+ for (const p of pts)
514
+ ctx.lineTo(p.x + ox, p.y + oy);
515
+ ctx.lineTo(pts[pts.length - 1].x + ox, baseY + oy);
516
+ ctx.closePath();
517
+ ctx.fill();
518
+ }
519
+ ctx.globalAlpha = 1;
520
+ ctx.strokeStyle = color;
521
+ ctx.lineWidth = 1.5;
522
+ ctx.beginPath();
523
+ pts.forEach((p, i) => {
524
+ if (i === 0)
525
+ ctx.moveTo(p.x, p.y);
526
+ else
527
+ ctx.lineTo(p.x, p.y);
528
+ });
529
+ ctx.stroke();
530
+ }
531
+ }
532
+ function drawScatter(ctx, def, plot) {
533
+ let xMin = Infinity;
534
+ let xMax = -Infinity;
535
+ let yMin = Infinity;
536
+ let yMax = -Infinity;
537
+ for (const series of def.points)
538
+ for (const p of series) {
539
+ if (p.x < xMin)
540
+ xMin = p.x;
541
+ if (p.x > xMax)
542
+ xMax = p.x;
543
+ if (p.y < yMin)
544
+ yMin = p.y;
545
+ if (p.y > yMax)
546
+ yMax = p.y;
547
+ }
548
+ if (!Number.isFinite(xMin))
549
+ return;
550
+ const ticks = (0, chart_geom_1.niceTicks)(yMin, yMax, 5);
551
+ drawAxes(ctx, plot, ticks, [], false);
552
+ const tmin = ticks[0];
553
+ const tmax = ticks[ticks.length - 1];
554
+ for (let si = 0; si < def.points.length; si++) {
555
+ const color = def.series[si]?.color ?? "#3b82f6";
556
+ ctx.fillStyle = color;
557
+ for (const p of def.points[si]) {
558
+ const cx = plot.x + ((p.x - xMin) / (xMax - xMin || 1)) * plot.w;
559
+ const cy = plot.y + plot.h - ((p.y - tmin) / (tmax - tmin)) * plot.h;
560
+ ctx.beginPath();
561
+ ctx.arc(cx, cy, 3, 0, Math.PI * 2);
562
+ ctx.fill();
563
+ }
564
+ }
565
+ }
566
+ function hasCombo(def) {
567
+ return def.series.some((s) => s.chartType && s.chartType !== def.type);
568
+ }
569
+ function drawCombo(ctx, def, plot) {
570
+ const cats = def.categories ?? [];
571
+ let yMin = 0;
572
+ let yMax = 1;
573
+ for (const s of def.series) {
574
+ for (const v of s.values) {
575
+ if (v > yMax)
576
+ yMax = v;
577
+ if (v < yMin)
578
+ yMin = v;
579
+ }
580
+ if (s.chartType === "scatter" || s.chartType === "bubble") {
581
+ for (const p of (0, chart_geom_1.seriesXyPoints)(def, s)) {
582
+ if (p.y > yMax)
583
+ yMax = p.y;
584
+ if (p.y < yMin)
585
+ yMin = p.y;
586
+ }
587
+ }
588
+ }
589
+ ({ yMin, yMax } = (0, chart_geom_1.applyValueAxisScale)(yMin, yMax, def));
590
+ const ticks = (0, chart_geom_1.niceTicks)(yMin, yMax, 5);
591
+ drawAxes(ctx, plot, ticks, cats, def.type === "bar");
592
+ const groups = new Map();
593
+ for (const s of def.series) {
594
+ const ty = s.chartType ?? def.type;
595
+ const list = groups.get(ty) ?? [];
596
+ list.push(s);
597
+ groups.set(ty, list);
598
+ }
599
+ const order = ["area", "bar", "column", "stock", "radar", "scatter", "bubble", "line"];
600
+ const keys = [...groups.keys()].sort((a, b) => order.indexOf(a) - order.indexOf(b));
601
+ const shared = { ticks, skipAxes: true };
602
+ for (const ty of keys) {
603
+ const series = groups.get(ty);
604
+ if (ty === "bar" || ty === "column") {
605
+ drawBar(ctx, { ...def, type: ty, series }, plot, shared);
606
+ }
607
+ else if (ty === "area" || ty === "surface") {
608
+ drawArea(ctx, { ...def, type: "area", series }, plot, shared);
609
+ }
610
+ else if (ty === "scatter" || ty === "bubble") {
611
+ drawComboXy(ctx, def, series, plot, ticks, ty === "bubble");
612
+ }
613
+ else {
614
+ drawLine(ctx, { ...def, type: "line", series }, plot, shared);
615
+ }
616
+ }
617
+ overlayGuides(ctx, def, plot, ticks);
618
+ }
619
+ function drawRadar(ctx, def, plot) {
620
+ const cats = def.categories ?? [];
621
+ const n = Math.max(cats.length, 1);
622
+ const cx = plot.x + plot.w / 2;
623
+ const cy = plot.y + plot.h / 2;
624
+ const r = Math.min(plot.w, plot.h) / 2 - 8;
625
+ let max = 1;
626
+ for (const s of def.series)
627
+ for (const v of s.values)
628
+ if (v > max)
629
+ max = v;
630
+ ctx.strokeStyle = ts.grid;
631
+ ctx.lineWidth = 1;
632
+ for (let ring = 1; ring <= 4; ring++) {
633
+ ctx.beginPath();
634
+ for (let i = 0; i < n; i++) {
635
+ const a = -Math.PI / 2 + (i / n) * Math.PI * 2;
636
+ const rr = (r * ring) / 4;
637
+ const x = cx + Math.cos(a) * rr;
638
+ const y = cy + Math.sin(a) * rr;
639
+ if (i === 0)
640
+ ctx.moveTo(x, y);
641
+ else
642
+ ctx.lineTo(x, y);
643
+ }
644
+ ctx.closePath();
645
+ ctx.stroke();
646
+ }
647
+ ctx.fillStyle = ts.muted;
648
+ ctx.font = `${ts.size}px sans-serif`;
649
+ ctx.textAlign = "center";
650
+ ctx.textBaseline = "middle";
651
+ for (let i = 0; i < n; i++) {
652
+ const a = -Math.PI / 2 + (i / n) * Math.PI * 2;
653
+ ctx.beginPath();
654
+ ctx.moveTo(cx, cy);
655
+ ctx.lineTo(cx + Math.cos(a) * r, cy + Math.sin(a) * r);
656
+ ctx.stroke();
657
+ const lx = cx + Math.cos(a) * (r + 12);
658
+ const ly = cy + Math.sin(a) * (r + 12);
659
+ if (cats[i])
660
+ ctx.fillText(cats[i], lx, ly);
661
+ }
662
+ const filled = def.radarStyle === "filled";
663
+ for (const s of def.series) {
664
+ ctx.beginPath();
665
+ for (let i = 0; i < n; i++) {
666
+ const a = -Math.PI / 2 + (i / n) * Math.PI * 2;
667
+ const rr = r * ((s.values[i] ?? 0) / max);
668
+ const x = cx + Math.cos(a) * rr;
669
+ const y = cy + Math.sin(a) * rr;
670
+ if (i === 0)
671
+ ctx.moveTo(x, y);
672
+ else
673
+ ctx.lineTo(x, y);
674
+ }
675
+ ctx.closePath();
676
+ ctx.strokeStyle = s.color ?? "#3b82f6";
677
+ ctx.lineWidth = 2;
678
+ if (filled) {
679
+ ctx.fillStyle = s.color ?? "#3b82f6";
680
+ ctx.globalAlpha = 0.25;
681
+ ctx.fill();
682
+ ctx.globalAlpha = 1;
683
+ }
684
+ ctx.stroke();
685
+ if (def.radarStyle !== "standard") {
686
+ ctx.fillStyle = s.color ?? "#3b82f6";
687
+ for (let i = 0; i < n; i++) {
688
+ const a = -Math.PI / 2 + (i / n) * Math.PI * 2;
689
+ const rr = r * ((s.values[i] ?? 0) / max);
690
+ ctx.beginPath();
691
+ ctx.arc(cx + Math.cos(a) * rr, cy + Math.sin(a) * rr, 3, 0, Math.PI * 2);
692
+ ctx.fill();
693
+ }
694
+ }
695
+ }
696
+ }
697
+ function drawBubble(ctx, def, plot) {
698
+ let xMin = Infinity;
699
+ let xMax = -Infinity;
700
+ let yMin = Infinity;
701
+ let yMax = -Infinity;
702
+ let zMax = 1;
703
+ for (const series of def.points) {
704
+ for (const p of series) {
705
+ if (p.x < xMin)
706
+ xMin = p.x;
707
+ if (p.x > xMax)
708
+ xMax = p.x;
709
+ if (p.y < yMin)
710
+ yMin = p.y;
711
+ if (p.y > yMax)
712
+ yMax = p.y;
713
+ if ((p.z ?? 1) > zMax)
714
+ zMax = p.z ?? 1;
715
+ }
716
+ }
717
+ if (!Number.isFinite(xMin))
718
+ return;
719
+ const ticks = (0, chart_geom_1.niceTicks)(yMin, yMax, 5);
720
+ drawAxes(ctx, plot, ticks, [], false);
721
+ const tmin = ticks[0];
722
+ const tmax = ticks[ticks.length - 1];
723
+ const rMax = Math.min(plot.w, plot.h) * 0.08;
724
+ for (let si = 0; si < def.points.length; si++) {
725
+ const color = def.series[si]?.color ?? "#3b82f6";
726
+ ctx.fillStyle = color;
727
+ ctx.globalAlpha = 0.55;
728
+ for (const p of def.points[si]) {
729
+ const cx = plot.x + ((p.x - xMin) / (xMax - xMin || 1)) * plot.w;
730
+ const cy = plot.y + plot.h - ((p.y - tmin) / (tmax - tmin)) * plot.h;
731
+ const rr = Math.max(3, rMax * Math.sqrt((p.z ?? 1) / zMax));
732
+ ctx.beginPath();
733
+ ctx.arc(cx, cy, rr, 0, Math.PI * 2);
734
+ ctx.fill();
735
+ }
736
+ ctx.globalAlpha = 1;
737
+ }
738
+ }
739
+ function drawStock(ctx, def, plot) {
740
+ const cats = def.categories ?? [];
741
+ const n = def.series.length;
742
+ if (n < 3) {
743
+ drawLine(ctx, { ...def, type: "line" }, plot);
744
+ return;
745
+ }
746
+ const high = def.series[n >= 4 ? n - 3 : 0];
747
+ const low = def.series[n >= 4 ? n - 2 : 1];
748
+ const close = def.series[n >= 4 ? n - 1 : 2];
749
+ const open = n >= 4 ? def.series[n - 4] : undefined;
750
+ let yMin = 0;
751
+ let yMax = 1;
752
+ for (const s of [high, low, close, open]) {
753
+ if (!s)
754
+ continue;
755
+ for (const v of s.values) {
756
+ if (v > yMax)
757
+ yMax = v;
758
+ if (v < yMin)
759
+ yMin = v;
760
+ }
761
+ }
762
+ const ticks = (0, chart_geom_1.niceTicks)(yMin, yMax, 5);
763
+ drawAxes(ctx, plot, ticks, cats, false);
764
+ const tmin = ticks[0];
765
+ const tmax = ticks[ticks.length - 1];
766
+ const step = plot.w / Math.max(cats.length, 1);
767
+ const yOf = (v) => plot.y + plot.h - ((v - tmin) / (tmax - tmin)) * plot.h;
768
+ ctx.strokeStyle = ts.text;
769
+ ctx.lineWidth = 1.25;
770
+ for (let i = 0; i < cats.length; i++) {
771
+ const x = plot.x + step * (i + 0.5);
772
+ const hi = high.values[i] ?? 0;
773
+ const lo = low.values[i] ?? 0;
774
+ const cl = close.values[i] ?? 0;
775
+ ctx.beginPath();
776
+ ctx.moveTo(x, yOf(hi));
777
+ ctx.lineTo(x, yOf(lo));
778
+ ctx.stroke();
779
+ ctx.beginPath();
780
+ ctx.moveTo(x, yOf(cl));
781
+ ctx.lineTo(x + step * 0.18, yOf(cl));
782
+ ctx.stroke();
783
+ if (open) {
784
+ ctx.beginPath();
785
+ ctx.moveTo(x - step * 0.18, yOf(open.values[i] ?? 0));
786
+ ctx.lineTo(x, yOf(open.values[i] ?? 0));
787
+ ctx.stroke();
788
+ }
789
+ }
790
+ }
791
+ function drawLegend(ctx, def, area) {
792
+ const items = [];
793
+ if (def.type === "pie" || def.type === "doughnut") {
794
+ const s0 = def.series[0];
795
+ if (s0) {
796
+ const palette = s0.perSliceColors ?? def.colors ?? [];
797
+ (def.categories ?? []).forEach((c, i) => {
798
+ items.push({ name: c || `Item ${i + 1}`, color: palette[i] ?? s0.color ?? "#3b82f6" });
799
+ });
800
+ }
801
+ }
802
+ else {
803
+ for (const s of def.series)
804
+ items.push({ name: s.name ?? "Series", color: s.color ?? "#3b82f6" });
805
+ }
806
+ let x = area.x + 12;
807
+ const y = area.y + 4;
808
+ ctx.font = `${ts.size}px sans-serif`;
809
+ ctx.fillStyle = ts.muted;
810
+ ctx.textBaseline = "top";
811
+ ctx.textAlign = "left";
812
+ for (const it of items) {
813
+ ctx.fillStyle = it.color;
814
+ ctx.fillRect(x, y, 10, 10);
815
+ ctx.fillStyle = ts.muted;
816
+ ctx.fillText(it.name, x + 14, y);
817
+ x += ctx.measureText(it.name).width + 22 + 8;
818
+ }
819
+ }
820
+ function drawSurface(ctx, def, plot) {
821
+ for (const q of (0, chart_geom_1.surfaceIsoQuads)(def, plot)) {
822
+ if (q.pts.length < 3)
823
+ continue;
824
+ ctx.beginPath();
825
+ ctx.moveTo(q.pts[0].x, q.pts[0].y);
826
+ for (let i = 1; i < q.pts.length; i++)
827
+ ctx.lineTo(q.pts[i].x, q.pts[i].y);
828
+ ctx.closePath();
829
+ if (q.stroke) {
830
+ ctx.strokeStyle = q.color;
831
+ ctx.lineWidth = 1;
832
+ ctx.stroke();
833
+ }
834
+ else {
835
+ ctx.fillStyle = q.color;
836
+ ctx.globalAlpha = 0.72;
837
+ ctx.fill();
838
+ ctx.globalAlpha = 1;
839
+ ctx.strokeStyle = "rgba(255,255,255,0.45)";
840
+ ctx.lineWidth = 0.6;
841
+ ctx.stroke();
842
+ }
843
+ }
844
+ }
845
+ function drawComboXy(ctx, def, series, plot, ticks, bubble) {
846
+ const cats = def.categories ?? [];
847
+ const tmin = ticks[0];
848
+ const tmax = ticks[ticks.length - 1];
849
+ const xs = series.flatMap((s) => (0, chart_geom_1.seriesXyPoints)(def, s).map((p) => p.x));
850
+ for (const s of series) {
851
+ const pts = (0, chart_geom_1.seriesXyPoints)(def, s);
852
+ ctx.fillStyle = s.color ?? "#3b82f6";
853
+ for (const p of pts) {
854
+ const cx = (0, chart_geom_1.comboXyPlotX)(p.x, cats, plot, xs);
855
+ const cy = plot.y + plot.h - ((p.y - tmin) / (tmax - tmin || 1)) * plot.h;
856
+ const r = bubble ? Math.max(3, Math.min(16, (p.z ?? 1) * 1.8)) : 3.5;
857
+ ctx.beginPath();
858
+ ctx.arc(cx, cy, r, 0, Math.PI * 2);
859
+ ctx.globalAlpha = bubble ? 0.7 : 1;
860
+ ctx.fill();
861
+ ctx.globalAlpha = 1;
862
+ }
863
+ }
864
+ }
865
+ function overlayGuides(ctx, def, plot, ticksIn) {
866
+ if (def.type === "pie" ||
867
+ def.type === "doughnut" ||
868
+ def.type === "surface" ||
869
+ def.type === "radar" ||
870
+ def.type === "sunburst" ||
871
+ def.type === "treemap" ||
872
+ def.type === "funnel" ||
873
+ def.type === "region") {
874
+ return;
875
+ }
876
+ const cats = def.categories ?? [];
877
+ let yMin = 0;
878
+ let yMax = 1;
879
+ for (const s of def.series) {
880
+ for (const v of s.values) {
881
+ if (v > yMax)
882
+ yMax = v;
883
+ if (v < yMin)
884
+ yMin = v;
885
+ }
886
+ if (s.errorBars?.val) {
887
+ for (const v of s.values) {
888
+ if (v + s.errorBars.val > yMax)
889
+ yMax = v + s.errorBars.val;
890
+ if (v - s.errorBars.val < yMin)
891
+ yMin = v - s.errorBars.val;
892
+ }
893
+ }
894
+ }
895
+ ({ yMin, yMax } = (0, chart_geom_1.applyValueAxisScale)(yMin, yMax, def));
896
+ const ticks = ticksIn ?? (0, chart_geom_1.niceTicks)(yMin, yMax, 5);
897
+ const tmin = ticks[0];
898
+ const tmax = ticks[ticks.length - 1];
899
+ const yOf = (v) => plot.y + plot.h - ((v - tmin) / (tmax - tmin || 1)) * plot.h;
900
+ const step = cats.length > 1 ? plot.w / (cats.length - 1) : plot.w;
901
+ for (const s of def.series) {
902
+ const ty = s.chartType ?? def.type;
903
+ const color = s.color ?? ts.muted;
904
+ if (ty === "scatter" || ty === "bubble") {
905
+ const pts = (0, chart_geom_1.seriesXyPoints)(def, s);
906
+ if (s.trendline) {
907
+ const fit = (0, chart_geom_1.linearFit)(pts.map((p) => p.x), pts.map((p) => p.y));
908
+ if (fit && pts.length >= 2) {
909
+ const xs = pts.map((p) => p.x);
910
+ const x0 = Math.min(...xs);
911
+ const x1 = Math.max(...xs);
912
+ const xAt = (xv) => (0, chart_geom_1.comboXyPlotX)(xv, cats, plot, xs);
913
+ ctx.strokeStyle = color;
914
+ ctx.lineWidth = 1.25;
915
+ ctx.setLineDash([5, 3]);
916
+ ctx.beginPath();
917
+ ctx.moveTo(xAt(x0), yOf(fit.a + fit.b * x0));
918
+ ctx.lineTo(xAt(x1), yOf(fit.a + fit.b * x1));
919
+ ctx.stroke();
920
+ ctx.setLineDash([]);
921
+ }
922
+ }
923
+ continue;
924
+ }
925
+ if (s.errorBars) {
926
+ const half = s.errorBars.val ?? 0;
927
+ ctx.strokeStyle = color;
928
+ ctx.lineWidth = 1;
929
+ s.values.forEach((v, i) => {
930
+ const xx = plot.x + step * i;
931
+ ctx.beginPath();
932
+ ctx.moveTo(xx, yOf(v - half));
933
+ ctx.lineTo(xx, yOf(v + half));
934
+ ctx.stroke();
935
+ ctx.beginPath();
936
+ ctx.moveTo(xx - 3, yOf(v - half));
937
+ ctx.lineTo(xx + 3, yOf(v - half));
938
+ ctx.moveTo(xx - 3, yOf(v + half));
939
+ ctx.lineTo(xx + 3, yOf(v + half));
940
+ ctx.stroke();
941
+ });
942
+ }
943
+ if (s.trendline) {
944
+ const xs = s.values.map((_, i) => i);
945
+ const fit = (0, chart_geom_1.linearFit)(xs, s.values);
946
+ if (fit && xs.length >= 2) {
947
+ ctx.strokeStyle = color;
948
+ ctx.lineWidth = 1.25;
949
+ ctx.setLineDash([5, 3]);
950
+ ctx.beginPath();
951
+ ctx.moveTo(plot.x, yOf(fit.a));
952
+ ctx.lineTo(plot.x + step * (s.values.length - 1), yOf(fit.a + fit.b * (s.values.length - 1)));
953
+ ctx.stroke();
954
+ ctx.setLineDash([]);
955
+ }
956
+ }
957
+ }
958
+ }
959
+ function overlayDataLabels(ctx, def, plot) {
960
+ ctx.save();
961
+ ctx.fillStyle = ts.text;
962
+ ctx.font = `${ts.size}px sans-serif`;
963
+ ctx.textAlign = "center";
964
+ ctx.textBaseline = "bottom";
965
+ if (def.type === "pie" || def.type === "doughnut") {
966
+ const s0 = def.series[0];
967
+ if (!s0) {
968
+ ctx.restore();
969
+ return;
970
+ }
971
+ const total = s0.values.reduce((a, b) => a + Math.abs(b), 0);
972
+ if (!total) {
973
+ ctx.restore();
974
+ return;
975
+ }
976
+ const cx0 = plot.x + plot.w / 2;
977
+ const cy0 = plot.y + plot.h / 2;
978
+ const r = Math.min(plot.w, plot.h) / 2 - 4;
979
+ let start = -Math.PI / 2 + ((def.startAngle ?? 0) * Math.PI) / 180;
980
+ for (let i = 0; i < s0.values.length; i++) {
981
+ const v = Math.abs(s0.values[i]);
982
+ const frac = v / total;
983
+ if (frac <= 0)
984
+ continue;
985
+ const end = start + frac * Math.PI * 2;
986
+ const mid = (start + end) / 2;
987
+ const label = (0, chart_geom_1.pointLabelText)(def, s0, i, s0.values[i]);
988
+ if (label)
989
+ ctx.fillText(label, cx0 + Math.cos(mid) * r * 0.62, cy0 + Math.sin(mid) * r * 0.62);
990
+ start = end;
991
+ }
992
+ ctx.restore();
993
+ return;
994
+ }
995
+ const cats = def.categories ?? [];
996
+ // Clustered / stacked bars: label each bar at its own free end, in the bar renderer's geometry.
997
+ if ((def.type === "column" || def.type === "bar") && !hasCombo(def)) {
998
+ const geo = (0, chart_geom_1.barGeometry)(def, plot);
999
+ for (let ci = 0; ci < cats.length; ci++) {
1000
+ let stackPos = 0;
1001
+ let stackNeg = 0;
1002
+ def.series.forEach((s, si) => {
1003
+ let v = s.values[ci] ?? 0;
1004
+ if (def.percentStacked)
1005
+ v = (geo.totals[ci] ?? 0) > 0 ? v / geo.totals[ci] : 0;
1006
+ const label = (0, chart_geom_1.pointLabelText)(def, s, ci, s.values[ci] ?? 0);
1007
+ if (label) {
1008
+ const a = (0, chart_geom_1.barLabelAnchor)(geo, plot, si, ci, v, v >= 0 ? stackPos : stackNeg);
1009
+ ctx.textAlign = a.anchor === "middle" ? "center" : a.anchor === "start" ? "left" : "right";
1010
+ ctx.textBaseline = a.baseline;
1011
+ ctx.fillText(label, a.x, a.y);
1012
+ }
1013
+ if (v >= 0)
1014
+ stackPos += v;
1015
+ else
1016
+ stackNeg += v;
1017
+ });
1018
+ }
1019
+ ctx.restore();
1020
+ return;
1021
+ }
1022
+ const step = cats.length > 1 ? plot.w / (cats.length - 1) : plot.w;
1023
+ let yMin = 0;
1024
+ let yMax = 1;
1025
+ for (const s of def.series)
1026
+ for (const v of s.values) {
1027
+ if (v > yMax)
1028
+ yMax = v;
1029
+ if (v < yMin)
1030
+ yMin = v;
1031
+ }
1032
+ ({ yMin, yMax } = (0, chart_geom_1.applyValueAxisScale)(yMin, yMax, def));
1033
+ const ticks = (0, chart_geom_1.niceTicks)(yMin, yMax, 5);
1034
+ const tmin = ticks[0];
1035
+ const tmax = ticks[ticks.length - 1];
1036
+ const yOf = (v) => plot.y + plot.h - ((v - tmin) / (tmax - tmin || 1)) * plot.h;
1037
+ for (const s of def.series) {
1038
+ const ty = s.chartType ?? def.type;
1039
+ if (ty === "scatter" || ty === "bubble") {
1040
+ const pts = (0, chart_geom_1.seriesXyPoints)(def, s);
1041
+ const xs = pts.map((p) => p.x);
1042
+ pts.forEach((p, i) => {
1043
+ const label = (0, chart_geom_1.pointLabelText)(def, s, i, p.y);
1044
+ if (!label)
1045
+ return;
1046
+ ctx.fillText(label, (0, chart_geom_1.comboXyPlotX)(p.x, cats, plot, xs), yOf(p.y) - 4);
1047
+ });
1048
+ continue;
1049
+ }
1050
+ s.values.forEach((v, i) => {
1051
+ const label = (0, chart_geom_1.pointLabelText)(def, s, i, v);
1052
+ if (!label)
1053
+ return;
1054
+ const xx = plot.x + step * (cats.length > 1 ? i : 0.5);
1055
+ ctx.fillText(label, xx, yOf(v) - 3);
1056
+ });
1057
+ }
1058
+ ctx.restore();
1059
+ }