chartforge 0.0.1 → 0.0.2

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 (94) hide show
  1. package/dist/chartforge.js +1148 -0
  2. package/dist/chartforge.umd.cjs +1 -0
  3. package/dist/index-BCodEFNd.js +1060 -0
  4. package/dist/plugins.js +14 -0
  5. package/dist/themes.js +44 -0
  6. package/dist/types/ChartForge.d.ts +50 -0
  7. package/dist/types/ChartForge.d.ts.map +1 -0
  8. package/dist/types/adapters/PollingAdapter.d.ts +18 -0
  9. package/dist/types/adapters/PollingAdapter.d.ts.map +1 -0
  10. package/dist/types/adapters/RealTimeModule.d.ts +14 -0
  11. package/dist/types/adapters/RealTimeModule.d.ts.map +1 -0
  12. package/dist/types/adapters/WebSocketAdapter.d.ts +17 -0
  13. package/dist/types/adapters/WebSocketAdapter.d.ts.map +1 -0
  14. package/dist/types/adapters/index.d.ts +4 -0
  15. package/dist/types/adapters/index.d.ts.map +1 -0
  16. package/dist/types/core/AnimationEngine.d.ts +8 -0
  17. package/dist/types/core/AnimationEngine.d.ts.map +1 -0
  18. package/dist/types/core/DataPipeline.d.ts +10 -0
  19. package/dist/types/core/DataPipeline.d.ts.map +1 -0
  20. package/dist/types/core/EventBus.d.ts +10 -0
  21. package/dist/types/core/EventBus.d.ts.map +1 -0
  22. package/dist/types/core/MiddlewarePipeline.d.ts +7 -0
  23. package/dist/types/core/MiddlewarePipeline.d.ts.map +1 -0
  24. package/dist/types/core/PluginManager.d.ts +15 -0
  25. package/dist/types/core/PluginManager.d.ts.map +1 -0
  26. package/dist/types/core/ThemeManager.d.ts +11 -0
  27. package/dist/types/core/ThemeManager.d.ts.map +1 -0
  28. package/dist/types/core/VirtualRenderer.d.ts +18 -0
  29. package/dist/types/core/VirtualRenderer.d.ts.map +1 -0
  30. package/dist/types/core/index.d.ts +8 -0
  31. package/dist/types/core/index.d.ts.map +1 -0
  32. package/dist/types/index.d.ts +9 -0
  33. package/dist/types/index.d.ts.map +1 -0
  34. package/dist/types/plugins/AnnotationPlugin.d.ts +42 -0
  35. package/dist/types/plugins/AnnotationPlugin.d.ts.map +1 -0
  36. package/dist/types/plugins/AxisPlugin.d.ts +27 -0
  37. package/dist/types/plugins/AxisPlugin.d.ts.map +1 -0
  38. package/dist/types/plugins/BasePlugin.d.ts +10 -0
  39. package/dist/types/plugins/BasePlugin.d.ts.map +1 -0
  40. package/dist/types/plugins/CrosshairPlugin.d.ts +29 -0
  41. package/dist/types/plugins/CrosshairPlugin.d.ts.map +1 -0
  42. package/dist/types/plugins/DataLabelsPlugin.d.ts +19 -0
  43. package/dist/types/plugins/DataLabelsPlugin.d.ts.map +1 -0
  44. package/dist/types/plugins/ExportPlugin.d.ts +20 -0
  45. package/dist/types/plugins/ExportPlugin.d.ts.map +1 -0
  46. package/dist/types/plugins/GridPlugin.d.ts +26 -0
  47. package/dist/types/plugins/GridPlugin.d.ts.map +1 -0
  48. package/dist/types/plugins/LegendPlugin.d.ts +26 -0
  49. package/dist/types/plugins/LegendPlugin.d.ts.map +1 -0
  50. package/dist/types/plugins/TooltipPlugin.d.ts +34 -0
  51. package/dist/types/plugins/TooltipPlugin.d.ts.map +1 -0
  52. package/dist/types/plugins/ZoomPlugin.d.ts +24 -0
  53. package/dist/types/plugins/ZoomPlugin.d.ts.map +1 -0
  54. package/dist/types/plugins/index.d.ts +37 -0
  55. package/dist/types/plugins/index.d.ts.map +1 -0
  56. package/dist/types/renderers/BarRenderer.d.ts +5 -0
  57. package/dist/types/renderers/BarRenderer.d.ts.map +1 -0
  58. package/dist/types/renderers/BaseRenderer.d.ts +31 -0
  59. package/dist/types/renderers/BaseRenderer.d.ts.map +1 -0
  60. package/dist/types/renderers/CandlestickRenderer.d.ts +5 -0
  61. package/dist/types/renderers/CandlestickRenderer.d.ts.map +1 -0
  62. package/dist/types/renderers/ColumnRenderer.d.ts +5 -0
  63. package/dist/types/renderers/ColumnRenderer.d.ts.map +1 -0
  64. package/dist/types/renderers/DonutRenderer.d.ts +5 -0
  65. package/dist/types/renderers/DonutRenderer.d.ts.map +1 -0
  66. package/dist/types/renderers/FunnelRenderer.d.ts +5 -0
  67. package/dist/types/renderers/FunnelRenderer.d.ts.map +1 -0
  68. package/dist/types/renderers/HeatmapRenderer.d.ts +5 -0
  69. package/dist/types/renderers/HeatmapRenderer.d.ts.map +1 -0
  70. package/dist/types/renderers/LineRenderer.d.ts +5 -0
  71. package/dist/types/renderers/LineRenderer.d.ts.map +1 -0
  72. package/dist/types/renderers/PieRenderer.d.ts +8 -0
  73. package/dist/types/renderers/PieRenderer.d.ts.map +1 -0
  74. package/dist/types/renderers/ScatterRenderer.d.ts +5 -0
  75. package/dist/types/renderers/ScatterRenderer.d.ts.map +1 -0
  76. package/dist/types/renderers/StackedBarRenderer.d.ts +5 -0
  77. package/dist/types/renderers/StackedBarRenderer.d.ts.map +1 -0
  78. package/dist/types/renderers/StackedColumnRenderer.d.ts +5 -0
  79. package/dist/types/renderers/StackedColumnRenderer.d.ts.map +1 -0
  80. package/dist/types/renderers/index.d.ts +17 -0
  81. package/dist/types/renderers/index.d.ts.map +1 -0
  82. package/dist/types/themes/builtins.d.ts +6 -0
  83. package/dist/types/themes/builtins.d.ts.map +1 -0
  84. package/dist/types/themes/index.d.ts +2 -0
  85. package/dist/types/themes/index.d.ts.map +1 -0
  86. package/dist/types/types.d.ts +97 -0
  87. package/dist/types/types.d.ts.map +1 -0
  88. package/dist/types/utils/dom.d.ts +9 -0
  89. package/dist/types/utils/dom.d.ts.map +1 -0
  90. package/dist/types/utils/index.d.ts +3 -0
  91. package/dist/types/utils/index.d.ts.map +1 -0
  92. package/dist/types/utils/misc.d.ts +12 -0
  93. package/dist/types/utils/misc.d.ts.map +1 -0
  94. package/package.json +10 -8
