chartai 0.0.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.
@@ -0,0 +1,544 @@
1
+ // src/chart-library.ts
2
+ var globalPlugins = [];
3
+ function registerPlugin(plugin) {
4
+ if (!globalPlugins.some((p) => p.name === plugin.name)) {
5
+ globalPlugins.push(plugin);
6
+ }
7
+ }
8
+ function unregisterPlugin(name) {
9
+ const idx = globalPlugins.findIndex((p) => p.name === name);
10
+ if (idx >= 0)
11
+ globalPlugins.splice(idx, 1);
12
+ }
13
+ function getPlugins() {
14
+ return globalPlugins;
15
+ }
16
+
17
+ class ChartManager {
18
+ static instance = null;
19
+ static MARGIN = { left: 55, right: 10, top: 8, bottom: 45 };
20
+ static HOVER_MAX_DISTANCE_PX = 50;
21
+ static MIN_ZOOM = 0.1;
22
+ static MAX_ZOOM = 1e7;
23
+ static DEFAULT_LABEL_SIZE = 12;
24
+ static DEFAULT_FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif';
25
+ static resizeCanvas(canvas, cssWidth, cssHeight) {
26
+ const dpr = devicePixelRatio || 1;
27
+ canvas.width = Math.round(cssWidth * dpr);
28
+ canvas.height = Math.round(cssHeight * dpr);
29
+ if (canvas instanceof HTMLCanvasElement) {
30
+ canvas.style.width = `${cssWidth}px`;
31
+ canvas.style.height = `${cssHeight}px`;
32
+ }
33
+ }
34
+ worker = null;
35
+ charts = new Map;
36
+ chartIdCounter = 0;
37
+ _syncViews = false;
38
+ _isDark = false;
39
+ statsCallbacks = [];
40
+ currentStats = {
41
+ fps: 0,
42
+ renderMs: 0,
43
+ total: 0,
44
+ active: 0
45
+ };
46
+ visibilityObserver;
47
+ resizeObserver;
48
+ constructor() {
49
+ this._isDark = document.documentElement.classList.contains("dark");
50
+ this.visibilityObserver = new IntersectionObserver((entries) => {
51
+ for (const entry of entries) {
52
+ const id = entry.target.dataset.chartId;
53
+ if (id) {
54
+ const chart = this.charts.get(id);
55
+ if (chart) {
56
+ chart.visible = entry.isIntersecting;
57
+ this.worker?.postMessage({
58
+ type: "set-visibility",
59
+ id,
60
+ visible: entry.isIntersecting
61
+ });
62
+ }
63
+ }
64
+ }
65
+ }, { threshold: 0.01 });
66
+ this.resizeObserver = new ResizeObserver((entries) => {
67
+ for (const entry of entries) {
68
+ const id = entry.target.dataset.chartId;
69
+ if (!id)
70
+ continue;
71
+ const chart = this.charts.get(id);
72
+ if (!chart)
73
+ continue;
74
+ const { width, height } = entry.contentRect;
75
+ if (width <= 0 || height <= 0)
76
+ continue;
77
+ chart.width = width;
78
+ chart.height = height;
79
+ const dpr = devicePixelRatio || 1;
80
+ this.worker?.postMessage({
81
+ type: "resize",
82
+ id,
83
+ width: Math.round(width * dpr),
84
+ height: Math.round(height * dpr)
85
+ });
86
+ ChartManager.resizeCanvas(chart.backCanvas, width, height);
87
+ ChartManager.resizeCanvas(chart.axisCanvas, width, height);
88
+ this.drawChart(chart);
89
+ }
90
+ });
91
+ }
92
+ static getInstance() {
93
+ if (!ChartManager.instance) {
94
+ ChartManager.instance = new ChartManager;
95
+ }
96
+ return ChartManager.instance;
97
+ }
98
+ get isDark() {
99
+ return this._isDark;
100
+ }
101
+ get syncViews() {
102
+ return this._syncViews;
103
+ }
104
+ async init() {
105
+ if (this.worker)
106
+ return true;
107
+ return new Promise((resolve) => {
108
+ const workerUrl = new URL("./gpu-worker.js", import.meta.url);
109
+ this.worker = new Worker(workerUrl, { type: "module" });
110
+ this.worker.onmessage = (e) => {
111
+ const { type, ...data } = e.data;
112
+ switch (type) {
113
+ case "gpu-ready":
114
+ resolve(true);
115
+ break;
116
+ case "error":
117
+ console.error("ChartManager GPU Error:", data.message);
118
+ resolve(false);
119
+ break;
120
+ case "stats":
121
+ this.currentStats = {
122
+ fps: data.fps,
123
+ renderMs: data.renderMs,
124
+ total: data.totalCharts,
125
+ active: data.activeCharts
126
+ };
127
+ for (const cb of this.statsCallbacks)
128
+ cb(this.currentStats);
129
+ break;
130
+ case "bounds-update": {
131
+ const chart = this.charts.get(data.id);
132
+ if (chart) {
133
+ chart.bounds = {
134
+ minX: data.minX,
135
+ maxX: data.maxX,
136
+ minY: data.minY,
137
+ maxY: data.maxY
138
+ };
139
+ this.drawChart(chart);
140
+ }
141
+ break;
142
+ }
143
+ }
144
+ };
145
+ this.worker.postMessage({ type: "init", isDark: this._isDark });
146
+ });
147
+ }
148
+ create(config) {
149
+ if (!this.worker) {
150
+ throw new Error("ChartManager not initialized. Call init() first.");
151
+ }
152
+ const id = `chart-${++this.chartIdCounter}`;
153
+ const el = document.createElement("div");
154
+ el.className = "gpu-chart";
155
+ el.dataset.chartId = id;
156
+ const canvasWrap = document.createElement("div");
157
+ canvasWrap.className = "gpu-chart-canvas-wrap";
158
+ canvasWrap.dataset.chartId = id;
159
+ const backCanvas = document.createElement("canvas");
160
+ backCanvas.className = "gpu-chart-back-canvas";
161
+ const gpuCanvas = document.createElement("canvas");
162
+ gpuCanvas.className = "gpu-chart-canvas";
163
+ const axisCanvas = document.createElement("canvas");
164
+ axisCanvas.className = "gpu-chart-axis-canvas";
165
+ canvasWrap.appendChild(backCanvas);
166
+ canvasWrap.appendChild(gpuCanvas);
167
+ canvasWrap.appendChild(axisCanvas);
168
+ el.appendChild(canvasWrap);
169
+ config.container.appendChild(el);
170
+ let offscreen;
171
+ try {
172
+ offscreen = gpuCanvas.transferControlToOffscreen();
173
+ } catch (e) {
174
+ console.error(`ChartManager: Failed to create offscreen canvas for ${id}:`, e);
175
+ throw new Error(`Failed to create chart ${id}: ${e}`);
176
+ }
177
+ const rect = canvasWrap.getBoundingClientRect();
178
+ const cssW = rect.width || 400;
179
+ const cssH = rect.height || 200;
180
+ ChartManager.resizeCanvas(offscreen, cssW, cssH);
181
+ ChartManager.resizeCanvas(backCanvas, cssW, cssH);
182
+ ChartManager.resizeCanvas(axisCanvas, cssW, cssH);
183
+ if (config.bgColor) {
184
+ const [r, g, b] = config.bgColor;
185
+ canvasWrap.style.background = `rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)})`;
186
+ }
187
+ const chart = {
188
+ id,
189
+ config,
190
+ el,
191
+ backCanvas,
192
+ axisCanvas,
193
+ width: cssW,
194
+ height: cssH,
195
+ series: [],
196
+ bounds: { minX: 0, maxX: 1, minY: 0, maxY: 1 },
197
+ view: { panX: 0, panY: 0, zoomX: 1, zoomY: 1 },
198
+ zoomMode: config.zoomMode || "both",
199
+ visible: true,
200
+ dragging: false,
201
+ bgColor: config.bgColor,
202
+ textColor: config.textColor,
203
+ gridColor: config.gridColor,
204
+ fontFamily: config.fontFamily,
205
+ momentum: null,
206
+ findNearestPoint(screenX, screenY, width, height) {
207
+ if (this.series.length === 0)
208
+ return null;
209
+ const normX = screenX / width;
210
+ const normY = screenY / height;
211
+ const rangeX = this.bounds.maxX - this.bounds.minX;
212
+ const rangeY = this.bounds.maxY - this.bounds.minY;
213
+ const viewWidth = rangeX / this.view.zoomX;
214
+ const viewHeight = rangeY / this.view.zoomY;
215
+ const viewMinX = this.bounds.minX + this.view.panX * rangeX;
216
+ const viewMinY = this.bounds.minY + this.view.panY * rangeY;
217
+ const dataX = viewMinX + normX * viewWidth;
218
+ const dataY = viewMinY + (1 - normY) * viewHeight;
219
+ let bestSeriesIdx = -1;
220
+ let bestIdx = -1;
221
+ let bestDistX = Infinity;
222
+ let bestDistY = Infinity;
223
+ for (let s = 0;s < this.series.length; s++) {
224
+ const series2 = this.series[s];
225
+ const n = series2.rawX.length;
226
+ if (n === 0)
227
+ continue;
228
+ let lo = 0, hi = n - 1;
229
+ while (lo < hi) {
230
+ const mid = lo + hi >> 1;
231
+ if (series2.rawX[mid] < dataX)
232
+ lo = mid + 1;
233
+ else
234
+ hi = mid;
235
+ }
236
+ let idx = lo;
237
+ if (lo > 0) {
238
+ const distLo = Math.abs(series2.rawX[lo] - dataX);
239
+ const distPrev = Math.abs(series2.rawX[lo - 1] - dataX);
240
+ if (distPrev < distLo)
241
+ idx = lo - 1;
242
+ }
243
+ const distX = Math.abs(series2.rawX[idx] - dataX);
244
+ const distY = Math.abs(series2.rawY[idx] - dataY);
245
+ if (distX < bestDistX || distX === bestDistX && distY < bestDistY) {
246
+ bestDistX = distX;
247
+ bestDistY = distY;
248
+ bestSeriesIdx = s;
249
+ bestIdx = idx;
250
+ }
251
+ }
252
+ if (bestSeriesIdx === -1)
253
+ return null;
254
+ const series = this.series[bestSeriesIdx];
255
+ const pointNormX = (series.rawX[bestIdx] - viewMinX) / viewWidth;
256
+ const pointScreenX = pointNormX * width;
257
+ const pixelDistX = Math.abs(pointScreenX - screenX);
258
+ if (pixelDistX > ChartManager.HOVER_MAX_DISTANCE_PX)
259
+ return null;
260
+ return {
261
+ x: series.rawX[bestIdx],
262
+ y: series.rawY[bestIdx],
263
+ index: bestIdx,
264
+ screenX,
265
+ screenY,
266
+ seriesIndex: bestSeriesIdx,
267
+ seriesLabel: series.label
268
+ };
269
+ }
270
+ };
271
+ this.charts.set(id, chart);
272
+ const workerType = config.type === "bar" ? "box" : config.type;
273
+ this.worker.postMessage({
274
+ type: "register-chart",
275
+ id,
276
+ canvas: offscreen,
277
+ chartType: workerType,
278
+ pointSize: config.pointSize ?? 3,
279
+ maxSamplesPerPixel: config.maxSamplesPerPixel ?? 1e4,
280
+ bgColor: config.bgColor ?? null
281
+ }, [offscreen]);
282
+ this.visibilityObserver.observe(el);
283
+ this.resizeObserver.observe(canvasWrap);
284
+ for (const plugin of globalPlugins) {
285
+ plugin.install?.(chart, canvasWrap);
286
+ }
287
+ this.updateSeries(id, config.series);
288
+ return id;
289
+ }
290
+ destroy(id) {
291
+ const chart = this.charts.get(id);
292
+ if (!chart)
293
+ return;
294
+ for (const plugin of globalPlugins) {
295
+ plugin.uninstall?.(chart);
296
+ }
297
+ if (chart.momentum)
298
+ cancelAnimationFrame(chart.momentum);
299
+ this.visibilityObserver.unobserve(chart.el);
300
+ const wrap = chart.el.querySelector(".gpu-chart-canvas-wrap");
301
+ if (wrap)
302
+ this.resizeObserver.unobserve(wrap);
303
+ chart.el.remove();
304
+ this.worker?.postMessage({ type: "unregister-chart", id });
305
+ this.charts.delete(id);
306
+ }
307
+ updateSeries(id, series) {
308
+ const chart = this.charts.get(id);
309
+ if (!chart || !this.worker)
310
+ return;
311
+ if (series.length === 0)
312
+ return;
313
+ chart.series = series.map((s) => {
314
+ const n = s.x.length;
315
+ if (n === 0)
316
+ return { label: s.label, color: s.color, rawX: [], rawY: [] };
317
+ const indices = Array.from({ length: n }, (_, i) => i);
318
+ indices.sort((a, b) => s.x[a] - s.x[b]);
319
+ return {
320
+ label: s.label,
321
+ color: s.color,
322
+ rawX: indices.map((i) => s.x[i]),
323
+ rawY: indices.map((i) => s.y[i])
324
+ };
325
+ });
326
+ let minX = Infinity, maxX = -Infinity;
327
+ let minY = Infinity, maxY = -Infinity;
328
+ for (const s of chart.series) {
329
+ for (let i = 0;i < s.rawX.length; i++) {
330
+ if (s.rawX[i] < minX)
331
+ minX = s.rawX[i];
332
+ if (s.rawX[i] > maxX)
333
+ maxX = s.rawX[i];
334
+ if (s.rawY[i] < minY)
335
+ minY = s.rawY[i];
336
+ if (s.rawY[i] > maxY)
337
+ maxY = s.rawY[i];
338
+ }
339
+ }
340
+ const px = (maxX - minX) * 0.05 || 1;
341
+ const py = (maxY - minY) * 0.1 || 1;
342
+ minX -= px;
343
+ maxX += px;
344
+ minY -= py;
345
+ maxY += py;
346
+ const db = chart.config.defaultBounds;
347
+ if (db) {
348
+ if (db.minX !== undefined)
349
+ minX = db.minX;
350
+ if (db.maxX !== undefined)
351
+ maxX = db.maxX;
352
+ if (db.minY !== undefined)
353
+ minY = db.minY;
354
+ if (db.maxY !== undefined)
355
+ maxY = db.maxY;
356
+ }
357
+ chart.bounds = { minX, maxX, minY, maxY };
358
+ const seriesData = chart.series.map((s) => ({
359
+ label: s.label,
360
+ colorR: s.color.r,
361
+ colorG: s.color.g,
362
+ colorB: s.color.b,
363
+ dataX: new Float32Array(s.rawX),
364
+ dataY: new Float32Array(s.rawY)
365
+ }));
366
+ this.worker.postMessage({
367
+ type: "update-series",
368
+ id,
369
+ series: seriesData,
370
+ bounds: chart.bounds
371
+ }, seriesData.flatMap((s) => [s.dataX.buffer, s.dataY.buffer]));
372
+ this.sendViewTransform(chart);
373
+ this.drawChart(chart);
374
+ }
375
+ setPointSize(id, size) {
376
+ const chart = this.charts.get(id);
377
+ if (!chart)
378
+ return;
379
+ this.worker?.postMessage({
380
+ type: "set-point-size",
381
+ id,
382
+ pointSize: Math.max(1, Math.min(8, Math.round(size)))
383
+ });
384
+ }
385
+ setMaxSamplesPerPixel(id, maxSamples) {
386
+ const chart = this.charts.get(id);
387
+ if (!chart)
388
+ return;
389
+ this.worker?.postMessage({
390
+ type: "set-max-samples",
391
+ id,
392
+ maxSamplesPerPixel: Math.max(0, maxSamples | 0)
393
+ });
394
+ }
395
+ setStyle(id, style) {
396
+ const chart = this.charts.get(id);
397
+ if (!chart)
398
+ return;
399
+ if (style.bgColor !== undefined)
400
+ chart.bgColor = style.bgColor;
401
+ if (style.textColor !== undefined)
402
+ chart.textColor = style.textColor;
403
+ if (style.gridColor !== undefined)
404
+ chart.gridColor = style.gridColor;
405
+ if (style.fontFamily !== undefined)
406
+ chart.fontFamily = style.fontFamily;
407
+ if (style.bgColor !== undefined) {
408
+ const wrap = chart.el.querySelector(".gpu-chart-canvas-wrap");
409
+ if (wrap) {
410
+ const [r, g, b] = style.bgColor;
411
+ wrap.style.background = `rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)})`;
412
+ }
413
+ this.worker?.postMessage({
414
+ type: "set-style",
415
+ id,
416
+ bgColor: style.bgColor
417
+ });
418
+ }
419
+ this.drawChart(chart);
420
+ }
421
+ setZoomMode(id, mode) {
422
+ const chart = this.charts.get(id);
423
+ if (chart)
424
+ chart.zoomMode = mode;
425
+ }
426
+ getChartCount() {
427
+ return this.charts.size;
428
+ }
429
+ getZoomMode(id) {
430
+ return this.charts.get(id)?.zoomMode || "both";
431
+ }
432
+ setSyncViews(sync) {
433
+ this._syncViews = sync;
434
+ }
435
+ setTheme(dark) {
436
+ this._isDark = dark;
437
+ this.worker?.postMessage({ type: "theme", isDark: dark });
438
+ for (const chart of this.charts.values()) {
439
+ this.drawChart(chart);
440
+ }
441
+ }
442
+ onStats(callback) {
443
+ this.statsCallbacks.push(callback);
444
+ return () => {
445
+ const idx = this.statsCallbacks.indexOf(callback);
446
+ if (idx >= 0)
447
+ this.statsCallbacks.splice(idx, 1);
448
+ };
449
+ }
450
+ getStats() {
451
+ return { ...this.currentStats };
452
+ }
453
+ resetView(id) {
454
+ const chart = this.charts.get(id);
455
+ if (!chart)
456
+ return;
457
+ if (chart.momentum) {
458
+ cancelAnimationFrame(chart.momentum);
459
+ chart.momentum = null;
460
+ }
461
+ const startPanX = chart.view.panX;
462
+ const startPanY = chart.view.panY;
463
+ const startZoomX = chart.view.zoomX;
464
+ const startZoomY = chart.view.zoomY;
465
+ const startTime = performance.now();
466
+ const animate = () => {
467
+ const t = Math.min(1, (performance.now() - startTime) / 300);
468
+ const ease = 1 - Math.pow(1 - t, 3);
469
+ chart.view.panX = startPanX * (1 - ease);
470
+ chart.view.panY = startPanY * (1 - ease);
471
+ chart.view.zoomX = startZoomX + (1 - startZoomX) * ease;
472
+ chart.view.zoomY = startZoomY + (1 - startZoomY) * ease;
473
+ this.sendViewTransform(chart);
474
+ this.drawChart(chart);
475
+ if (this._syncViews)
476
+ this.syncAllViews(chart);
477
+ if (t < 1)
478
+ requestAnimationFrame(animate);
479
+ };
480
+ requestAnimationFrame(animate);
481
+ }
482
+ sendViewTransform(chart) {
483
+ this.worker?.postMessage({
484
+ type: "view-transform",
485
+ id: chart.id,
486
+ panX: chart.view.panX,
487
+ panY: chart.view.panY,
488
+ zoomX: chart.view.zoomX,
489
+ zoomY: chart.view.zoomY
490
+ });
491
+ }
492
+ syncAllViews(source) {
493
+ const transforms = [];
494
+ for (const chart of this.charts.values()) {
495
+ if (chart.id !== source.id) {
496
+ chart.view = { ...source.view };
497
+ transforms.push({ id: chart.id });
498
+ this.drawChart(chart);
499
+ }
500
+ }
501
+ if (transforms.length > 0) {
502
+ this.worker?.postMessage({
503
+ type: "batch-view-transform",
504
+ panX: source.view.panX,
505
+ panY: source.view.panY,
506
+ zoomX: source.view.zoomX,
507
+ zoomY: source.view.zoomY,
508
+ transforms
509
+ });
510
+ }
511
+ }
512
+ drawChart(chart) {
513
+ if (!chart.visible)
514
+ return;
515
+ const dpr = devicePixelRatio || 1;
516
+ const backCtx = chart.backCanvas.getContext("2d");
517
+ if (backCtx) {
518
+ backCtx.clearRect(0, 0, chart.backCanvas.width, chart.backCanvas.height);
519
+ backCtx.save();
520
+ backCtx.scale(dpr, dpr);
521
+ for (const plugin of globalPlugins) {
522
+ plugin.beforeDraw?.(backCtx, chart);
523
+ }
524
+ backCtx.restore();
525
+ }
526
+ const ctx = chart.axisCanvas.getContext("2d");
527
+ if (ctx) {
528
+ ctx.clearRect(0, 0, chart.axisCanvas.width, chart.axisCanvas.height);
529
+ ctx.save();
530
+ ctx.scale(dpr, dpr);
531
+ for (const plugin of globalPlugins) {
532
+ plugin.afterDraw?.(ctx, chart);
533
+ }
534
+ ctx.restore();
535
+ }
536
+ }
537
+ }
538
+ export {
539
+ unregisterPlugin,
540
+ registerPlugin,
541
+ getPlugins,
542
+ ChartManager
543
+ };
544
+ export { ChartManager };
@@ -0,0 +1 @@
1
+ var $=[];function c(j){if(!$.some((q)=>q.name===j.name))$.push(j)}function i(j){let q=$.findIndex((A)=>A.name===j);if(q>=0)$.splice(q,1)}function e(){return $}class V{static instance=null;static MARGIN={left:55,right:10,top:8,bottom:45};static HOVER_MAX_DISTANCE_PX=50;static MIN_ZOOM=0.1;static MAX_ZOOM=1e7;static DEFAULT_LABEL_SIZE=12;static DEFAULT_FONT='-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif';static resizeCanvas(j,q,A){let B=devicePixelRatio||1;if(j.width=Math.round(q*B),j.height=Math.round(A*B),j instanceof HTMLCanvasElement)j.style.width=`${q}px`,j.style.height=`${A}px`}worker=null;charts=new Map;chartIdCounter=0;_syncViews=!1;_isDark=!1;statsCallbacks=[];currentStats={fps:0,renderMs:0,total:0,active:0};visibilityObserver;resizeObserver;constructor(){this._isDark=document.documentElement.classList.contains("dark"),this.visibilityObserver=new IntersectionObserver((j)=>{for(let q of j){let A=q.target.dataset.chartId;if(A){let B=this.charts.get(A);if(B)B.visible=q.isIntersecting,this.worker?.postMessage({type:"set-visibility",id:A,visible:q.isIntersecting})}}},{threshold:0.01}),this.resizeObserver=new ResizeObserver((j)=>{for(let q of j){let A=q.target.dataset.chartId;if(!A)continue;let B=this.charts.get(A);if(!B)continue;let{width:E,height:J}=q.contentRect;if(E<=0||J<=0)continue;B.width=E,B.height=J;let N=devicePixelRatio||1;this.worker?.postMessage({type:"resize",id:A,width:Math.round(E*N),height:Math.round(J*N)}),V.resizeCanvas(B.backCanvas,E,J),V.resizeCanvas(B.axisCanvas,E,J),this.drawChart(B)}})}static getInstance(){if(!V.instance)V.instance=new V;return V.instance}get isDark(){return this._isDark}get syncViews(){return this._syncViews}async init(){if(this.worker)return!0;return new Promise((j)=>{let q=new URL("./gpu-worker.js",import.meta.url);this.worker=new Worker(q,{type:"module"}),this.worker.onmessage=(A)=>{let{type:B,...E}=A.data;switch(B){case"gpu-ready":j(!0);break;case"error":console.error("ChartManager GPU Error:",E.message),j(!1);break;case"stats":this.currentStats={fps:E.fps,renderMs:E.renderMs,total:E.totalCharts,active:E.activeCharts};for(let J of this.statsCallbacks)J(this.currentStats);break;case"bounds-update":{let J=this.charts.get(E.id);if(J)J.bounds={minX:E.minX,maxX:E.maxX,minY:E.minY,maxY:E.maxY},this.drawChart(J);break}}},this.worker.postMessage({type:"init",isDark:this._isDark})})}create(j){if(!this.worker)throw new Error("ChartManager not initialized. Call init() first.");let q=`chart-${++this.chartIdCounter}`,A=document.createElement("div");A.className="gpu-chart",A.dataset.chartId=q;let B=document.createElement("div");B.className="gpu-chart-canvas-wrap",B.dataset.chartId=q;let E=document.createElement("canvas");E.className="gpu-chart-back-canvas";let J=document.createElement("canvas");J.className="gpu-chart-canvas";let N=document.createElement("canvas");N.className="gpu-chart-axis-canvas",B.appendChild(E),B.appendChild(J),B.appendChild(N),A.appendChild(B),j.container.appendChild(A);let _;try{_=J.transferControlToOffscreen()}catch(Q){throw console.error(`ChartManager: Failed to create offscreen canvas for ${q}:`,Q),new Error(`Failed to create chart ${q}: ${Q}`)}let G=B.getBoundingClientRect(),K=G.width||400,T=G.height||200;if(V.resizeCanvas(_,K,T),V.resizeCanvas(E,K,T),V.resizeCanvas(N,K,T),j.bgColor){let[Q,R,L]=j.bgColor;B.style.background=`rgb(${Math.round(Q*255)},${Math.round(R*255)},${Math.round(L*255)})`}let F={id:q,config:j,el:A,backCanvas:E,axisCanvas:N,width:K,height:T,series:[],bounds:{minX:0,maxX:1,minY:0,maxY:1},view:{panX:0,panY:0,zoomX:1,zoomY:1},zoomMode:j.zoomMode||"both",visible:!0,dragging:!1,bgColor:j.bgColor,textColor:j.textColor,gridColor:j.gridColor,fontFamily:j.fontFamily,momentum:null,findNearestPoint(Q,R,L,p){if(this.series.length===0)return null;let m=Q/L,x=R/p,Y=this.bounds.maxX-this.bounds.minX,u=this.bounds.maxY-this.bounds.minY,y=Y/this.view.zoomX,b=u/this.view.zoomY,X=this.bounds.minX+this.view.panX*Y,l=this.bounds.minY+this.view.panY*u,S=X+m*y,o=l+(1-x)*b,H=-1,Z=-1,k=1/0,C=1/0;for(let P=0;P<this.series.length;P++){let U=this.series[P],f=U.rawX.length;if(f===0)continue;let z=0,w=f-1;while(z<w){let D=z+w>>1;if(U.rawX[D]<S)z=D+1;else w=D}let W=z;if(z>0){let D=Math.abs(U.rawX[z]-S);if(Math.abs(U.rawX[z-1]-S)<D)W=z-1}let M=Math.abs(U.rawX[W]-S),v=Math.abs(U.rawY[W]-o);if(M<k||M===k&&v<C)k=M,C=v,H=P,Z=W}if(H===-1)return null;let I=this.series[H],g=(I.rawX[Z]-X)/y*L;if(Math.abs(g-Q)>V.HOVER_MAX_DISTANCE_PX)return null;return{x:I.rawX[Z],y:I.rawY[Z],index:Z,screenX:Q,screenY:R,seriesIndex:H,seriesLabel:I.label}}};this.charts.set(q,F);let O=j.type==="bar"?"box":j.type;this.worker.postMessage({type:"register-chart",id:q,canvas:_,chartType:O,pointSize:j.pointSize??3,maxSamplesPerPixel:j.maxSamplesPerPixel??1e4,bgColor:j.bgColor??null},[_]),this.visibilityObserver.observe(A),this.resizeObserver.observe(B);for(let Q of $)Q.install?.(F,B);return this.updateSeries(q,j.series),q}destroy(j){let q=this.charts.get(j);if(!q)return;for(let B of $)B.uninstall?.(q);if(q.momentum)cancelAnimationFrame(q.momentum);this.visibilityObserver.unobserve(q.el);let A=q.el.querySelector(".gpu-chart-canvas-wrap");if(A)this.resizeObserver.unobserve(A);q.el.remove(),this.worker?.postMessage({type:"unregister-chart",id:j}),this.charts.delete(j)}updateSeries(j,q){let A=this.charts.get(j);if(!A||!this.worker)return;if(q.length===0)return;A.series=q.map((F)=>{let O=F.x.length;if(O===0)return{label:F.label,color:F.color,rawX:[],rawY:[]};let Q=Array.from({length:O},(R,L)=>L);return Q.sort((R,L)=>F.x[R]-F.x[L]),{label:F.label,color:F.color,rawX:Q.map((R)=>F.x[R]),rawY:Q.map((R)=>F.y[R])}});let B=1/0,E=-1/0,J=1/0,N=-1/0;for(let F of A.series)for(let O=0;O<F.rawX.length;O++){if(F.rawX[O]<B)B=F.rawX[O];if(F.rawX[O]>E)E=F.rawX[O];if(F.rawY[O]<J)J=F.rawY[O];if(F.rawY[O]>N)N=F.rawY[O]}let _=(E-B)*0.05||1,G=(N-J)*0.1||1;B-=_,E+=_,J-=G,N+=G;let K=A.config.defaultBounds;if(K){if(K.minX!==void 0)B=K.minX;if(K.maxX!==void 0)E=K.maxX;if(K.minY!==void 0)J=K.minY;if(K.maxY!==void 0)N=K.maxY}A.bounds={minX:B,maxX:E,minY:J,maxY:N};let T=A.series.map((F)=>({label:F.label,colorR:F.color.r,colorG:F.color.g,colorB:F.color.b,dataX:new Float32Array(F.rawX),dataY:new Float32Array(F.rawY)}));this.worker.postMessage({type:"update-series",id:j,series:T,bounds:A.bounds},T.flatMap((F)=>[F.dataX.buffer,F.dataY.buffer])),this.sendViewTransform(A),this.drawChart(A)}setPointSize(j,q){if(!this.charts.get(j))return;this.worker?.postMessage({type:"set-point-size",id:j,pointSize:Math.max(1,Math.min(8,Math.round(q)))})}setMaxSamplesPerPixel(j,q){if(!this.charts.get(j))return;this.worker?.postMessage({type:"set-max-samples",id:j,maxSamplesPerPixel:Math.max(0,q|0)})}setStyle(j,q){let A=this.charts.get(j);if(!A)return;if(q.bgColor!==void 0)A.bgColor=q.bgColor;if(q.textColor!==void 0)A.textColor=q.textColor;if(q.gridColor!==void 0)A.gridColor=q.gridColor;if(q.fontFamily!==void 0)A.fontFamily=q.fontFamily;if(q.bgColor!==void 0){let B=A.el.querySelector(".gpu-chart-canvas-wrap");if(B){let[E,J,N]=q.bgColor;B.style.background=`rgb(${Math.round(E*255)},${Math.round(J*255)},${Math.round(N*255)})`}this.worker?.postMessage({type:"set-style",id:j,bgColor:q.bgColor})}this.drawChart(A)}setZoomMode(j,q){let A=this.charts.get(j);if(A)A.zoomMode=q}getChartCount(){return this.charts.size}getZoomMode(j){return this.charts.get(j)?.zoomMode||"both"}setSyncViews(j){this._syncViews=j}setTheme(j){this._isDark=j,this.worker?.postMessage({type:"theme",isDark:j});for(let q of this.charts.values())this.drawChart(q)}onStats(j){return this.statsCallbacks.push(j),()=>{let q=this.statsCallbacks.indexOf(j);if(q>=0)this.statsCallbacks.splice(q,1)}}getStats(){return{...this.currentStats}}resetView(j){let q=this.charts.get(j);if(!q)return;if(q.momentum)cancelAnimationFrame(q.momentum),q.momentum=null;let A=q.view.panX,B=q.view.panY,E=q.view.zoomX,J=q.view.zoomY,N=performance.now(),_=()=>{let G=Math.min(1,(performance.now()-N)/300),K=1-Math.pow(1-G,3);if(q.view.panX=A*(1-K),q.view.panY=B*(1-K),q.view.zoomX=E+(1-E)*K,q.view.zoomY=J+(1-J)*K,this.sendViewTransform(q),this.drawChart(q),this._syncViews)this.syncAllViews(q);if(G<1)requestAnimationFrame(_)};requestAnimationFrame(_)}sendViewTransform(j){this.worker?.postMessage({type:"view-transform",id:j.id,panX:j.view.panX,panY:j.view.panY,zoomX:j.view.zoomX,zoomY:j.view.zoomY})}syncAllViews(j){let q=[];for(let A of this.charts.values())if(A.id!==j.id)A.view={...j.view},q.push({id:A.id}),this.drawChart(A);if(q.length>0)this.worker?.postMessage({type:"batch-view-transform",panX:j.view.panX,panY:j.view.panY,zoomX:j.view.zoomX,zoomY:j.view.zoomY,transforms:q})}drawChart(j){if(!j.visible)return;let q=devicePixelRatio||1,A=j.backCanvas.getContext("2d");if(A){A.clearRect(0,0,j.backCanvas.width,j.backCanvas.height),A.save(),A.scale(q,q);for(let E of $)E.beforeDraw?.(A,j);A.restore()}let B=j.axisCanvas.getContext("2d");if(B){B.clearRect(0,0,j.axisCanvas.width,j.axisCanvas.height),B.save(),B.scale(q,q);for(let E of $)E.afterDraw?.(B,j);B.restore()}}}export{i as unregisterPlugin,c as registerPlugin,e as getPlugins,V as ChartManager};export{V as a};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gpu-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu-worker.d.ts","sourceRoot":"","sources":["../src/gpu-worker.ts"],"names":[],"mappings":""}