@wick-charts/vue 0.4.2 → 0.5.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 (144) hide show
  1. package/README.md +5 -3
  2. package/dist/BarSeries.vue.js +44 -0
  3. package/dist/BarSeries.vue2.js +4 -0
  4. package/dist/CandlestickSeries.vue.js +45 -0
  5. package/dist/CandlestickSeries.vue2.js +4 -0
  6. package/dist/ChartContainer.vue.js +168 -0
  7. package/dist/ChartContainer.vue2.js +4 -0
  8. package/dist/LineSeries.vue.js +44 -0
  9. package/dist/LineSeries.vue2.js +4 -0
  10. package/dist/PieSeries.vue.js +34 -0
  11. package/dist/PieSeries.vue2.js +4 -0
  12. package/dist/composables.js +44 -0
  13. package/dist/context.js +25 -0
  14. package/dist/core/dist/animation/animator.js +72 -0
  15. package/dist/core/dist/animation/config.js +145 -0
  16. package/dist/core/dist/animation/easing.js +4 -0
  17. package/dist/core/dist/animation/scalar-spring.js +57 -0
  18. package/dist/core/dist/animation/spring.js +11 -0
  19. package/dist/core/dist/animation/time.js +19 -0
  20. package/dist/core/dist/animation/viewport-engine.js +174 -0
  21. package/dist/core/dist/animation/visible-range-spring.js +64 -0
  22. package/dist/core/dist/animation/y-range-hermite.js +72 -0
  23. package/dist/core/dist/animation/y-range-snap.js +31 -0
  24. package/dist/core/dist/animation/y-range-spring.js +59 -0
  25. package/dist/core/dist/axis/dom-labels.js +60 -0
  26. package/dist/core/dist/canvas-manager.js +109 -0
  27. package/dist/core/dist/chart/edge-indicators.js +26 -0
  28. package/dist/core/dist/chart/fit-to-data.js +9 -0
  29. package/dist/core/dist/chart/last-value.js +31 -0
  30. package/dist/core/dist/chart/options.js +35 -0
  31. package/dist/core/dist/chart/pan-zoom-math.js +66 -0
  32. package/dist/core/dist/chart/streaming-cadence.js +70 -0
  33. package/dist/core/dist/chart/streaming-target.js +22 -0
  34. package/dist/core/dist/chart/viewport-padding.js +6 -0
  35. package/dist/core/dist/chart/y-target.js +24 -0
  36. package/dist/core/dist/chart.js +1495 -0
  37. package/dist/core/dist/components/crosshair.js +9 -0
  38. package/dist/core/dist/components/edge-indicator.js +36 -0
  39. package/dist/core/dist/components/grid.js +22 -0
  40. package/dist/core/dist/components/marker.js +41 -0
  41. package/dist/core/dist/components/reference-line.js +48 -0
  42. package/dist/core/dist/components/time-region.js +35 -0
  43. package/dist/core/dist/data/decimation.js +54 -0
  44. package/dist/core/dist/data/store.js +76 -0
  45. package/dist/core/dist/data/sync.js +47 -0
  46. package/dist/core/dist/events.js +19 -0
  47. package/dist/core/dist/interactions/handler.js +84 -0
  48. package/dist/core/dist/interactions/pan.js +26 -0
  49. package/dist/core/dist/interactions/zoom.js +21 -0
  50. package/dist/core/dist/navigator/controller.js +374 -0
  51. package/dist/core/dist/navigator/decimate.js +44 -0
  52. package/dist/core/dist/navigator/interactions.js +28 -0
  53. package/dist/core/dist/navigator/render.js +68 -0
  54. package/dist/core/dist/perf/counting-context.js +19 -0
  55. package/dist/core/dist/perf/perf-hud.js +453 -0
  56. package/dist/core/dist/perf/perf-monitor.js +132 -0
  57. package/dist/core/dist/render-scheduler.js +31 -0
  58. package/dist/core/dist/scales/tick-tracker.js +110 -0
  59. package/dist/core/dist/scales/x-scale.js +145 -0
  60. package/dist/core/dist/scales/y-scale.js +156 -0
  61. package/dist/core/dist/series/bar.js +341 -0
  62. package/dist/core/dist/series/base-multi-layer.js +438 -0
  63. package/dist/core/dist/series/candlestick.js +481 -0
  64. package/dist/core/dist/series/definition.js +11 -0
  65. package/dist/core/dist/series/line.js +446 -0
  66. package/dist/core/dist/series/painters/builtins.js +36 -0
  67. package/dist/core/dist/series/painters/canvas-path.js +79 -0
  68. package/dist/core/dist/series/painters/resolve.js +32 -0
  69. package/dist/core/dist/series/pie.js +545 -0
  70. package/dist/core/dist/series/register-builtins.js +11 -0
  71. package/dist/core/dist/series/stack-math.js +17 -0
  72. package/dist/core/dist/snapshots.js +107 -0
  73. package/dist/core/dist/theme/create.js +176 -0
  74. package/dist/core/dist/theme/resolve.js +14 -0
  75. package/dist/core/dist/theme/themes/andromeda.js +33 -0
  76. package/dist/core/dist/theme/themes/ayu-mirage.js +33 -0
  77. package/dist/core/dist/theme/themes/catppuccin.js +33 -0
  78. package/dist/core/dist/theme/themes/dracula.js +35 -0
  79. package/dist/core/dist/theme/themes/github-light.js +34 -0
  80. package/dist/core/dist/theme/themes/gruvbox.js +33 -0
  81. package/dist/core/dist/theme/themes/handwritten.js +101 -0
  82. package/dist/core/dist/theme/themes/high-contrast.js +33 -0
  83. package/dist/core/dist/theme/themes/lavender-mist.js +33 -0
  84. package/dist/core/dist/theme/themes/light-pink.js +34 -0
  85. package/dist/core/dist/theme/themes/material-palenight.js +33 -0
  86. package/dist/core/dist/theme/themes/minimal-light.js +34 -0
  87. package/dist/core/dist/theme/themes/mint-breeze.js +33 -0
  88. package/dist/core/dist/theme/themes/monokai-pro.js +33 -0
  89. package/dist/core/dist/theme/themes/night-owl.js +35 -0
  90. package/dist/core/dist/theme/themes/one-dark-pro.js +33 -0
  91. package/dist/core/dist/theme/themes/panda.js +33 -0
  92. package/dist/core/dist/theme/themes/peach-cream.js +33 -0
  93. package/dist/core/dist/theme/themes/quiet-light.js +37 -0
  94. package/dist/core/dist/theme/themes/rose-pine-dawn.js +33 -0
  95. package/dist/core/dist/theme/themes/sand-dune.js +33 -0
  96. package/dist/core/dist/theme/themes/solarized-light.js +34 -0
  97. package/dist/core/dist/tooltip-position.js +10 -0
  98. package/dist/core/dist/utils/color.js +37 -0
  99. package/dist/core/dist/utils/format.js +29 -0
  100. package/dist/core/dist/utils/math.js +26 -0
  101. package/dist/core/dist/utils/poisoned-data-reporter.js +15 -0
  102. package/dist/core/dist/utils/time.js +41 -0
  103. package/dist/index.d.ts +827 -63
  104. package/dist/index.js +142 -8025
  105. package/dist/ui/Crosshair.vue.js +53 -0
  106. package/dist/ui/Crosshair.vue2.js +4 -0
  107. package/dist/ui/InfoBar.vue.js +143 -0
  108. package/dist/ui/InfoBar.vue2.js +4 -0
  109. package/dist/ui/Legend.vue.js +212 -0
  110. package/dist/ui/Legend.vue2.js +4 -0
  111. package/dist/ui/Marker.vue.js +47 -0
  112. package/dist/ui/Marker.vue2.js +4 -0
  113. package/dist/ui/Navigator.vue.js +58 -0
  114. package/dist/ui/Navigator.vue2.js +4 -0
  115. package/dist/ui/NumberFlow.vue.js +69 -0
  116. package/dist/ui/NumberFlow.vue2.js +4 -0
  117. package/dist/ui/PieLegend.vue.js +132 -0
  118. package/dist/ui/PieLegend.vue2.js +4 -0
  119. package/dist/ui/PieTooltip.vue.js +135 -0
  120. package/dist/ui/PieTooltip.vue2.js +4 -0
  121. package/dist/ui/ReferenceLine.vue.js +44 -0
  122. package/dist/ui/ReferenceLine.vue2.js +4 -0
  123. package/dist/ui/TimeAxis.vue.js +43 -0
  124. package/dist/ui/TimeAxis.vue2.js +4 -0
  125. package/dist/ui/TimeRegion.vue.js +41 -0
  126. package/dist/ui/TimeRegion.vue2.js +4 -0
  127. package/dist/ui/Title.vue.js +54 -0
  128. package/dist/ui/Title.vue2.js +4 -0
  129. package/dist/ui/Tooltip.vue.js +195 -0
  130. package/dist/ui/Tooltip.vue2.js +4 -0
  131. package/dist/ui/YAxis.vue.js +42 -0
  132. package/dist/ui/YAxis.vue2.js +4 -0
  133. package/dist/ui/YLabel.vue.js +105 -0
  134. package/dist/ui/YLabel.vue2.js +4 -0
  135. package/package.json +2 -2
  136. package/src/BarSeries.vue +12 -13
  137. package/src/CandlestickSeries.vue +17 -6
  138. package/src/LineSeries.vue +12 -13
  139. package/src/PieSeries.vue +2 -1
  140. package/src/index.ts +23 -0
  141. package/src/ui/Legend.vue +1 -2
  142. package/src/ui/Marker.vue +62 -0
  143. package/src/ui/ReferenceLine.vue +58 -0
  144. package/src/ui/TimeRegion.vue +54 -0