@@ -0,0 +1,1148 @@
1
+ import { BUILT_IN_THEMES } from "./themes.js";
2
+ import { darkTheme, lightTheme, neonTheme } from "./themes.js";
3
+ import { c as createSVGElement, p as polarToCartesian, u as uid, m as merge, d as debounce, r as removeChildren } from "./index-BCodEFNd.js";
4
+ import { a, b, f, e, g, t } from "./index-BCodEFNd.js";
5
+ class EventBus {
6
+ constructor() {
7
+ this._events = /* @__PURE__ */ new Map();
8
+ }
9
+ on(event, handler, priority = 0) {
10
+ if (!this._events.has(event)) this._events.set(event, []);
11
+ const list = this._events.get(event);
12
+ list.push({ handler, priority });
13
+ list.sort((a2, b2) => b2.priority - a2.priority);
14
+ return () => this.off(event, handler);
15
+ }
16
+ off(event, handler) {
17
+ const list = this._events.get(event);
18
+ if (!list) return;
19
+ const idx = list.findIndex((h) => h.handler === handler);
20
+ if (idx !== -1) list.splice(idx, 1);
21
+ }
22
+ emit(event, data) {
23
+ this._events.get(event)?.forEach(({ handler }) => handler(data));
24
+ }
25
+ async emitAsync(event, data) {
26
+ const list = this._events.get(event);
27
+ if (!list) return;
28
+ for (const { handler } of list) await handler(data);
29
+ }
30
+ clear(event) {
31
+ event ? this._events.delete(event) : this._events.clear();
32
+ }
33
+ }
34
+ class MiddlewarePipeline {
35
+ constructor() {
36
+ this._fns = [];
37
+ }
38
+ use(fn) {
39
+ this._fns.push(fn);
40
+ return this;
41
+ }
42
+ async execute(ctx) {
43
+ let i = 0;
44
+ const next = async () => {
45
+ if (i >= this._fns.length) return;
46
+ await this._fns[i++](ctx, next);
47
+ };
48
+ await next();
49
+ return ctx;
50
+ }
51
+ }
52
+ class DataPipeline {
53
+ constructor() {
54
+ this._transformers = [];
55
+ }
56
+ addTransformer(name, fn) {
57
+ this._transformers.push({ name, fn });
58
+ return this;
59
+ }
60
+ removeTransformer(name) {
61
+ const idx = this._transformers.findIndex((t2) => t2.name === name);
62
+ if (idx !== -1) this._transformers.splice(idx, 1);
63
+ }
64
+ async transform(data, config) {
65
+ let result = data;
66
+ for (const t2 of this._transformers) {
67
+ result = await t2.fn(result, config);
68
+ }
69
+ return result;
70
+ }
71
+ }
72
+ const EASINGS = {
73
+ linear: (t2) => t2,
74
+ easeInQuad: (t2) => t2 * t2,
75
+ easeOutQuad: (t2) => t2 * (2 - t2),
76
+ easeInOutQuad: (t2) => t2 < 0.5 ? 2 * t2 * t2 : -1 + (4 - 2 * t2) * t2,
77
+ easeInCubic: (t2) => t2 * t2 * t2,
78
+ easeOutCubic: (t2) => --t2 * t2 * t2 + 1,
79
+ easeInOutCubic: (t2) => t2 < 0.5 ? 4 * t2 * t2 * t2 : (t2 - 1) * (2 * t2 - 2) * (2 * t2 - 2) + 1,
80
+ easeInElastic: (t2) => {
81
+ const c4 = 2 * Math.PI / 3;
82
+ return t2 === 0 ? 0 : t2 === 1 ? 1 : -Math.pow(2, 10 * t2 - 10) * Math.sin((t2 * 10 - 10.75) * c4);
83
+ },
84
+ easeOutElastic: (t2) => {
85
+ const c4 = 2 * Math.PI / 3;
86
+ return t2 === 0 ? 0 : t2 === 1 ? 1 : Math.pow(2, -10 * t2) * Math.sin((t2 * 10 - 0.75) * c4) + 1;
87
+ },
88
+ easeInBounce: (t2) => {
89
+ const n1 = 7.5625, d1 = 2.75;
90
+ const eob = (t22) => {
91
+ if (t22 < 1 / d1) return n1 * t22 * t22;
92
+ if (t22 < 2 / d1) return n1 * (t22 -= 1.5 / d1) * t22 + 0.75;
93
+ if (t22 < 2.5 / d1) return n1 * (t22 -= 2.25 / d1) * t22 + 0.9375;
94
+ return n1 * (t22 -= 2.625 / d1) * t22 + 0.984375;
95
+ };
96
+ return 1 - eob(1 - t2);
97
+ },
98
+ easeOutBounce: (t2) => {
99
+ const n1 = 7.5625, d1 = 2.75;
100
+ if (t2 < 1 / d1) return n1 * t2 * t2;
101
+ if (t2 < 2 / d1) return n1 * (t2 -= 1.5 / d1) * t2 + 0.75;
102
+ if (t2 < 2.5 / d1) return n1 * (t2 -= 2.25 / d1) * t2 + 0.9375;
103
+ return n1 * (t2 -= 2.625 / d1) * t2 + 0.984375;
104
+ }
105
+ };
106
+ class AnimationEngine {
107
+ constructor() {
108
+ this._running = /* @__PURE__ */ new Map();
109
+ }
110
+ animate(id, from, to, duration, easing = "easeOutQuad", onUpdate, onComplete) {
111
+ this.stop(id);
112
+ const easingFn = EASINGS[easing] ?? EASINGS.linear;
113
+ const start = performance.now();
114
+ const tick = (now) => {
115
+ const elapsed = now - start;
116
+ const progress = Math.min(elapsed / duration, 1);
117
+ const eased = easingFn(progress);
118
+ onUpdate(from + (to - from) * eased, progress);
119
+ if (progress < 1) {
120
+ const rafId2 = requestAnimationFrame(tick);
121
+ this._running.set(id, { rafId: rafId2 });
122
+ } else {
123
+ this._running.delete(id);
124
+ onComplete?.();
125
+ }
126
+ };
127
+ const rafId = requestAnimationFrame(tick);
128
+ this._running.set(id, { rafId });
129
+ }
130
+ stop(id) {
131
+ const state = this._running.get(id);
132
+ if (state) {
133
+ cancelAnimationFrame(state.rafId);
134
+ this._running.delete(id);
135
+ }
136
+ }
137
+ stopAll() {
138
+ this._running.forEach((s) => cancelAnimationFrame(s.rafId));
139
+ this._running.clear();
140
+ }
141
+ }
142
+ class ThemeManager {
143
+ constructor() {
144
+ this._themes = /* @__PURE__ */ new Map();
145
+ this._current = null;
146
+ }
147
+ register(name, theme) {
148
+ this._themes.set(name, theme);
149
+ }
150
+ get(name) {
151
+ return this._themes.get(name);
152
+ }
153
+ apply(name) {
154
+ const theme = this._themes.get(name);
155
+ if (!theme) {
156
+ console.warn(`[ChartForge] Theme "${name}" not found`);
157
+ return null;
158
+ }
159
+ this._current = name;
160
+ return theme;
161
+ }
162
+ getCurrent() {
163
+ return this._current ? this._themes.get(this._current) ?? null : null;
164
+ }
165
+ get currentName() {
166
+ return this._current;
167
+ }
168
+ }
169
+ class PluginManager {
170
+ constructor(_chart) {
171
+ this._chart = _chart;
172
+ this._plugins = /* @__PURE__ */ new Map();
173
+ this._inited = /* @__PURE__ */ new Set();
174
+ }
175
+ register(name, Plugin, config = {}) {
176
+ if (this._plugins.has(name)) {
177
+ console.warn(`[ChartForge] Plugin "${name}" already registered`);
178
+ return;
179
+ }
180
+ const instance = new Plugin(this._chart, config);
181
+ this._plugins.set(name, { instance, config });
182
+ if (this._chart.initialized) {
183
+ this._initOne(name, instance);
184
+ }
185
+ }
186
+ get(name) {
187
+ return this._plugins.get(name)?.instance ?? null;
188
+ }
189
+ has(name) {
190
+ return this._plugins.has(name);
191
+ }
192
+ remove(name) {
193
+ const entry = this._plugins.get(name);
194
+ if (!entry) return;
195
+ entry.instance.destroy?.();
196
+ this._plugins.delete(name);
197
+ this._inited.delete(name);
198
+ }
199
+ initAll() {
200
+ this._plugins.forEach((entry, name) => {
201
+ if (!this._inited.has(name)) this._initOne(name, entry.instance);
202
+ });
203
+ }
204
+ destroyAll() {
205
+ this._plugins.forEach((e2) => e2.instance.destroy?.());
206
+ this._plugins.clear();
207
+ this._inited.clear();
208
+ }
209
+ _initOne(name, plugin) {
210
+ plugin.init?.();
211
+ this._inited.add(name);
212
+ }
213
+ }
214
+ class VirtualRenderer {
215
+ constructor(_chart) {
216
+ this._chart = _chart;
217
+ this._viewport = { start: 0, end: 100 };
218
+ this._threshold = _chart.config.virtual?.threshold ?? 1e4;
219
+ }
220
+ updateViewport(start, end) {
221
+ this._viewport = { start, end };
222
+ }
223
+ shouldVirtualize() {
224
+ const total = this._chart.config.data.series.reduce(
225
+ (sum, s) => sum + (Array.isArray(s.data) ? s.data.length : 0),
226
+ 0
227
+ );
228
+ return total > this._threshold;
229
+ }
230
+ getVisibleData() {
231
+ const { start, end } = this._viewport;
232
+ const src = this._chart.config.data;
233
+ return {
234
+ series: src.series.map((s) => ({
235
+ ...s,
236
+ data: s.data.slice(start, end)
237
+ })),
238
+ ...src.labels && { labels: src.labels.slice(start, end) }
239
+ };
240
+ }
241
+ }
242
+ class RealTimeModule {
243
+ constructor(_chart) {
244
+ this._chart = _chart;
245
+ this._registry = /* @__PURE__ */ new Map();
246
+ this._connections = /* @__PURE__ */ new Map();
247
+ }
248
+ registerAdapter(name, Adapter) {
249
+ this._registry.set(name, Adapter);
250
+ }
251
+ connect(type, config) {
252
+ const Adapter = this._registry.get(type);
253
+ if (!Adapter) {
254
+ console.error(`[ChartForge] Real-time adapter "${type}" not registered`);
255
+ return;
256
+ }
257
+ const adapter = new Adapter(config);
258
+ adapter.on("data", (data) => this._chart.updateData(data));
259
+ void adapter.connect();
260
+ this._connections.set(type, adapter);
261
+ }
262
+ disconnect(type) {
263
+ const adapter = this._connections.get(type);
264
+ if (adapter) {
265
+ adapter.disconnect();
266
+ this._connections.delete(type);
267
+ }
268
+ }
269
+ disconnectAll() {
270
+ this._connections.forEach((a2) => a2.disconnect());
271
+ this._connections.clear();
272
+ }
273
+ }
274
+ class WebSocketAdapter {
275
+ constructor(_config) {
276
+ this._config = _config;
277
+ this._ws = null;
278
+ this._listeners = /* @__PURE__ */ new Map();
279
+ }
280
+ on(event, handler) {
281
+ if (!this._listeners.has(event)) this._listeners.set(event, []);
282
+ this._listeners.get(event).push(handler);
283
+ }
284
+ _emit(event, data) {
285
+ this._listeners.get(event)?.forEach((h) => h(data));
286
+ }
287
+ connect() {
288
+ this._ws = new WebSocket(this._config.url);
289
+ this._ws.addEventListener("message", (e2) => {
290
+ try {
291
+ const data = JSON.parse(e2.data);
292
+ this._emit("data", data);
293
+ } catch {
294
+ console.warn("[ChartForge] WebSocketAdapter: invalid JSON", e2.data);
295
+ }
296
+ });
297
+ this._ws.addEventListener("error", (e2) => this._emit("error", e2));
298
+ }
299
+ disconnect() {
300
+ this._ws?.close();
301
+ this._ws = null;
302
+ }
303
+ send(data) {
304
+ if (this._ws?.readyState === WebSocket.OPEN) {
305
+ this._ws.send(JSON.stringify(data));
306
+ }
307
+ }
308
+ }
309
+ class PollingAdapter {
310
+ constructor(_config) {
311
+ this._config = _config;
312
+ this._timer = null;
313
+ this._listeners = /* @__PURE__ */ new Map();
314
+ }
315
+ on(event, handler) {
316
+ if (!this._listeners.has(event)) this._listeners.set(event, []);
317
+ this._listeners.get(event).push(handler);
318
+ }
319
+ _emit(event, data) {
320
+ this._listeners.get(event)?.forEach((h) => h(data));
321
+ }
322
+ async _poll() {
323
+ try {
324
+ const res = await fetch(this._config.url);
325
+ const data = await res.json();
326
+ this._emit("data", data);
327
+ } catch (err) {
328
+ this._emit("error", err);
329
+ }
330
+ }
331
+ connect() {
332
+ void this._poll();
333
+ this._timer = setInterval(() => void this._poll(), this._config.interval ?? 5e3);
334
+ }
335
+ disconnect() {
336
+ if (this._timer) {
337
+ clearInterval(this._timer);
338
+ this._timer = null;
339
+ }
340
+ }
341
+ }
342
+ class BaseRenderer {
343
+ constructor(chart, data) {
344
+ this.chart = chart;
345
+ this.data = data;
346
+ this.theme = chart.theme;
347
+ this.config = chart.config;
348
+ this.group = chart.mainGroup;
349
+ this.padding = {
350
+ top: chart.config.padding?.top ?? 40,
351
+ right: chart.config.padding?.right ?? 40,
352
+ bottom: chart.config.padding?.bottom ?? 60,
353
+ left: chart.config.padding?.left ?? 60
354
+ };
355
+ }
356
+ dims() {
357
+ const vb = this.chart.svg.getAttribute("viewBox").split(" ").map(Number);
358
+ const tw = vb[2], th = vb[3];
359
+ return {
360
+ width: tw - this.padding.left - this.padding.right,
361
+ height: th - this.padding.top - this.padding.bottom,
362
+ totalWidth: tw,
363
+ totalHeight: th
364
+ };
365
+ }
366
+ color(i) {
367
+ return this.theme.colors[i % this.theme.colors.length];
368
+ }
369
+ g(className) {
370
+ return createSVGElement("g", { className });
371
+ }
372
+ }
373
+ class PieRenderer extends BaseRenderer {
374
+ render() {
375
+ const d = this.dims();
376
+ const cx = d.totalWidth / 2;
377
+ const cy = d.totalHeight / 2;
378
+ const r = Math.min(d.width, d.height) / 2 - 20;
379
+ const group = this.g("chartforge-pie");
380
+ this.group.appendChild(group);
381
+ this._drawSlices(group, cx, cy, r);
382
+ if (this.config.animation?.enabled) this._animate(group);
383
+ }
384
+ _drawSlices(group, cx, cy, r, innerR = 0) {
385
+ const values = this.data.series[0].data;
386
+ const total = values.reduce((s, v) => s + v, 0);
387
+ let angle = 0;
388
+ values.forEach((value, i) => {
389
+ const sweep = value / total * 360;
390
+ const endAngle = angle + sweep;
391
+ const path = this._slice(cx, cy, r, innerR, angle, endAngle, i);
392
+ group.appendChild(path);
393
+ if (innerR === 0) {
394
+ const mid = angle + sweep / 2;
395
+ const lr = r * 0.7;
396
+ const lpos = polarToCartesian(cx, cy, lr, mid);
397
+ const text = createSVGElement("text", {
398
+ x: lpos.x,
399
+ y: lpos.y,
400
+ "text-anchor": "middle",
401
+ "dominant-baseline": "middle",
402
+ fill: "#fff",
403
+ "font-size": "12",
404
+ "font-weight": "bold"
405
+ });
406
+ text.textContent = `${(value / total * 100).toFixed(1)}%`;
407
+ group.appendChild(text);
408
+ }
409
+ angle = endAngle;
410
+ });
411
+ }
412
+ _slice(cx, cy, outerR, innerR, startAngle, endAngle, i) {
413
+ const oStart = polarToCartesian(cx, cy, outerR, endAngle);
414
+ const oEnd = polarToCartesian(cx, cy, outerR, startAngle);
415
+ const large = endAngle - startAngle <= 180 ? "0" : "1";
416
+ const value = this.data.series[0].data[i];
417
+ let d;
418
+ if (innerR > 0) {
419
+ const iStart = polarToCartesian(cx, cy, innerR, endAngle);
420
+ const iEnd = polarToCartesian(cx, cy, innerR, startAngle);
421
+ d = [
422
+ `M ${oStart.x} ${oStart.y}`,
423
+ `A ${outerR} ${outerR} 0 ${large} 0 ${oEnd.x} ${oEnd.y}`,
424
+ `L ${iEnd.x} ${iEnd.y}`,
425
+ `A ${innerR} ${innerR} 0 ${large} 1 ${iStart.x} ${iStart.y}`,
426
+ "Z"
427
+ ].join(" ");
428
+ } else {
429
+ d = [
430
+ `M ${cx} ${cy}`,
431
+ `L ${oStart.x} ${oStart.y}`,
432
+ `A ${outerR} ${outerR} 0 ${large} 0 ${oEnd.x} ${oEnd.y}`,
433
+ "Z"
434
+ ].join(" ");
435
+ }
436
+ const path = createSVGElement("path", {
437
+ d,
438
+ fill: this.color(i),
439
+ stroke: this.theme.background,
440
+ "stroke-width": "2"
441
+ });
442
+ path.addEventListener("mouseenter", () => {
443
+ path.setAttribute("opacity", "0.8");
444
+ this.chart.emit("hover", { type: "pie", index: i, value });
445
+ });
446
+ path.addEventListener("mouseleave", () => path.setAttribute("opacity", "1"));
447
+ path.addEventListener("click", () => this.chart.emit("click", { type: "pie", index: i, value }));
448
+ return path;
449
+ }
450
+ _animate(group) {
451
+ group.querySelectorAll("path").forEach((path, i) => {
452
+ path.style.transformOrigin = "center";
453
+ path.style.transform = "scale(0)";
454
+ this.chart.animationEngine.animate(
455
+ `pie-${i}`,
456
+ 0,
457
+ 1,
458
+ this.config.animation?.duration ?? 750,
459
+ "easeOutElastic",
460
+ (v) => {
461
+ path.style.transform = `scale(${v})`;
462
+ }
463
+ );
464
+ });
465
+ }
466
+ }
467
+ class DonutRenderer extends PieRenderer {
468
+ render() {
469
+ const d = this.dims();
470
+ const cx = d.totalWidth / 2;
471
+ const cy = d.totalHeight / 2;
472
+ const outerR = Math.min(d.width, d.height) / 2 - 20;
473
+ const innerR = outerR * 0.6;
474
+ const group = this.g("chartforge-donut");
475
+ this.group.appendChild(group);
476
+ this._drawSlices(group, cx, cy, outerR, innerR);
477
+ const label = createSVGElement("text", {
478
+ x: cx,
479
+ y: cy,
480
+ "text-anchor": "middle",
481
+ "dominant-baseline": "middle",
482
+ fill: this.theme.text,
483
+ "font-size": "24",
484
+ "font-weight": "bold"
485
+ });
486
+ label.textContent = "Total";
487
+ group.appendChild(label);
488
+ if (this.config.animation?.enabled) this._animate(group);
489
+ }
490
+ }
491
+ class ColumnRenderer extends BaseRenderer {
492
+ render() {
493
+ const d = this.dims();
494
+ const values = this.data.series[0].data;
495
+ const maxVal = Math.max(...values);
496
+ const cw = d.width / values.length * 0.8;
497
+ const gap = d.width / values.length * 0.2;
498
+ const group = this.g("chartforge-columns");
499
+ this.group.appendChild(group);
500
+ values.forEach((value, i) => {
501
+ const targetH = value / maxVal * d.height;
502
+ const x = this.padding.left + i * (cw + gap);
503
+ const baseY = this.padding.top + d.height;
504
+ const rect = createSVGElement("rect", {
505
+ x,
506
+ y: baseY,
507
+ width: cw,
508
+ height: 0,
509
+ fill: this.color(i)
510
+ });
511
+ group.appendChild(rect);
512
+ if (this.config.animation?.enabled) {
513
+ this.chart.animationEngine.animate(
514
+ `col-${i}`,
515
+ 0,
516
+ targetH,
517
+ this.config.animation.duration ?? 750,
518
+ this.config.animation.easing ?? "easeOutQuad",
519
+ (h) => {
520
+ rect.setAttribute("height", String(h));
521
+ rect.setAttribute("y", String(baseY - h));
522
+ }
523
+ );
524
+ } else {
525
+ rect.setAttribute("height", String(targetH));
526
+ rect.setAttribute("y", String(baseY - targetH));
527
+ }
528
+ rect.addEventListener("mouseenter", () => {
529
+ rect.setAttribute("opacity", "0.8");
530
+ this.chart.emit("hover", { type: "column", index: i, value });
531
+ });
532
+ rect.addEventListener("mouseleave", () => rect.setAttribute("opacity", "1"));
533
+ rect.addEventListener("click", () => this.chart.emit("click", { type: "column", index: i, value }));
534
+ });
535
+ }
536
+ }
537
+ class BarRenderer extends BaseRenderer {
538
+ render() {
539
+ const d = this.dims();
540
+ const values = this.data.series[0].data;
541
+ const maxVal = Math.max(...values);
542
+ const bh = d.height / values.length * 0.8;
543
+ const gap = d.height / values.length * 0.2;
544
+ const group = this.g("chartforge-bars");
545
+ this.group.appendChild(group);
546
+ values.forEach((value, i) => {
547
+ const targetW = value / maxVal * d.width;
548
+ const y = this.padding.top + i * (bh + gap);
549
+ const rect = createSVGElement("rect", {
550
+ x: this.padding.left,
551
+ y,
552
+ width: 0,
553
+ height: bh,
554
+ fill: this.color(i)
555
+ });
556
+ group.appendChild(rect);
557
+ if (this.config.animation?.enabled) {
558
+ this.chart.animationEngine.animate(
559
+ `bar-${i}`,
560
+ 0,
561
+ targetW,
562
+ this.config.animation.duration ?? 750,
563
+ this.config.animation.easing ?? "easeOutQuad",
564
+ (w) => rect.setAttribute("width", String(w))
565
+ );
566
+ } else {
567
+ rect.setAttribute("width", String(targetW));
568
+ }
569
+ rect.addEventListener("mouseenter", () => {
570
+ rect.setAttribute("opacity", "0.8");
571
+ this.chart.emit("hover", { type: "bar", index: i, value });
572
+ });
573
+ rect.addEventListener("mouseleave", () => rect.setAttribute("opacity", "1"));
574
+ rect.addEventListener("click", () => this.chart.emit("click", { type: "bar", index: i, value }));
575
+ const label = createSVGElement("text", {
576
+ x: this.padding.left + 6,
577
+ y: y + bh / 2,
578
+ fill: "#fff",
579
+ "font-size": "12",
580
+ "dominant-baseline": "middle"
581
+ });
582
+ label.textContent = String(this.data.labels?.[i] ?? `Item ${i + 1}`);
583
+ group.appendChild(label);
584
+ });
585
+ }
586
+ }
587
+ class LineRenderer extends BaseRenderer {
588
+ render() {
589
+ const d = this.dims();
590
+ const group = this.g("chartforge-lines");
591
+ this.group.appendChild(group);
592
+ const allVals = this.data.series.flatMap((s) => s.data);
593
+ const maxVal = Math.max(...allVals);
594
+ const minVal = Math.min(...allVals);
595
+ const range = maxVal - minVal || 1;
596
+ this.data.series.forEach((series, si) => {
597
+ const values = series.data;
598
+ const pts = values.map((v, i) => ({
599
+ x: this.padding.left + i / Math.max(values.length - 1, 1) * d.width,
600
+ y: this.padding.top + d.height - (v - minVal) / range * d.height,
601
+ value: v
602
+ }));
603
+ const pathD = pts.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
604
+ const path = createSVGElement("path", {
605
+ d: pathD,
606
+ fill: "none",
607
+ stroke: this.color(si),
608
+ "stroke-width": "2"
609
+ });
610
+ group.appendChild(path);
611
+ pts.forEach((pt, i) => {
612
+ const circle = createSVGElement("circle", {
613
+ cx: pt.x,
614
+ cy: pt.y,
615
+ r: 4,
616
+ fill: this.color(si)
617
+ });
618
+ circle.addEventListener("mouseenter", () => {
619
+ circle.setAttribute("r", "6");
620
+ this.chart.emit("hover", { type: "line", seriesIndex: si, index: i, value: pt.value });
621
+ });
622
+ circle.addEventListener("mouseleave", () => circle.setAttribute("r", "4"));
623
+ circle.addEventListener("click", () => this.chart.emit("click", { type: "line", seriesIndex: si, index: i, value: pt.value }));
624
+ group.appendChild(circle);
625
+ });
626
+ if (this.config.animation?.enabled) {
627
+ const len = path.getTotalLength();
628
+ path.style.strokeDasharray = String(len);
629
+ path.style.strokeDashoffset = String(len);
630
+ this.chart.animationEngine.animate(
631
+ `line-${si}`,
632
+ len,
633
+ 0,
634
+ this.config.animation.duration ?? 750,
635
+ this.config.animation.easing ?? "easeOutQuad",
636
+ (off) => {
637
+ path.style.strokeDashoffset = String(off);
638
+ }
639
+ );
640
+ }
641
+ });
642
+ }
643
+ }
644
+ class ScatterRenderer extends BaseRenderer {
645
+ render() {
646
+ const d = this.dims();
647
+ const group = this.g("chartforge-scatter");
648
+ this.group.appendChild(group);
649
+ this.data.series.forEach((series, si) => {
650
+ const pts = series.data;
651
+ const maxX = Math.max(...pts.map((p) => p.x));
652
+ const maxY = Math.max(...pts.map((p) => p.y));
653
+ pts.forEach((pt, i) => {
654
+ const cx = this.padding.left + pt.x / maxX * d.width;
655
+ const cy = this.padding.top + d.height - pt.y / maxY * d.height;
656
+ const r0 = pt.r ?? 5;
657
+ const circle = createSVGElement("circle", {
658
+ cx,
659
+ cy,
660
+ r: this.config.animation?.enabled ? 0 : r0,
661
+ fill: this.color(si),
662
+ opacity: "0.7"
663
+ });
664
+ group.appendChild(circle);
665
+ if (this.config.animation?.enabled) {
666
+ this.chart.animationEngine.animate(
667
+ `scatter-${si}-${i}`,
668
+ 0,
669
+ r0,
670
+ this.config.animation.duration ?? 750,
671
+ "easeOutElastic",
672
+ (r) => circle.setAttribute("r", String(r))
673
+ );
674
+ }
675
+ circle.addEventListener("mouseenter", () => {
676
+ circle.setAttribute("r", String(r0 * 1.5));
677
+ circle.setAttribute("opacity", "1");
678
+ this.chart.emit("hover", { type: "scatter", seriesIndex: si, index: i, point: pt });
679
+ });
680
+ circle.addEventListener("mouseleave", () => {
681
+ circle.setAttribute("r", String(r0));
682
+ circle.setAttribute("opacity", "0.7");
683
+ });
684
+ circle.addEventListener("click", () => this.chart.emit("click", { type: "scatter", seriesIndex: si, index: i, point: pt }));
685
+ });
686
+ });
687
+ }
688
+ }
689
+ class StackedColumnRenderer extends BaseRenderer {
690
+ render() {
691
+ const d = this.dims();
692
+ const labels = this.data.labels ?? [];
693
+ const nCats = labels.length;
694
+ const cw = d.width / nCats * 0.8;
695
+ const gap = d.width / nCats * 0.2;
696
+ const totals = Array.from(
697
+ { length: nCats },
698
+ (_, i) => this.data.series.reduce((s, ser) => s + (ser.data[i] ?? 0), 0)
699
+ );
700
+ const maxTotal = Math.max(...totals);
701
+ const group = this.g("chartforge-stacked-columns");
702
+ this.group.appendChild(group);
703
+ for (let ci = 0; ci < nCats; ci++) {
704
+ let yOff = 0;
705
+ const x = this.padding.left + ci * (cw + gap);
706
+ const baseY = this.padding.top + d.height;
707
+ this.data.series.forEach((series, si) => {
708
+ const value = series.data[ci] ?? 0;
709
+ const targetH = value / maxTotal * d.height;
710
+ const rect = createSVGElement("rect", {
711
+ x,
712
+ y: baseY - yOff,
713
+ width: cw,
714
+ height: 0,
715
+ fill: this.color(si)
716
+ });
717
+ group.appendChild(rect);
718
+ const capturedYOff = yOff;
719
+ if (this.config.animation?.enabled) {
720
+ this.chart.animationEngine.animate(
721
+ `scol-${ci}-${si}`,
722
+ 0,
723
+ targetH,
724
+ this.config.animation.duration ?? 750,
725
+ this.config.animation.easing ?? "easeOutQuad",
726
+ (h) => {
727
+ rect.setAttribute("height", String(h));
728
+ rect.setAttribute("y", String(baseY - capturedYOff - h));
729
+ }
730
+ );
731
+ } else {
732
+ rect.setAttribute("height", String(targetH));
733
+ rect.setAttribute("y", String(baseY - capturedYOff - targetH));
734
+ }
735
+ rect.addEventListener("mouseenter", () => {
736
+ rect.setAttribute("opacity", "0.8");
737
+ this.chart.emit("hover", { type: "stackedColumn", catIndex: ci, seriesIndex: si, value });
738
+ });
739
+ rect.addEventListener("mouseleave", () => rect.setAttribute("opacity", "1"));
740
+ yOff += targetH;
741
+ });
742
+ }
743
+ }
744
+ }
745
+ class StackedBarRenderer extends BaseRenderer {
746
+ render() {
747
+ const d = this.dims();
748
+ const nCats = (this.data.labels ?? []).length;
749
+ const bh = d.height / nCats * 0.8;
750
+ const gap = d.height / nCats * 0.2;
751
+ const totals = Array.from(
752
+ { length: nCats },
753
+ (_, i) => this.data.series.reduce((s, ser) => s + (ser.data[i] ?? 0), 0)
754
+ );
755
+ const maxTotal = Math.max(...totals);
756
+ const group = this.g("chartforge-stacked-bars");
757
+ this.group.appendChild(group);
758
+ for (let ci = 0; ci < nCats; ci++) {
759
+ let xOff = 0;
760
+ const y = this.padding.top + ci * (bh + gap);
761
+ this.data.series.forEach((series, si) => {
762
+ const value = series.data[ci] ?? 0;
763
+ const targetW = value / maxTotal * d.width;
764
+ const rect = createSVGElement("rect", {
765
+ x: this.padding.left + xOff,
766
+ y,
767
+ width: 0,
768
+ height: bh,
769
+ fill: this.color(si)
770
+ });
771
+ group.appendChild(rect);
772
+ const capturedXOff = xOff;
773
+ if (this.config.animation?.enabled) {
774
+ this.chart.animationEngine.animate(
775
+ `sbar-${ci}-${si}`,
776
+ 0,
777
+ targetW,
778
+ this.config.animation.duration ?? 750,
779
+ this.config.animation.easing ?? "easeOutQuad",
780
+ (w) => {
781
+ rect.setAttribute("width", String(w));
782
+ rect.setAttribute("x", String(this.padding.left + capturedXOff));
783
+ }
784
+ );
785
+ } else {
786
+ rect.setAttribute("width", String(targetW));
787
+ }
788
+ rect.addEventListener("mouseenter", () => {
789
+ rect.setAttribute("opacity", "0.8");
790
+ this.chart.emit("hover", { type: "stackedBar", catIndex: ci, seriesIndex: si, value });
791
+ });
792
+ rect.addEventListener("mouseleave", () => rect.setAttribute("opacity", "1"));
793
+ xOff += targetW;
794
+ });
795
+ }
796
+ }
797
+ }
798
+ class FunnelRenderer extends BaseRenderer {
799
+ render() {
800
+ const d = this.dims();
801
+ const values = this.data.series[0].data;
802
+ const maxVal = Math.max(...values);
803
+ const segH = d.height / values.length;
804
+ const group = this.g("chartforge-funnel");
805
+ this.group.appendChild(group);
806
+ values.forEach((value, i) => {
807
+ const nextVal = values[i + 1] ?? 0;
808
+ const topW = value / maxVal * d.width;
809
+ const botW = nextVal / maxVal * d.width;
810
+ const y = this.padding.top + i * segH;
811
+ const topL = this.padding.left + (d.width - topW) / 2;
812
+ const botL = this.padding.left + (d.width - botW) / 2;
813
+ const poly = createSVGElement("polygon", {
814
+ points: `${topL},${y} ${topL + topW},${y} ${botL + botW},${y + segH} ${botL},${y + segH}`,
815
+ fill: this.color(i),
816
+ stroke: this.theme.background,
817
+ "stroke-width": "2"
818
+ });
819
+ group.appendChild(poly);
820
+ const label = createSVGElement("text", {
821
+ x: d.totalWidth / 2,
822
+ y: y + segH / 2,
823
+ fill: "#fff",
824
+ "font-size": "14",
825
+ "text-anchor": "middle",
826
+ "dominant-baseline": "middle",
827
+ "font-weight": "bold"
828
+ });
829
+ label.textContent = `${this.data.labels?.[i] ?? `Stage ${i + 1}`}: ${value}`;
830
+ group.appendChild(label);
831
+ poly.addEventListener("mouseenter", () => {
832
+ poly.setAttribute("opacity", "0.8");
833
+ this.chart.emit("hover", { type: "funnel", index: i, value });
834
+ });
835
+ poly.addEventListener("mouseleave", () => poly.setAttribute("opacity", "1"));
836
+ });
837
+ }
838
+ }
839
+ class HeatmapRenderer extends BaseRenderer {
840
+ render() {
841
+ const d = this.dims();
842
+ const grid = this.data.series[0].data;
843
+ const rows = grid.length;
844
+ const cols = grid[0]?.length ?? 0;
845
+ const cw = d.width / cols;
846
+ const ch = d.height / rows;
847
+ const flat = grid.flat();
848
+ const min = Math.min(...flat);
849
+ const max = Math.max(...flat);
850
+ const rng = max - min || 1;
851
+ const group = this.g("chartforge-heatmap");
852
+ this.group.appendChild(group);
853
+ grid.forEach((row, ri) => {
854
+ row.forEach((value, ci) => {
855
+ const intensity = (value - min) / rng;
856
+ const r = Math.round(intensity * 255);
857
+ const b2 = Math.round((1 - intensity) * 255);
858
+ const fill = `rgb(${r},100,${b2})`;
859
+ const rect = createSVGElement("rect", {
860
+ x: this.padding.left + ci * cw,
861
+ y: this.padding.top + ri * ch,
862
+ width: cw,
863
+ height: ch,
864
+ fill,
865
+ stroke: this.theme.background,
866
+ "stroke-width": "1"
867
+ });
868
+ group.appendChild(rect);
869
+ rect.addEventListener("mouseenter", () => {
870
+ rect.setAttribute("stroke-width", "2");
871
+ this.chart.emit("hover", { type: "heatmap", row: ri, col: ci, value });
872
+ });
873
+ rect.addEventListener("mouseleave", () => rect.setAttribute("stroke-width", "1"));
874
+ });
875
+ });
876
+ }
877
+ }
878
+ class CandlestickRenderer extends BaseRenderer {
879
+ render() {
880
+ const d = this.dims();
881
+ const candles = this.data.series[0].data;
882
+ const allVals = candles.flatMap((c) => [c.open, c.high, c.low, c.close]);
883
+ const minVal = Math.min(...allVals);
884
+ const maxVal = Math.max(...allVals);
885
+ const range = maxVal - minVal || 1;
886
+ const cw = d.width / candles.length * 0.7;
887
+ const gap = d.width / candles.length * 0.3;
888
+ const group = this.g("chartforge-candlestick");
889
+ this.group.appendChild(group);
890
+ const toY = (v) => this.padding.top + d.height - (v - minVal) / range * d.height;
891
+ candles.forEach((candle, i) => {
892
+ const cx = this.padding.left + i * (cw + gap) + cw / 2;
893
+ const openY = toY(candle.open);
894
+ const closeY = toY(candle.close);
895
+ const highY = toY(candle.high);
896
+ const lowY = toY(candle.low);
897
+ const positive = candle.close >= candle.open;
898
+ const color = positive ? "#10b981" : "#ef4444";
899
+ const wick = createSVGElement("line", {
900
+ x1: cx,
901
+ y1: highY,
902
+ x2: cx,
903
+ y2: lowY,
904
+ stroke: color,
905
+ "stroke-width": "1"
906
+ });
907
+ group.appendChild(wick);
908
+ const bodyY = Math.min(openY, closeY);
909
+ const bodyH = Math.max(Math.abs(closeY - openY), 1);
910
+ const body = createSVGElement("rect", {
911
+ x: cx - cw / 2,
912
+ y: bodyY,
913
+ width: cw,
914
+ height: bodyH,
915
+ fill: positive ? color : "#ffffff",
916
+ stroke: color,
917
+ "stroke-width": "2"
918
+ });
919
+ group.appendChild(body);
920
+ body.addEventListener("mouseenter", () => {
921
+ body.setAttribute("opacity", "0.8");
922
+ this.chart.emit("hover", { type: "candlestick", index: i, candle });
923
+ });
924
+ body.addEventListener("mouseleave", () => body.setAttribute("opacity", "1"));
925
+ body.addEventListener("click", () => this.chart.emit("click", { type: "candlestick", index: i, candle }));
926
+ });
927
+ }
928
+ }
929
+ const RENDERERS = {
930
+ pie: PieRenderer,
931
+ donut: DonutRenderer,
932
+ column: ColumnRenderer,
933
+ bar: BarRenderer,
934
+ row: BarRenderer,
935
+ line: LineRenderer,
936
+ scatter: ScatterRenderer,
937
+ stackedColumn: StackedColumnRenderer,
938
+ stackedBar: StackedBarRenderer,
939
+ funnel: FunnelRenderer,
940
+ heatmap: HeatmapRenderer,
941
+ candlestick: CandlestickRenderer
942
+ };
943
+ const DEFAULT_CONFIG = {
944
+ width: "auto",
945
+ height: 400,
946
+ responsive: true,
947
+ theme: "light",
948
+ animation: { enabled: true, duration: 750, easing: "easeOutQuad" },
949
+ plugins: {},
950
+ middleware: [],
951
+ virtual: { enabled: false, threshold: 1e4 },
952
+ padding: { top: 40, right: 40, bottom: 60, left: 60 }
953
+ };
954
+ const _ChartForge = class _ChartForge {
955
+ constructor(container, config) {
956
+ this.initialized = false;
957
+ this._resizeObserver = null;
958
+ this._rendering = false;
959
+ this.id = uid();
960
+ this.container = typeof container === "string" ? document.querySelector(container) : container;
961
+ if (!this.container) throw new Error("[ChartForge] Container not found");
962
+ this.config = merge({ ...DEFAULT_CONFIG }, config);
963
+ this.eventBus = new EventBus();
964
+ this.middleware = new MiddlewarePipeline();
965
+ this.dataPipeline = new DataPipeline();
966
+ this.animationEngine = new AnimationEngine();
967
+ this.themeManager = new ThemeManager();
968
+ this.pluginManager = new PluginManager(this);
969
+ this.virtualRenderer = new VirtualRenderer(this);
970
+ this.realTime = new RealTimeModule(this);
971
+ for (const [name, t2] of Object.entries(BUILT_IN_THEMES)) {
972
+ this.themeManager.register(name, t2);
973
+ }
974
+ this.realTime.registerAdapter("websocket", WebSocketAdapter);
975
+ this.realTime.registerAdapter("polling", PollingAdapter);
976
+ this._init();
977
+ }
978
+ _init() {
979
+ const theme = this.themeManager.apply(this.config.theme ?? "light");
980
+ this.theme = theme ?? BUILT_IN_THEMES["light"];
981
+ this._createSVG();
982
+ this._setupMiddleware();
983
+ if (this.config.responsive) {
984
+ this._resizeObserver = new ResizeObserver(debounce(() => this.resize(), 150));
985
+ this._resizeObserver.observe(this.container);
986
+ }
987
+ this.initialized = true;
988
+ this.pluginManager.initAll();
989
+ void this.render();
990
+ }
991
+ _createSVG() {
992
+ const w = this.config.width === "auto" ? this.container.offsetWidth || 600 : this.config.width ?? 600;
993
+ const h = this.config.height ?? 400;
994
+ this.svg = createSVGElement("svg", {
995
+ width: "100%",
996
+ height: "100%",
997
+ viewBox: `0 0 ${w} ${h}`,
998
+ className: "chartforge-svg",
999
+ role: "img"
1000
+ });
1001
+ this.svg.appendChild(createSVGElement("defs"));
1002
+ this.mainGroup = createSVGElement("g", { className: "chartforge-main" });
1003
+ this.svg.appendChild(this.mainGroup);
1004
+ this.svg.style.cssText = `
1005
+ display:block;
1006
+ font-family:'system-ui', sans-serif;
1007
+ background:${this.theme.background};
1008
+ border-radius:inherit;
1009
+ `;
1010
+ this.container.appendChild(this.svg);
1011
+ }
1012
+ _setupMiddleware() {
1013
+ this.middleware.use(async (ctx, next) => {
1014
+ this.eventBus.emit("beforeRender", ctx);
1015
+ await next();
1016
+ });
1017
+ for (const fn of this.config.middleware ?? []) {
1018
+ this.middleware.use(fn);
1019
+ }
1020
+ }
1021
+ async render() {
1022
+ if (this._rendering) return;
1023
+ this._rendering = true;
1024
+ try {
1025
+ const ctx = {
1026
+ data: this.config.data,
1027
+ theme: this.theme,
1028
+ svg: this.svg,
1029
+ mainGroup: this.mainGroup
1030
+ };
1031
+ await this.middleware.execute(ctx);
1032
+ const data = await this.dataPipeline.transform(this.config.data, this.config);
1033
+ const useVirtual = this.config.virtual?.enabled && this.virtualRenderer.shouldVirtualize();
1034
+ this._renderData(useVirtual ? this.virtualRenderer.getVisibleData() : data);
1035
+ this.eventBus.emit("afterRender", ctx);
1036
+ } finally {
1037
+ this._rendering = false;
1038
+ }
1039
+ }
1040
+ _renderData(data) {
1041
+ removeChildren(this.mainGroup);
1042
+ const RendererClass = RENDERERS[this.config.type];
1043
+ if (!RendererClass) {
1044
+ console.error(`[ChartForge] Unknown chart type: "${this.config.type}"`);
1045
+ return;
1046
+ }
1047
+ new RendererClass(this, data).render();
1048
+ }
1049
+ updateData(data) {
1050
+ this.config.data = merge({ ...this.config.data }, data);
1051
+ void this.render();
1052
+ }
1053
+ updateConfig(config) {
1054
+ this.config = merge({ ...this.config }, config);
1055
+ void this.render();
1056
+ }
1057
+ setTheme(name) {
1058
+ const t2 = this.themeManager.apply(name);
1059
+ if (!t2) return;
1060
+ this.theme = t2;
1061
+ this.svg.style.background = t2.background;
1062
+ void this.render();
1063
+ }
1064
+ use(name, Plugin, config) {
1065
+ this.pluginManager.register(name, Plugin, config);
1066
+ return this;
1067
+ }
1068
+ getPlugin(name) {
1069
+ return this.pluginManager.get(name);
1070
+ }
1071
+ setViewport(start, end) {
1072
+ this.virtualRenderer.updateViewport(start, end);
1073
+ void this.render();
1074
+ }
1075
+ resize() {
1076
+ const w = this.container.offsetWidth || 600;
1077
+ const h = this.config.height ?? 400;
1078
+ this.svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
1079
+ void this.render();
1080
+ }
1081
+ on(event, handler, priority) {
1082
+ return this.eventBus.on(event, handler, priority);
1083
+ }
1084
+ off(event, handler) {
1085
+ this.eventBus.off(event, handler);
1086
+ }
1087
+ emit(event, data) {
1088
+ this.eventBus.emit(event, data);
1089
+ }
1090
+ destroy() {
1091
+ this.animationEngine.stopAll();
1092
+ this.realTime.disconnectAll();
1093
+ this.pluginManager.destroyAll();
1094
+ this._resizeObserver?.disconnect();
1095
+ this.eventBus.clear();
1096
+ this.svg?.parentNode?.removeChild(this.svg);
1097
+ }
1098
+ static create(container, config) {
1099
+ return new _ChartForge(container, config);
1100
+ }
1101
+ static registerTheme(name, theme) {
1102
+ _ChartForge._globalThemes.set(name, theme);
1103
+ }
1104
+ };
1105
+ _ChartForge._globalThemes = /* @__PURE__ */ new Map();
1106
+ let ChartForge = _ChartForge;
1107
+ export {
1108
+ AnimationEngine,
1109
+ BUILT_IN_THEMES,
1110
+ BarRenderer,
1111
+ BaseRenderer,
1112
+ CandlestickRenderer,
1113
+ ChartForge,
1114
+ ColumnRenderer,
1115
+ DataPipeline,
1116
+ DonutRenderer,
1117
+ EventBus,
1118
+ FunnelRenderer,
1119
+ HeatmapRenderer,
1120
+ LineRenderer,
1121
+ MiddlewarePipeline,
1122
+ PieRenderer,
1123
+ PluginManager,
1124
+ PollingAdapter,
1125
+ RENDERERS,
1126
+ RealTimeModule,
1127
+ ScatterRenderer,
1128
+ StackedBarRenderer,
1129
+ StackedColumnRenderer,
1130
+ ThemeManager,
1131
+ VirtualRenderer,
1132
+ WebSocketAdapter,
1133
+ a as clamp,
1134
+ createSVGElement,
1135
+ darkTheme,
1136
+ debounce,
1137
+ b as describeArc,
1138
+ f as flatMax,
1139
+ e as flatMin,
1140
+ lightTheme,
1141
+ merge,
1142
+ neonTheme,
1143
+ g as plugins,
1144
+ polarToCartesian,
1145
+ removeChildren,
1146
+ t as throttle,
1147
+ uid
1148
+ };