@@ -0,0 +1,545 @@
1
+ import { lighten as st } from "../utils/color.js";
2
+ import { smoothToward as K, clamp as E } from "../utils/math.js";
3
+ const tt = {
4
+ mode: "outside",
5
+ content: "both",
6
+ fontSize: 11,
7
+ // 2.5° ≈ 0.7% of the pie. The PAV pass handles the resulting vertical
8
+ // crowding; raise when labels-per-slice still crosses into illegible.
9
+ minSliceAngle: 2.5,
10
+ elbowLen: 12,
11
+ legPad: 6,
12
+ // Radial distance from the pie edge to the label ring. The label then
13
+ // extends horizontally outward by `railWidth` so the text anchor sits
14
+ // past a consistent-length horizontal tail.
15
+ distance: 14,
16
+ railWidth: 16,
17
+ // Multiplier on fontSize for min vertical gap between same-side labels.
18
+ // 1.8 gives each label room to breathe without spreading so aggressively
19
+ // that PAV collapses into a centered block on dense datasets.
20
+ labelGap: 1.8
21
+ }, ot = {
22
+ innerRadiusRatio: 0,
23
+ // 1.15° ≈ 0.02 rad — same visual default as before the radians→degrees switch.
24
+ padAngle: 1.15,
25
+ sliceLabels: { ...tt },
26
+ // Motion off by default: label draw-in + hover explode both skipped.
27
+ animate: !1,
28
+ // Small-slice grouping on by default: dense datasets collapse into the top
29
+ // slices plus a single "Other" so outside labels stay legible.
30
+ other: !0,
31
+ // Radial depth gradient (lighter center → flat edge) on by default. Set
32
+ // false for flat, solid-color slices.
33
+ gradient: !0
34
+ }, Q = {
35
+ maxSlices: 8,
36
+ label: "Other"
37
+ };
38
+ function nt(l) {
39
+ return l === !1 ? null : l === !0 || l === void 0 ? Q : { ...Q, ...l };
40
+ }
41
+ const U = Math.PI / 180;
42
+ function lt(l, e) {
43
+ return { ...l ?? {}, ...e };
44
+ }
45
+ function it(l) {
46
+ return { ...tt, ...l ?? {} };
47
+ }
48
+ function at(l) {
49
+ const e = { color: "rgba(0, 0, 0, 0.22)", blur: 24, offsetX: 0, offsetY: 10 };
50
+ return l === !0 || l === !1 ? e : { ...e, ...l };
51
+ }
52
+ function rt(l) {
53
+ const e = { color: "rgba(0, 0, 0, 0.1)", depth: 0.3 };
54
+ return l === !0 || l === !1 ? e : { ...e, ...l };
55
+ }
56
+ const N = Math.PI * 2, et = 0.85;
57
+ function Z(l) {
58
+ const { bitmapWidth: e, bitmapHeight: t, padTop: o, padBottom: i } = l, s = Math.max(0, l.labelReserve ?? 0), d = Math.min(Math.max(0, o), t), r = Math.min(Math.max(0, i), t - d), c = t - d - r, g = Math.max(0, e - 2 * s), C = e / 2, x = d + c / 2, R = Math.max(0, Math.min(g, c) / 2 * et);
59
+ return { cx: C, cy: x, maxR: R };
60
+ }
61
+ function ht(l) {
62
+ if (!l.startsWith("#")) return !1;
63
+ const e = parseInt(l.slice(1, 3), 16), t = parseInt(l.slice(3, 5), 16), o = parseInt(l.slice(5, 7), 16);
64
+ return e * 0.299 + t * 0.587 + o * 0.114 > 150;
65
+ }
66
+ function ct(l, e) {
67
+ if (!l.startsWith("#")) return l;
68
+ const t = l.length === 4 ? `#${l[1]}${l[1]}${l[2]}${l[2]}${l[3]}${l[3]}` : l;
69
+ return st(t, e);
70
+ }
71
+ function J(l) {
72
+ if (l.startsWith("#"))
73
+ return {
74
+ r: parseInt(l.slice(1, 3), 16) || 0,
75
+ g: parseInt(l.slice(3, 5), 16) || 0,
76
+ b: parseInt(l.slice(5, 7), 16) || 0,
77
+ a: 1
78
+ };
79
+ const e = l.match(/^rgba?\s*\(([^)]+)\)$/i);
80
+ if (e) {
81
+ const t = e[1].split(",").map((o) => o.trim());
82
+ return {
83
+ r: Number.parseFloat(t[0]) || 0,
84
+ g: Number.parseFloat(t[1]) || 0,
85
+ b: Number.parseFloat(t[2]) || 0,
86
+ a: t[3] === void 0 ? 1 : Number.parseFloat(t[3])
87
+ };
88
+ }
89
+ return { r: 0, g: 0, b: 0, a: 1 };
90
+ }
91
+ function ut(l, e) {
92
+ const t = J(l), o = J(e), i = o.a, s = Math.round(o.r * i + t.r * (1 - i)), d = Math.round(o.g * i + t.g * (1 - i)), r = Math.round(o.b * i + t.b * (1 - i));
93
+ return `#${s.toString(16).padStart(2, "0")}${d.toString(16).padStart(2, "0")}${r.toString(16).padStart(2, "0")}`;
94
+ }
95
+ class ft {
96
+ kind = "pie";
97
+ #n = [];
98
+ /**
99
+ * Slices actually rendered. Equals {@link #data} unless "other" grouping is
100
+ * active and the dataset exceeds `maxSlices` — then the smallest slices are
101
+ * summed into a synthetic "Other" slice appended at the end. Every read
102
+ * path (render, hit-test, hover / slice info, label reserve) goes through
103
+ * this array so the grouped view is consistent across all surfaces.
104
+ */
105
+ #t = [];
106
+ /**
107
+ * Index of the synthetic "Other" slice in {@link #display}, or -1 when
108
+ * grouping didn't apply. Lets the color resolution pick the theme's muted
109
+ * `pie.otherColor` for the aggregate instead of the next palette color.
110
+ */
111
+ #c = -1;
112
+ #e;
113
+ /** Index of the hovered slice (-1 = none). Set via {@link setHoverIndex}. */
114
+ #i = -1;
115
+ /** Animated offset per slice (0..1), smoothly interpolated toward target. */
116
+ #s = [];
117
+ #u = 0;
118
+ /**
119
+ * Outside-label reveal progress (0..1). Eased toward 1 on mount / data-swap
120
+ * when `animate` is on; drives the leader-line draw-in and label fade-in
121
+ * stagger. When `animate` is off (the default) this stays pinned at 1 so
122
+ * labels paint fully-revealed on the first frame.
123
+ */
124
+ #a = 1;
125
+ /**
126
+ * Cached horizontal bitmap-pixel reserve for outside labels. Invalidated on
127
+ * `setData` / `updateOptions`; computed lazily on first `render` call per
128
+ * frame using the live canvas context for accurate `measureText`.
129
+ */
130
+ #l = null;
131
+ /** Subscribers notified on setData (pie has no TimeSeriesStore to piggy-back on). */
132
+ #r = [];
133
+ constructor(e) {
134
+ this.#e = this.#m(ot, e ?? {});
135
+ }
136
+ /**
137
+ * Merge partial options onto a base, deep-merging `sliceLabels` and `other`
138
+ * so callers can override one field (e.g. `mode` or `maxSlices`) without
139
+ * dropping sibling defaults.
140
+ */
141
+ #m(e, t) {
142
+ const o = { ...e, ...t };
143
+ return t.sliceLabels !== void 0 && (o.sliceLabels = lt(e.sliceLabels, t.sliceLabels)), typeof t.other == "object" && typeof e.other == "object" && (o.other = { ...e.other, ...t.other }), o;
144
+ }
145
+ /**
146
+ * Build the display array from the raw data. When grouping applies, the
147
+ * largest `maxSlices - 1` slices are kept in their original order; the rest
148
+ * are summed into one "Other" slice appended at the end. Ranking uses the
149
+ * layout-sanitized value so negative / non-finite entries can never outrank
150
+ * a real slice.
151
+ */
152
+ #p() {
153
+ this.#c = -1;
154
+ const e = nt(this.#e.other);
155
+ if (!e || this.#n.length <= e.maxSlices) return this.#n;
156
+ const t = Math.max(1, e.maxSlices - 1), o = this.#n.map((r, c) => ({ index: c, value: this.#o(r.value) })).sort((r, c) => c.value - r.value), i = /* @__PURE__ */ new Set();
157
+ for (const r of o.slice(0, t))
158
+ i.add(r.index);
159
+ const s = [];
160
+ let d = 0;
161
+ for (let r = 0; r < this.#n.length; r++)
162
+ i.has(r) ? s.push(this.#n[r]) : d += this.#o(this.#n[r].value);
163
+ return s.push({ label: e.label, value: d, color: e.color }), this.#c = s.length - 1, s;
164
+ }
165
+ /**
166
+ * Paint color for one display slice: explicit slice color → theme-driven
167
+ * "Other" color for the synthetic aggregate → palette by index. The
168
+ * `axis.textColor` fallback matches what {@link createTheme} derives for
169
+ * `pie.otherColor`, so hand-built theme literals without the `pie` block
170
+ * get the same muted tone.
171
+ */
172
+ #h(e) {
173
+ const { index: t, palette: o, theme: i } = e, s = this.#t[t];
174
+ return s?.color ? s.color : t === this.#c ? i.pie?.otherColor ?? i.axis.textColor : o[t % o.length];
175
+ }
176
+ getData() {
177
+ return this.#n;
178
+ }
179
+ setData(e) {
180
+ const t = e ?? [];
181
+ this.#n = t, this.#t = this.#p(), this.#s = new Array(this.#t.length).fill(0), this.#a = this.#e.animate ? 0 : 1, this.#l = null;
182
+ for (const o of this.#r)
183
+ o();
184
+ }
185
+ onDataChanged(e) {
186
+ return this.#r.push(e), () => {
187
+ const t = this.#r.indexOf(e);
188
+ t >= 0 && this.#r.splice(t, 1);
189
+ };
190
+ }
191
+ updateOptions(e) {
192
+ const t = this.#e.sliceLabels?.mode;
193
+ this.#e = this.#m(this.#e, e ?? {}), this.#e.sliceLabels?.mode === "outside" && t !== "outside" && (this.#a = this.#e.animate ? 0 : 1), this.#t = this.#p(), this.#s.length !== this.#t.length && (this.#s = new Array(this.#t.length).fill(0)), this.#l = null;
194
+ }
195
+ getColor() {
196
+ return this.#t[0]?.color ?? this.#e.colors?.[0] ?? "#888";
197
+ }
198
+ getLayerCount() {
199
+ return 1;
200
+ }
201
+ setLayerVisible(e, t) {
202
+ }
203
+ isLayerVisible(e) {
204
+ return !0;
205
+ }
206
+ getLayerColors() {
207
+ return [this.getColor()];
208
+ }
209
+ applyTheme(e, t) {
210
+ e.typography.fontFamily !== t.typography.fontFamily && (this.#l = null);
211
+ }
212
+ /** Hit-test: which slice is at this bitmap coordinate? Returns index or -1. */
213
+ hitTest(e, t, o, i, s) {
214
+ if (this.#t.length === 0) return -1;
215
+ const d = this.#f();
216
+ if (d <= 0) return -1;
217
+ const r = this.#l?.reserve ?? 0, { cx: c, cy: g, maxR: C } = Z({
218
+ bitmapWidth: o,
219
+ bitmapHeight: i,
220
+ padTop: s?.top ?? 0,
221
+ padBottom: s?.bottom ?? 0,
222
+ labelReserve: r
223
+ }), x = C, R = x * this.#e.innerRadiusRatio, M = e - c, X = t - g, $ = Math.sqrt(M * M + X * X);
224
+ if ($ > x || $ < R) return -1;
225
+ let L = Math.atan2(X, M) + Math.PI / 2;
226
+ L < 0 && (L += N);
227
+ let A = 0;
228
+ for (let b = 0; b < this.#t.length; b++) {
229
+ const I = this.#o(this.#t[b].value) / d * N;
230
+ if (L >= A && L < A + I) return b;
231
+ A += I;
232
+ }
233
+ return -1;
234
+ }
235
+ setHoverIndex(e) {
236
+ return this.#i === e ? !1 : (this.#i = e, !0);
237
+ }
238
+ getHoverIndex() {
239
+ return this.#i;
240
+ }
241
+ getHoverInfo(e) {
242
+ if (this.#i < 0) return null;
243
+ const t = this.#t[this.#i];
244
+ if (!t) return null;
245
+ const o = this.#f(), i = this.#e.colors ?? e.seriesColors;
246
+ return {
247
+ label: t.label,
248
+ value: t.value,
249
+ percent: o > 0 ? this.#o(t.value) / o * 100 : 0,
250
+ color: this.#h({ index: this.#i, palette: i, theme: e })
251
+ };
252
+ }
253
+ getSliceInfo(e) {
254
+ if (this.#t.length === 0) return null;
255
+ const t = this.#f(), o = this.#e.colors ?? e.seriesColors;
256
+ return this.#t.map((i, s) => ({
257
+ label: i.label,
258
+ value: i.value,
259
+ percent: t > 0 ? this.#o(i.value) / t * 100 : 0,
260
+ color: this.#h({ index: s, palette: o, theme: e })
261
+ }));
262
+ }
263
+ /** Returns true if animation is still in progress. */
264
+ get needsAnimation() {
265
+ if (!this.#e.animate) return !1;
266
+ for (let e = 0; e < this.#s.length; e++) {
267
+ const t = e === this.#i ? 1 : 0;
268
+ if (Math.abs(this.#s[e] - t) > 0.01) return !0;
269
+ }
270
+ return this.#e.sliceLabels?.mode === "outside" && this.#a < 0.99;
271
+ }
272
+ dispose() {
273
+ this.#r = [], this.#n = [], this.#t = [], this.#c = -1, this.#s = [], this.#u = 0;
274
+ }
275
+ /**
276
+ * @internal Test-only accessor for the animated slice offsets. The backing
277
+ * `#sliceOffsets` field is ECMAScript-private and can't be reached via a
278
+ * type-cast escape hatch, so the pie-animation regression tests use this
279
+ * method to assert convergence behavior. Do not call from production code.
280
+ */
281
+ _inspectSliceOffsets() {
282
+ return this.#s;
283
+ }
284
+ /**
285
+ * Layout-only sanitized slice value: finite and positive, else 0. Negative
286
+ * and non-finite values would corrupt the arc sweep (a negative slice drags
287
+ * the running angle backwards, desyncing fills / labels / hit-test, and a
288
+ * `NaN` slips past the `total <= 0` guard since `NaN <= 0` is false), so they
289
+ * contribute nothing to the geometry. The original value is still surfaced
290
+ * verbatim in reported SliceInfo / HoverInfo.
291
+ */
292
+ #o(e) {
293
+ return Number.isFinite(e) && e > 0 ? e : 0;
294
+ }
295
+ /** Sum of layout-sanitized slice values — the denominator for every arc
296
+ * sweep and percentage. Always finite and ≥ 0. */
297
+ #f() {
298
+ let e = 0;
299
+ for (const t of this.#t)
300
+ e += this.#o(t.value);
301
+ return e;
302
+ }
303
+ /** Build the text shown for one slice given the resolved content mode. */
304
+ #d(e, t, o) {
305
+ const i = t > 0 ? this.#o(e.value) / t * 100 : 0, s = `${i.toFixed(i >= 10 ? 0 : 1)}%`;
306
+ return o === "percent" ? s : o === "label" ? e.label : `${e.label} ${s}`;
307
+ }
308
+ /**
309
+ * Bitmap-pixel horizontal reserve needed for outside labels. Measured using
310
+ * the live `context` so font metrics match the paint path exactly. Cached
311
+ * until `setData` / `updateOptions` fires (`#labelReserveCache` is cleared).
312
+ *
313
+ * Keyed by both `fontScale` (= `horizontalPixelRatio` — HiDPI changes) and
314
+ * the active `context.font` string so a theme swap that changes the font
315
+ * family / size busts the cache and triggers a fresh measurement.
316
+ */
317
+ #b(e, t, o, i) {
318
+ const s = e.font;
319
+ if (this.#l && this.#l.fontScale === i && this.#l.font === s)
320
+ return this.#l.reserve;
321
+ let d = 0;
322
+ for (const g of this.#t) {
323
+ const C = this.#d(g, o, t.content), x = e.measureText(C).width;
324
+ x > d && (d = x);
325
+ }
326
+ const r = Math.max(t.distance, t.elbowLen), c = (d + (r + Math.max(0, t.railWidth) + t.legPad + 4) * i) / et;
327
+ return this.#l = { fontScale: i, font: s, reserve: c }, c;
328
+ }
329
+ /**
330
+ * Draw one outside label: anchor dot → radial elbow (per-slice, at slice
331
+ * midangle) → short diagonal to the de-cluster-adjusted rail y → horizontal
332
+ * rail → text. Keeping the elbow local to each slice is what stops leader
333
+ * lines from cutting across the pie when several small slices cluster near
334
+ * 12 o'clock or 6 o'clock.
335
+ */
336
+ #g(e) {
337
+ const { context: t, entry: o, textColor: i, labels: s, hpr: d, progress: r } = e;
338
+ if (r <= 0) return;
339
+ const { anchorX: c, anchorY: g, elbowX: C, elbowY: x, side: R, text: M, color: X, labelX: $, labelY: L } = o, A = $ + R * s.legPad * d, b = E(r / 0.15, 0, 1), I = E((r - 0.15) / 0.45, 0, 1), S = E((r - 0.6) / 0.4, 0, 1);
340
+ if (t.save(), t.globalAlpha = b, t.fillStyle = X, t.beginPath(), t.arc(c, g, 2.5 * d, 0, N), t.fill(), t.restore(), I > 0) {
341
+ t.save(), t.globalAlpha = I, t.strokeStyle = X, t.lineWidth = 1 * d, t.lineCap = "round", t.beginPath(), t.moveTo(c, g);
342
+ const v = c + (C - c) * I, u = g + (x - g) * I;
343
+ if (t.lineTo(v, u), S > 0) {
344
+ const a = C + ($ - C) * S, w = x + (L - x) * S;
345
+ t.lineTo(a, w);
346
+ }
347
+ t.stroke(), t.restore();
348
+ }
349
+ S > 0 && (t.save(), t.globalAlpha = S, t.fillStyle = i, t.textAlign = R >= 0 ? "left" : "right", t.textBaseline = "middle", t.fillText(M, A, L), t.restore());
350
+ }
351
+ /**
352
+ * Lay out one outside label per eligible slice. Returns entries in original
353
+ * slice order so the caller can map by index to reveal progress / explode.
354
+ *
355
+ * Radial per-slice layout: each label sits on its own slice's radial ray at
356
+ * radius `outerR + distance`. Rails stay short regardless of slice angle
357
+ * because the label is placed next to its slice, not at a canvas-wide
358
+ * column. PAV resolves vertical crowding by adjusting Y (labelX stays
359
+ * pinned to the slice's midangle ray).
360
+ *
361
+ * Side assignment is purely geometric (sign of cos). `balanceSides` is a
362
+ * no-op here because each label's X is tied to its slice — forcing a slice
363
+ * to the opposite side would mean flipping text direction without moving
364
+ * the label, which would push text across the pie.
365
+ */
366
+ #x(e) {
367
+ const { cx: t, cy: o, outerR: i, total: s, palette: d, theme: r, labels: c, hpr: g, bitmapHeight: C } = e, x = new Array(this.#t.length).fill(null), R = c.elbowLen * g, M = Math.max(c.distance, c.elbowLen) * g, X = Math.max(0, c.railWidth) * g, $ = [];
368
+ let L = -Math.PI / 2;
369
+ const A = c.minSliceAngle * U;
370
+ for (let v = 0; v < this.#t.length; v++) {
371
+ const u = this.#t[v], a = this.#o(u.value) / s * N;
372
+ if (a >= A) {
373
+ const w = L + a / 2, n = this.#s[v] * (8 * g), f = Math.cos(w), h = Math.sin(w), m = f >= 0 ? 1 : -1, O = t + f * (i + n), P = o + h * (i + n), H = t + f * (i + n + R), p = o + h * (i + n + R), W = t + f * (i + n + M) + m * X, y = o + h * (i + n + M), Y = {
374
+ anchorX: O,
375
+ anchorY: P,
376
+ elbowX: H,
377
+ elbowY: p,
378
+ side: m,
379
+ labelX: W,
380
+ labelY: y,
381
+ text: this.#d(u, s, c.content),
382
+ color: this.#h({ index: v, palette: d, theme: r })
383
+ };
384
+ x[v] = Y, $.push({ entry: Y });
385
+ }
386
+ L += a;
387
+ }
388
+ const b = Math.max(1, c.fontSize * c.labelGap) * g, I = c.fontSize * g, S = C - c.fontSize * g;
389
+ for (const v of [1, -1]) {
390
+ const u = [];
391
+ for (const n of $)
392
+ n.entry.side === v && u.push(n.entry);
393
+ if (u.length === 0) continue;
394
+ u.sort((n, f) => n.labelY - f.labelY);
395
+ const a = [];
396
+ for (let n = 0; n < u.length; n++)
397
+ for (a.push({ count: 1, sumIdeal: u[n].labelY, startIdx: n }); a.length >= 2; ) {
398
+ const f = a[a.length - 2], h = a[a.length - 1], m = f.sumIdeal / f.count, O = h.sumIdeal / h.count, P = m + (f.count - 1) * b / 2;
399
+ if (O - (h.count - 1) * b / 2 >= P + b) break;
400
+ f.count += h.count, f.sumIdeal += h.sumIdeal, a.pop();
401
+ }
402
+ if (a.length > 0) {
403
+ const n = a[0], f = n.sumIdeal / n.count - (n.count - 1) * b / 2;
404
+ if (f < I)
405
+ for (n.sumIdeal += (I - f) * n.count; a.length >= 2; ) {
406
+ const h = a[0], m = a[1], O = h.sumIdeal / h.count, P = m.sumIdeal / m.count, H = O + (h.count - 1) * b / 2;
407
+ if (P - (m.count - 1) * b / 2 >= H + b) break;
408
+ h.count += m.count, h.sumIdeal += m.sumIdeal, a.splice(1, 1);
409
+ }
410
+ }
411
+ if (a.length > 0) {
412
+ const n = a[a.length - 1], f = n.sumIdeal / n.count + (n.count - 1) * b / 2;
413
+ if (f > S)
414
+ for (n.sumIdeal -= (f - S) * n.count; a.length >= 2; ) {
415
+ const h = a[a.length - 2], m = a[a.length - 1], O = h.sumIdeal / h.count, P = m.sumIdeal / m.count, H = O + (h.count - 1) * b / 2;
416
+ if (P - (m.count - 1) * b / 2 >= H + b) break;
417
+ h.count += m.count, h.sumIdeal += m.sumIdeal, a.pop();
418
+ }
419
+ }
420
+ const w = (I + S) / 2;
421
+ for (const n of a) {
422
+ const f = (n.count - 1) * b, h = n.sumIdeal / n.count;
423
+ f > S - I && h - f / 2 < I && (n.sumIdeal = w * n.count);
424
+ }
425
+ for (const n of a) {
426
+ const f = n.sumIdeal / n.count;
427
+ for (let h = 0; h < n.count; h++)
428
+ u[n.startIdx + h].labelY = f + (h - (n.count - 1) / 2) * b;
429
+ }
430
+ }
431
+ for (const v of $) {
432
+ const u = v.entry, a = u.labelY - u.anchorY;
433
+ let w;
434
+ if (Math.abs(a) <= R) {
435
+ const h = u.side * Math.sqrt(Math.max(0, R * R - a * a));
436
+ w = u.anchorX + h;
437
+ } else
438
+ w = (u.anchorX + u.labelX) / 2;
439
+ const n = Math.min(u.anchorX, u.labelX), f = Math.max(u.anchorX, u.labelX);
440
+ u.elbowX = Math.max(n, Math.min(f, w)), u.elbowY = u.labelY;
441
+ }
442
+ return x;
443
+ }
444
+ render(e) {
445
+ if (this.#t.length === 0) return;
446
+ const { scope: t, theme: o, padding: i } = e, { context: s, bitmapSize: d, horizontalPixelRatio: r, verticalPixelRatio: c } = t, g = performance.now(), C = this.#u ? Math.min(0.05, (g - this.#u) / 1e3) : 0;
447
+ this.#u = g;
448
+ const x = this.#f();
449
+ if (x <= 0) return;
450
+ const R = 12;
451
+ for (; this.#s.length < this.#t.length; )
452
+ this.#s.push(0);
453
+ if (this.#e.animate)
454
+ for (let p = 0; p < this.#t.length; p++) {
455
+ const W = p === this.#i ? 1 : 0;
456
+ this.#s[p] = K(this.#s[p], W, R, C);
457
+ }
458
+ const M = it(this.#e.sliceLabels);
459
+ s.font = `${M.fontSize * r}px ${o.typography.fontFamily}`, M.mode === "outside" && this.#e.animate && (this.#a = K(this.#a, 1, 6, C));
460
+ const X = this.#e.colors ?? o.seriesColors, $ = M.mode === "outside" ? this.#b(s, M, x, r) : 0, { cx: L, cy: A, maxR: b } = Z({
461
+ bitmapWidth: d.width,
462
+ bitmapHeight: d.height,
463
+ padTop: i.top * c,
464
+ padBottom: i.bottom * c,
465
+ labelReserve: $
466
+ }), I = b, S = I * this.#e.innerRadiusRatio, v = this.#e.padAngle * U, u = 8 * r, a = this.#e.shadow ?? !1, w = at(a), n = this.#e.innerShadow ?? !1, f = rt(n), h = this.#e.gradient !== !1;
467
+ let m = -Math.PI / 2;
468
+ for (let p = 0; p < this.#t.length; p++) {
469
+ const W = this.#t[p], y = this.#o(W.value) / x * N, Y = m + v / 2, B = m + y - v / 2, D = m + y / 2, F = this.#h({ index: p, palette: X, theme: o });
470
+ if (B <= Y) {
471
+ m += y;
472
+ continue;
473
+ }
474
+ const V = this.#s[p] * u, q = Math.cos(D) * V, _ = Math.sin(D) * V, z = L + q, k = A + _;
475
+ if (s.beginPath(), s.arc(z, k, I, Y, B), S > 0 ? s.arc(z, k, S, B, Y, !0) : s.lineTo(z, k), s.closePath(), h || n) {
476
+ const T = s.createRadialGradient(z, k, S || 0, z, k, I);
477
+ if (T.addColorStop(0, h ? ct(F, 0.15) : F), n) {
478
+ const G = Math.max(0, Math.min(0.999, 1 - f.depth));
479
+ T.addColorStop(G, F), T.addColorStop(1, ut(F, f.color));
480
+ } else
481
+ T.addColorStop(1, F);
482
+ s.fillStyle = T;
483
+ } else
484
+ s.fillStyle = F;
485
+ const j = this.#s[p];
486
+ if (a || j > 0.01) {
487
+ const T = a ? 1 : 0, G = j;
488
+ s.shadowColor = a ? w.color : "rgba(0,0,0,0.25)", s.shadowBlur = (w.blur * T + 12 * G) * r, s.shadowOffsetX = w.offsetX * T * r + q * 0.3 * G, s.shadowOffsetY = w.offsetY * T * c + _ * 0.3 * G;
489
+ }
490
+ s.fill(), s.shadowColor = "transparent", s.shadowBlur = 0, s.shadowOffsetX = 0, s.shadowOffsetY = 0, m += y;
491
+ }
492
+ if (M.mode === "none") return;
493
+ m = -Math.PI / 2;
494
+ const O = o.tooltip.textColor;
495
+ if (M.mode === "outside") {
496
+ const p = this.#x({
497
+ cx: L,
498
+ cy: A,
499
+ outerR: I,
500
+ total: x,
501
+ palette: X,
502
+ theme: o,
503
+ labels: M,
504
+ hpr: r,
505
+ bitmapHeight: d.height
506
+ }), W = p.length > 1 ? Math.min(0.06, 0.9 / (p.length - 1)) : 0;
507
+ for (let y = 0; y < p.length; y++) {
508
+ const Y = p[y];
509
+ if (Y === null) continue;
510
+ const B = y * W, D = E((this.#a - B) / (1 - B), 0, 1);
511
+ this.#g({
512
+ context: s,
513
+ entry: Y,
514
+ textColor: O,
515
+ labels: M,
516
+ hpr: r,
517
+ progress: D
518
+ });
519
+ }
520
+ return;
521
+ }
522
+ const P = S > 0 ? (I + S) / 2 : I * 0.65;
523
+ s.textAlign = "center", s.textBaseline = "middle";
524
+ const H = M.minSliceAngle * U;
525
+ for (let p = 0; p < this.#t.length; p++) {
526
+ const W = this.#t[p], y = this.#o(W.value) / x * N;
527
+ if (y >= H) {
528
+ const Y = m + y / 2, B = this.#s[p] * u, D = this.#h({ index: p, palette: X, theme: o }), F = this.#d(W, x, M.content), V = s.measureText(F).width, q = M.fontSize * 1.2 * r, _ = y < Math.PI ? 2 * P * Math.sin(y / 2) : 2 * P, z = V > _ - 4 * r, k = S > 0 && P - S < q;
529
+ if (!z && !k) {
530
+ const j = L + Math.cos(Y) * (P + B), T = A + Math.sin(Y) * (P + B);
531
+ s.fillStyle = ht(D) ? "#000000" : "#ffffff", s.fillText(F, j, T);
532
+ }
533
+ }
534
+ m += y;
535
+ }
536
+ }
537
+ }
538
+ const pt = {
539
+ type: "pie",
540
+ create: (l, e) => new ft({ ...e })
541
+ };
542
+ export {
543
+ ft as PieRenderer,
544
+ pt as PieSeriesDef
545
+ };
@@ -0,0 +1,11 @@
1
+ import { BarSeriesDef as i } from "./bar.js";
2
+ import { CandlestickSeriesDef as r } from "./candlestick.js";
3
+ import { registerSeriesDefinition as e } from "./definition.js";
4
+ import { LineSeriesDef as t } from "./line.js";
5
+ import { PieSeriesDef as s } from "./pie.js";
6
+ function S() {
7
+ e(r), e(t), e(i), e(s);
8
+ }
9
+ export {
10
+ S as registerBuiltinSeries
11
+ };
@@ -0,0 +1,17 @@
1
+ function r(t) {
2
+ let n = 0, i = 0;
3
+ for (const e of t)
4
+ e > 0 ? n += e : e < 0 && (i += e);
5
+ return { positive: n, negative: i };
6
+ }
7
+ function o(t) {
8
+ return t.positive > 0 ? t.positive : t.negative < 0 ? t.negative : 0;
9
+ }
10
+ function a(t) {
11
+ return t.positive > 0 ? 100 : t.negative < 0 ? -100 : 0;
12
+ }
13
+ export {
14
+ a as renderedStackPercentTop,
15
+ o as renderedStackTop,
16
+ r as sumStack
17
+ };
@@ -0,0 +1,107 @@
1
+ const f = /* @__PURE__ */ new WeakMap();
2
+ function b(e, r) {
3
+ return f.get(e)?.get(r);
4
+ }
5
+ function g(e, r, o) {
6
+ let n = f.get(e);
7
+ n || (n = /* @__PURE__ */ new Map(), f.set(e, n)), n.set(r, o);
8
+ }
9
+ function y(e) {
10
+ return "close" in e ? e.close : e.value;
11
+ }
12
+ function I(e, r) {
13
+ if (r === "none" || e.length <= 1) return e;
14
+ const o = [...e];
15
+ return o.sort((n, s) => {
16
+ const i = y(n.data), l = y(s.data);
17
+ return r === "asc" ? i - l : l - i;
18
+ }), o;
19
+ }
20
+ function u(e) {
21
+ const r = Object.freeze({ ...e.data }), o = {
22
+ id: e.id,
23
+ seriesId: e.seriesId,
24
+ layerIndex: e.layerIndex,
25
+ label: e.label,
26
+ data: r,
27
+ color: e.color
28
+ };
29
+ return Object.freeze(o);
30
+ }
31
+ function m(e, r) {
32
+ const o = r.sort ?? "none", n = e.getOverlayVersion(), s = b(e, r.cacheKey);
33
+ if (s && s.version === n && s.time === r.time && s.sort === o)
34
+ return s.data;
35
+ const i = [];
36
+ for (const t of e.getSeriesIds()) {
37
+ if (!e.isSeriesVisible(t)) continue;
38
+ if (e.getSeriesLayers(t) !== null) {
39
+ const d = e.getLayerSnapshots(t, r.time);
40
+ if (!d) continue;
41
+ for (const a of d)
42
+ i.push(
43
+ u({
44
+ id: `${t}_layer${a.layerIndex}`,
45
+ seriesId: t,
46
+ layerIndex: a.layerIndex,
47
+ label: e.getLayerLabel(t, a.layerIndex),
48
+ data: { time: a.time, value: a.value },
49
+ color: a.color
50
+ })
51
+ );
52
+ continue;
53
+ }
54
+ const c = e.getDataAtTime(t, r.time);
55
+ c && i.push(
56
+ u({
57
+ id: t,
58
+ seriesId: t,
59
+ label: e.getSeriesLabel(t),
60
+ data: c,
61
+ color: e.getSeriesColor(t) ?? "#888"
62
+ })
63
+ );
64
+ }
65
+ const l = Object.freeze(I(i, o));
66
+ return g(e, r.cacheKey, { version: n, time: r.time, sort: o, data: l }), l;
67
+ }
68
+ function S(e, r) {
69
+ const o = r.sort ?? "none", n = e.getOverlayVersion(), s = b(e, r.cacheKey);
70
+ if (s && s.version === n && s.time === null && s.sort === o)
71
+ return s.data;
72
+ const i = [];
73
+ for (const t of e.getSeriesIds()) {
74
+ if (!e.isSeriesVisible(t)) continue;
75
+ const c = e.getLayerLastSnapshots(t);
76
+ if (c) {
77
+ for (const a of c)
78
+ i.push(
79
+ u({
80
+ id: `${t}_layer${a.layerIndex}`,
81
+ seriesId: t,
82
+ layerIndex: a.layerIndex,
83
+ label: e.getLayerLabel(t, a.layerIndex),
84
+ data: { time: a.time, value: a.value },
85
+ color: a.color
86
+ })
87
+ );
88
+ continue;
89
+ }
90
+ const d = e.getLastData(t);
91
+ d && i.push(
92
+ u({
93
+ id: t,
94
+ seriesId: t,
95
+ label: e.getSeriesLabel(t),
96
+ data: d,
97
+ color: e.getSeriesColor(t) ?? "#888"
98
+ })
99
+ );
100
+ }
101
+ const l = Object.freeze(I(i, o));
102
+ return g(e, r.cacheKey, { version: n, time: null, sort: o, data: l }), l;
103
+ }
104
+ export {
105
+ m as buildHoverSnapshots,
106
+ S as buildLastSnapshots
107
+ };