chartai 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/chart-library.d.ts +34 -146
  2. package/dist/chart-library.d.ts.map +1 -1
  3. package/dist/chart-library.js +411 -322
  4. package/dist/chart-library.min.js +1 -1
  5. package/dist/charts/area.d.ts +6 -0
  6. package/dist/charts/area.d.ts.map +1 -0
  7. package/dist/charts/area.js +65 -0
  8. package/dist/charts/area.min.js +1 -0
  9. package/dist/charts/bar.d.ts +11 -0
  10. package/dist/charts/bar.d.ts.map +1 -0
  11. package/dist/charts/bar.js +65 -0
  12. package/dist/charts/bar.min.js +1 -0
  13. package/dist/charts/boids.js +167 -0
  14. package/dist/charts/boids.min.js +18 -0
  15. package/dist/charts/candlestick.d.ts +21 -0
  16. package/dist/charts/candlestick.d.ts.map +1 -0
  17. package/dist/charts/candlestick.js +10 -0
  18. package/dist/charts/candlestick.min.js +1 -0
  19. package/dist/charts/experimental/baseline-area.js +70 -0
  20. package/dist/charts/experimental/baseline-area.min.js +1 -0
  21. package/dist/charts/experimental/bubble.js +48 -0
  22. package/dist/charts/experimental/bubble.min.js +1 -0
  23. package/dist/charts/experimental/error-band.js +111 -0
  24. package/dist/charts/experimental/error-band.min.js +1 -0
  25. package/dist/charts/experimental/heatmap.js +69 -0
  26. package/dist/charts/experimental/heatmap.min.js +1 -0
  27. package/dist/charts/experimental/histogram.js +139 -0
  28. package/dist/charts/experimental/histogram.min.js +7 -0
  29. package/dist/charts/experimental/ohlc.js +132 -0
  30. package/dist/charts/experimental/ohlc.min.js +32 -0
  31. package/dist/charts/experimental/step.js +67 -0
  32. package/dist/charts/experimental/step.min.js +1 -0
  33. package/dist/charts/experimental/waterfall.js +121 -0
  34. package/dist/charts/experimental/waterfall.min.js +7 -0
  35. package/dist/charts/line.d.ts +12 -0
  36. package/dist/charts/line.d.ts.map +1 -0
  37. package/dist/charts/line.js +62 -0
  38. package/dist/charts/line.min.js +1 -0
  39. package/dist/charts/scatter.d.ts +11 -0
  40. package/dist/charts/scatter.d.ts.map +1 -0
  41. package/dist/charts/scatter.js +46 -0
  42. package/dist/charts/scatter.min.js +1 -0
  43. package/dist/chunk-0eh4rzy9.min.js +2 -0
  44. package/dist/chunk-0jepamv9.js +7 -0
  45. package/dist/chunk-1ngxm8t2.js +129 -0
  46. package/dist/chunk-50bcv2hw.min.js +2 -0
  47. package/dist/chunk-5gtx3pza.js +9 -0
  48. package/dist/chunk-64q9a7nw.min.js +2 -0
  49. package/dist/chunk-831dem4f.js +4 -0
  50. package/dist/chunk-93yrr7er.js +35 -0
  51. package/dist/chunk-bbyt23tw.min.js +2 -0
  52. package/dist/chunk-cbydth3q.min.js +2 -0
  53. package/dist/chunk-cvtt04m6.min.js +2 -0
  54. package/dist/chunk-g2qmt43n.min.js +33 -0
  55. package/dist/chunk-gm0d4cgx.min.js +2 -0
  56. package/dist/chunk-mmsy3yqt.js +27 -0
  57. package/dist/chunk-n8ew0z0e.js +637 -0
  58. package/dist/chunk-t0kdz02m.js +129 -0
  59. package/dist/chunk-wdfq2fpx.min.js +2 -0
  60. package/dist/chunk-yabjrff2.js +11 -0
  61. package/dist/gpu-worker.js +630 -686
  62. package/dist/gpu-worker.min.js +1 -1
  63. package/dist/msg.d.ts +33 -0
  64. package/dist/msg.d.ts.map +1 -0
  65. package/dist/plugins/coords.d.ts +18 -0
  66. package/dist/plugins/coords.d.ts.map +1 -0
  67. package/dist/plugins/experimental/annotations.js +164 -0
  68. package/dist/plugins/experimental/annotations.min.js +1 -0
  69. package/dist/plugins/experimental/crosshair.js +82 -0
  70. package/dist/plugins/experimental/crosshair.min.js +1 -0
  71. package/dist/plugins/experimental/minimap.js +190 -0
  72. package/dist/plugins/experimental/minimap.min.js +1 -0
  73. package/dist/plugins/experimental/range-selector.js +220 -0
  74. package/dist/plugins/experimental/range-selector.min.js +1 -0
  75. package/dist/plugins/experimental/ruler.js +434 -0
  76. package/dist/plugins/experimental/ruler.min.js +59 -0
  77. package/dist/plugins/experimental/stats.js +229 -0
  78. package/dist/plugins/experimental/stats.min.js +8 -0
  79. package/dist/plugins/experimental/threshold.js +96 -0
  80. package/dist/plugins/experimental/threshold.min.js +1 -0
  81. package/dist/plugins/experimental/tooltip-pin.js +177 -0
  82. package/dist/plugins/experimental/tooltip-pin.min.js +1 -0
  83. package/dist/plugins/experimental/watermark.js +76 -0
  84. package/dist/plugins/experimental/watermark.min.js +1 -0
  85. package/dist/plugins/hover.d.ts +15 -2
  86. package/dist/plugins/hover.d.ts.map +1 -1
  87. package/dist/plugins/hover.js +75 -14
  88. package/dist/plugins/hover.min.js +1 -1
  89. package/dist/plugins/labels-panel.d.ts +4 -0
  90. package/dist/plugins/labels-panel.d.ts.map +1 -0
  91. package/dist/plugins/labels-panel.js +122 -0
  92. package/dist/plugins/labels-panel.min.js +1 -0
  93. package/dist/plugins/labels.d.ts +17 -2
  94. package/dist/plugins/labels.d.ts.map +1 -1
  95. package/dist/plugins/labels.js +11 -99
  96. package/dist/plugins/labels.min.js +1 -1
  97. package/dist/plugins/legend.d.ts +16 -0
  98. package/dist/plugins/legend.d.ts.map +1 -0
  99. package/dist/plugins/legend.js +353 -0
  100. package/dist/plugins/legend.min.js +37 -0
  101. package/dist/plugins/shared.d.ts +7 -0
  102. package/dist/plugins/shared.d.ts.map +1 -0
  103. package/dist/plugins/zoom.d.ts +10 -2
  104. package/dist/plugins/zoom.d.ts.map +1 -1
  105. package/dist/plugins/zoom.js +63 -62
  106. package/dist/plugins/zoom.min.js +1 -1
  107. package/dist/types.d.ts +187 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/dist/types.js +0 -0
  110. package/dist/types.min.js +0 -0
  111. package/dist/worker-inline.d.ts +1 -1
  112. package/dist/worker-inline.d.ts.map +1 -1
  113. package/package.json +11 -11
  114. package/readme.md +54 -42
  115. package/dist/chunk-bgfkgcmg.js +0 -25
  116. package/dist/chunk-cj3zanvs.min.js +0 -2
@@ -0,0 +1,220 @@
1
+ import {
2
+ ChartManager
3
+ } from "../../chunk-n8ew0z0e.js";
4
+ import"../../chunk-93yrr7er.js";
5
+ import"../../chunk-5gtx3pza.js";
6
+
7
+ // src/plugins/experimental/range-selector.ts
8
+ var states = new WeakMap;
9
+ function drawMiniCanvas(chart, state) {
10
+ const { canvas, ctx } = state;
11
+ const w = canvas.width;
12
+ const h = canvas.height;
13
+ const dark = ChartManager.isDark;
14
+ const cfg = chart.config;
15
+ const bgColor = cfg.bgColor ?? (dark ? [0.11, 0.11, 0.12] : [0.98, 0.98, 0.98]);
16
+ ctx.fillStyle = `rgb(${bgColor.map((c) => Math.round(c * 255)).join(",")})`;
17
+ ctx.fillRect(0, 0, w, h);
18
+ const { bounds: b } = chart;
19
+ const rangeX = b.maxX - b.minX || 1;
20
+ const rangeY = b.maxY - b.minY || 1;
21
+ for (const series of chart.series) {
22
+ if (series.rawX.length === 0)
23
+ continue;
24
+ const { r, g, b: bv } = series.color;
25
+ ctx.strokeStyle = `rgba(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(bv * 255)},0.7)`;
26
+ ctx.lineWidth = 1;
27
+ ctx.beginPath();
28
+ const step = Math.max(1, Math.floor(series.rawX.length / w));
29
+ for (let i = 0;i < series.rawX.length; i += step) {
30
+ const sx = (series.rawX[i] - b.minX) / rangeX * w;
31
+ const sy = h - (series.rawY[i] - b.minY) / rangeY * h;
32
+ if (i === 0)
33
+ ctx.moveTo(sx, sy);
34
+ else
35
+ ctx.lineTo(sx, sy);
36
+ }
37
+ ctx.stroke();
38
+ }
39
+ const { view: v } = chart;
40
+ const hv = chart.homeView;
41
+ const homeRange = 1 / hv.zoomX;
42
+ const brushL = (v.panX - hv.panX) / homeRange * w;
43
+ const brushR = (v.panX - hv.panX + 1 / v.zoomX) / homeRange * w;
44
+ const brushColor = cfg.brushColor ?? (dark ? "rgba(255,255,255,0.12)" : "rgba(0,100,255,0.1)");
45
+ const brushBorder = dark ? "rgba(255,255,255,0.35)" : "rgba(0,100,255,0.5)";
46
+ ctx.fillStyle = dark ? "rgba(0,0,0,0.35)" : "rgba(255,255,255,0.5)";
47
+ ctx.fillRect(0, 0, brushL, h);
48
+ ctx.fillRect(brushR, 0, w - brushR, h);
49
+ ctx.fillStyle = brushColor;
50
+ ctx.fillRect(brushL, 0, brushR - brushL, h);
51
+ ctx.strokeStyle = brushBorder;
52
+ ctx.lineWidth = 1.5;
53
+ ctx.strokeRect(brushL, 0.75, brushR - brushL, h - 1.5);
54
+ }
55
+ var rangeSelectorPlugin = {
56
+ name: "range-selector",
57
+ install(chart, el) {
58
+ const cfg = chart.config;
59
+ const height = cfg.rangeSelectorHeight ?? 60;
60
+ const margin = cfg.rangeSelectorMargin ?? 4;
61
+ const ac = new AbortController;
62
+ const canvas = document.createElement("canvas");
63
+ canvas.style.cssText = `display:block;margin-top:${margin}px;`;
64
+ canvas.height = height;
65
+ canvas.width = el.offsetWidth || 400;
66
+ if (el.parentElement) {
67
+ el.parentElement.insertBefore(canvas, el.nextSibling);
68
+ }
69
+ const ctx2d = canvas.getContext("2d");
70
+ const state = {
71
+ canvas,
72
+ ctx: ctx2d,
73
+ abort: ac,
74
+ resizeObserver: null,
75
+ brushDrag: null
76
+ };
77
+ states.set(chart, state);
78
+ const getRelX = (e) => {
79
+ const rect = canvas.getBoundingClientRect();
80
+ return Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
81
+ };
82
+ canvas.addEventListener("mousemove", (e) => {
83
+ if (state.brushDrag)
84
+ return;
85
+ const rx = getRelX(e);
86
+ const { view: v } = chart;
87
+ const hv = chart.homeView;
88
+ const homeRange = 1 / hv.zoomX;
89
+ const brushL = (v.panX - hv.panX) / homeRange;
90
+ const brushR = brushL + hv.zoomX / v.zoomX;
91
+ const edgeTol = 5 / canvas.getBoundingClientRect().width;
92
+ if (Math.abs(rx - brushL) < edgeTol || Math.abs(rx - brushR) < edgeTol) {
93
+ canvas.style.cursor = "ew-resize";
94
+ } else if (rx > brushL && rx < brushR) {
95
+ canvas.style.cursor = "grab";
96
+ } else {
97
+ canvas.style.cursor = "default";
98
+ }
99
+ }, { signal: ac.signal });
100
+ canvas.addEventListener("mousedown", (e) => {
101
+ const rx = getRelX(e);
102
+ const { view: v } = chart;
103
+ const hv = chart.homeView;
104
+ const homeRange = 1 / hv.zoomX;
105
+ const brushL = (v.panX - hv.panX) / homeRange;
106
+ const brushR = brushL + hv.zoomX / v.zoomX;
107
+ const edgeTol = 5 / canvas.getBoundingClientRect().width;
108
+ if (Math.abs(rx - brushL) < edgeTol) {
109
+ state.brushDrag = {
110
+ type: "left",
111
+ startX: rx,
112
+ startPanX: v.panX,
113
+ startZoomX: v.zoomX
114
+ };
115
+ } else if (Math.abs(rx - brushR) < edgeTol) {
116
+ state.brushDrag = {
117
+ type: "right",
118
+ startX: rx,
119
+ startPanX: v.panX,
120
+ startZoomX: v.zoomX
121
+ };
122
+ } else if (rx > brushL && rx < brushR) {
123
+ state.brushDrag = {
124
+ type: "move",
125
+ startX: rx,
126
+ startPanX: v.panX,
127
+ startZoomX: v.zoomX
128
+ };
129
+ canvas.style.cursor = "grabbing";
130
+ } else {
131
+ const hv2 = chart.homeView;
132
+ const homeRange2 = 1 / hv2.zoomX;
133
+ const brushWidth = 1 / v.zoomX;
134
+ const newPanX = Math.max(hv2.panX, Math.min(hv2.panX + homeRange2 - brushWidth, hv2.panX + rx * homeRange2 - brushWidth / 2));
135
+ chart.view.panX = newPanX;
136
+ ChartManager.requestRender(chart.id);
137
+ ChartManager.drawChart(chart);
138
+ drawMiniCanvas(chart, state);
139
+ }
140
+ e.preventDefault();
141
+ }, { signal: ac.signal });
142
+ window.addEventListener("mousemove", (e) => {
143
+ if (!state.brushDrag)
144
+ return;
145
+ const rect = canvas.getBoundingClientRect();
146
+ const rx = (e.clientX - rect.left) / rect.width;
147
+ const dx = rx - state.brushDrag.startX;
148
+ const { startPanX, startZoomX } = state.brushDrag;
149
+ const hv = chart.homeView;
150
+ const homeRange = 1 / hv.zoomX;
151
+ if (state.brushDrag.type === "move") {
152
+ chart.view.panX = Math.max(hv.panX, Math.min(hv.panX + homeRange - 1 / startZoomX, startPanX + dx * homeRange));
153
+ } else if (state.brushDrag.type === "left") {
154
+ const newLeft = Math.max(hv.panX, startPanX + dx * homeRange);
155
+ const newRight = startPanX + 1 / startZoomX;
156
+ if (newLeft < newRight - 0.01) {
157
+ chart.view.panX = newLeft;
158
+ chart.view.zoomX = Math.max(hv.zoomX, 1 / (newRight - newLeft));
159
+ }
160
+ } else if (state.brushDrag.type === "right") {
161
+ const newRight = Math.min(hv.panX + homeRange, startPanX + 1 / startZoomX + dx * homeRange);
162
+ if (newRight > startPanX + 0.01) {
163
+ chart.view.zoomX = Math.max(hv.zoomX, 1 / (newRight - startPanX));
164
+ }
165
+ }
166
+ ChartManager.requestRender(chart.id);
167
+ ChartManager.drawChart(chart);
168
+ drawMiniCanvas(chart, state);
169
+ }, { signal: ac.signal });
170
+ window.addEventListener("mouseup", (e) => {
171
+ state.brushDrag = null;
172
+ const rect = canvas.getBoundingClientRect();
173
+ const rx = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
174
+ const { view: v } = chart;
175
+ const hv = chart.homeView;
176
+ const homeRange = 1 / hv.zoomX;
177
+ const brushL = (v.panX - hv.panX) / homeRange;
178
+ const brushR = brushL + hv.zoomX / v.zoomX;
179
+ const edgeTol = 5 / canvas.getBoundingClientRect().width;
180
+ if (Math.abs(rx - brushL) < edgeTol || Math.abs(rx - brushR) < edgeTol) {
181
+ canvas.style.cursor = "ew-resize";
182
+ } else if (rx > brushL && rx < brushR) {
183
+ canvas.style.cursor = "grab";
184
+ } else {
185
+ canvas.style.cursor = "default";
186
+ }
187
+ }, { signal: ac.signal });
188
+ const ro = new ResizeObserver(() => {
189
+ const w = el.offsetWidth;
190
+ if (w > 0)
191
+ canvas.width = w;
192
+ drawMiniCanvas(chart, state);
193
+ });
194
+ ro.observe(el);
195
+ state.resizeObserver = ro;
196
+ drawMiniCanvas(chart, state);
197
+ },
198
+ afterDraw(_, chart) {
199
+ const state = states.get(chart);
200
+ if (!state)
201
+ return;
202
+ const elW = chart.el.offsetWidth;
203
+ if (elW > 0 && state.canvas.width !== elW) {
204
+ state.canvas.width = elW;
205
+ }
206
+ drawMiniCanvas(chart, state);
207
+ },
208
+ uninstall(chart) {
209
+ const state = states.get(chart);
210
+ if (state) {
211
+ state.abort.abort();
212
+ state.resizeObserver.disconnect();
213
+ state.canvas.remove();
214
+ states.delete(chart);
215
+ }
216
+ }
217
+ };
218
+ export {
219
+ rangeSelectorPlugin
220
+ };
@@ -0,0 +1 @@
1
+ import{q as w}from"../../chunk-50bcv2hw.js";import"../../chunk-wdfq2fpx.js";import"../../chunk-bbyt23tw.js";var x=new WeakMap;function R(e,c){let{canvas:X,ctx:s}=c,d=X.width,b=X.height,t=w.isDark,C=e.config,i=C.bgColor??(t?[0.11,0.11,0.12]:[0.98,0.98,0.98]);s.fillStyle=`rgb(${i.map((o)=>Math.round(o*255)).join(",")})`,s.fillRect(0,0,d,b);let{bounds:f}=e,M=f.maxX-f.minX||1,u=f.maxY-f.minY||1;for(let o of e.series){if(o.rawX.length===0)continue;let{r:v,g:z,b:y}=o.color;s.strokeStyle=`rgba(${Math.round(v*255)},${Math.round(z*255)},${Math.round(y*255)},0.7)`,s.lineWidth=1,s.beginPath();let P=Math.max(1,Math.floor(o.rawX.length/d));for(let p=0;p<o.rawX.length;p+=P){let S=(o.rawX[p]-f.minX)/M*d,D=b-(o.rawY[p]-f.minY)/u*b;if(p===0)s.moveTo(S,D);else s.lineTo(S,D)}s.stroke()}let{view:n}=e,r=e.homeView,m=1/r.zoomX,a=(n.panX-r.panX)/m*d,g=(n.panX-r.panX+1/n.zoomX)/m*d,l=C.brushColor??(t?"rgba(255,255,255,0.12)":"rgba(0,100,255,0.1)"),h=t?"rgba(255,255,255,0.35)":"rgba(0,100,255,0.5)";s.fillStyle=t?"rgba(0,0,0,0.35)":"rgba(255,255,255,0.5)",s.fillRect(0,0,a,b),s.fillRect(g,0,d-g,b),s.fillStyle=l,s.fillRect(a,0,g-a,b),s.strokeStyle=h,s.lineWidth=1.5,s.strokeRect(a,0.75,g-a,b-1.5)}var L={name:"range-selector",install(e,c){let X=e.config,s=X.rangeSelectorHeight??60,d=X.rangeSelectorMargin??4,b=new AbortController,t=document.createElement("canvas");if(t.style.cssText=`display:block;margin-top:${d}px;`,t.height=s,t.width=c.offsetWidth||400,c.parentElement)c.parentElement.insertBefore(t,c.nextSibling);let C=t.getContext("2d"),i={canvas:t,ctx:C,abort:b,resizeObserver:null,brushDrag:null};x.set(e,i);let f=(u)=>{let n=t.getBoundingClientRect();return Math.max(0,Math.min(1,(u.clientX-n.left)/n.width))};t.addEventListener("mousemove",(u)=>{if(i.brushDrag)return;let n=f(u),{view:r}=e,m=e.homeView,a=1/m.zoomX,g=(r.panX-m.panX)/a,l=g+m.zoomX/r.zoomX,h=5/t.getBoundingClientRect().width;if(Math.abs(n-g)<h||Math.abs(n-l)<h)t.style.cursor="ew-resize";else if(n>g&&n<l)t.style.cursor="grab";else t.style.cursor="default"},{signal:b.signal}),t.addEventListener("mousedown",(u)=>{let n=f(u),{view:r}=e,m=e.homeView,a=1/m.zoomX,g=(r.panX-m.panX)/a,l=g+m.zoomX/r.zoomX,h=5/t.getBoundingClientRect().width;if(Math.abs(n-g)<h)i.brushDrag={type:"left",startX:n,startPanX:r.panX,startZoomX:r.zoomX};else if(Math.abs(n-l)<h)i.brushDrag={type:"right",startX:n,startPanX:r.panX,startZoomX:r.zoomX};else if(n>g&&n<l)i.brushDrag={type:"move",startX:n,startPanX:r.panX,startZoomX:r.zoomX},t.style.cursor="grabbing";else{let o=e.homeView,v=1/o.zoomX,z=1/r.zoomX,y=Math.max(o.panX,Math.min(o.panX+v-z,o.panX+n*v-z/2));e.view.panX=y,w.requestRender(e.id),w.drawChart(e),R(e,i)}u.preventDefault()},{signal:b.signal}),window.addEventListener("mousemove",(u)=>{if(!i.brushDrag)return;let n=t.getBoundingClientRect(),m=(u.clientX-n.left)/n.width-i.brushDrag.startX,{startPanX:a,startZoomX:g}=i.brushDrag,l=e.homeView,h=1/l.zoomX;if(i.brushDrag.type==="move")e.view.panX=Math.max(l.panX,Math.min(l.panX+h-1/g,a+m*h));else if(i.brushDrag.type==="left"){let o=Math.max(l.panX,a+m*h),v=a+1/g;if(o<v-0.01)e.view.panX=o,e.view.zoomX=Math.max(l.zoomX,1/(v-o))}else if(i.brushDrag.type==="right"){let o=Math.min(l.panX+h,a+1/g+m*h);if(o>a+0.01)e.view.zoomX=Math.max(l.zoomX,1/(o-a))}w.requestRender(e.id),w.drawChart(e),R(e,i)},{signal:b.signal}),window.addEventListener("mouseup",(u)=>{i.brushDrag=null;let n=t.getBoundingClientRect(),r=Math.max(0,Math.min(1,(u.clientX-n.left)/n.width)),{view:m}=e,a=e.homeView,g=1/a.zoomX,l=(m.panX-a.panX)/g,h=l+a.zoomX/m.zoomX,o=5/t.getBoundingClientRect().width;if(Math.abs(r-l)<o||Math.abs(r-h)<o)t.style.cursor="ew-resize";else if(r>l&&r<h)t.style.cursor="grab";else t.style.cursor="default"},{signal:b.signal});let M=new ResizeObserver(()=>{let u=c.offsetWidth;if(u>0)t.width=u;R(e,i)});M.observe(c),i.resizeObserver=M,R(e,i)},afterDraw(e,c){let X=x.get(c);if(!X)return;let s=c.el.offsetWidth;if(s>0&&X.canvas.width!==s)X.canvas.width=s;R(c,X)},uninstall(e){let c=x.get(e);if(c)c.abort.abort(),c.resizeObserver.disconnect(),c.canvas.remove(),x.delete(e)}};export{L as rangeSelectorPlugin};
@@ -0,0 +1,434 @@
1
+ import {
2
+ dataToScreen,
3
+ screenToData
4
+ } from "../../chunk-mmsy3yqt.js";
5
+ import {
6
+ DEFAULT_FONT
7
+ } from "../../chunk-1ngxm8t2.js";
8
+ import {
9
+ MARGIN
10
+ } from "../../chunk-831dem4f.js";
11
+ import {
12
+ ChartManager
13
+ } from "../../chunk-n8ew0z0e.js";
14
+ import"../../chunk-93yrr7er.js";
15
+ import"../../chunk-5gtx3pza.js";
16
+
17
+ // src/plugins/experimental/ruler.ts
18
+ var states = new WeakMap;
19
+ var ENDPOINT_HIT_PX = 12;
20
+ function showClearBtn(btn, visible) {
21
+ const isVisible = btn.hasAttribute("data-visible");
22
+ if (visible === isVisible)
23
+ return;
24
+ btn.style.transform = "";
25
+ if (visible) {
26
+ btn.setAttribute("data-visible", "");
27
+ } else {
28
+ btn.removeAttribute("data-visible");
29
+ }
30
+ }
31
+ function injectRulerStyles() {
32
+ if (document.getElementById("chart-ruler-styles"))
33
+ return;
34
+ const style = document.createElement("style");
35
+ style.id = "chart-ruler-styles";
36
+ style.textContent = `
37
+ @layer chartai.ruler {
38
+ .chart-ruler-wrapper {
39
+ position: absolute;
40
+ z-index: 1000;
41
+ pointer-events: none;
42
+ display: inline-block;
43
+ }
44
+ .chart-ruler-btn {
45
+ position: relative;
46
+ width: 28px;
47
+ height: 28px;
48
+ border-radius: 50%;
49
+ border: 1.5px solid var(--ruler-btn-border);
50
+ background: var(--ruler-btn-bg);
51
+ color: var(--ruler-btn-color);
52
+ z-index: 100;
53
+ cursor: pointer;
54
+ pointer-events: auto;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ padding: 0;
59
+ box-shadow: 0 1px 4px rgba(0,0,0,0.18);
60
+ }
61
+ .chart-ruler-btn[data-active] {
62
+ --ruler-btn-border: var(--ruler-active-border);
63
+ --ruler-btn-bg: var(--ruler-active-bg);
64
+ --ruler-btn-color: var(--ruler-active-color);
65
+ }
66
+ .chart-ruler-clear {
67
+ position: absolute;
68
+ top: -6px;
69
+ right: -6px;
70
+ width: 18px;
71
+ height: 18px;
72
+ border-radius: 50%;
73
+ border: 1px solid oklch(from var(--ruler-active-color) calc(l * 0.7) c h / 1);
74
+ background: oklch(from var(--ruler-active-color) calc(l * 0.22) c h / 1);
75
+ color: var(--ruler-active-color);
76
+ z-index: 101;
77
+ cursor: pointer;
78
+ pointer-events: none;
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ padding: 0;
83
+ box-shadow: 0 1px 3px rgba(0,0,0,0.22);
84
+ opacity: 0;
85
+ transform: scale(0.5);
86
+ transition: opacity 0.18s ease, transform 0.18s cubic-bezier(0.4, 0, 1, 1);
87
+ }
88
+ .chart-ruler-clear[data-visible] {
89
+ opacity: 1;
90
+ transform: scale(1);
91
+ pointer-events: auto;
92
+ transition: opacity 0.22s ease, transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
93
+ }
94
+ }`;
95
+ document.head.appendChild(style);
96
+ }
97
+ function applyTheme(el, dark) {
98
+ const s = el.style;
99
+ if (dark) {
100
+ s.setProperty("--ruler-btn-border", "rgba(255,255,255,0.2)");
101
+ s.setProperty("--ruler-btn-bg", "rgba(30,30,32,0.88)");
102
+ s.setProperty("--ruler-btn-color", "rgba(200,200,200,0.9)");
103
+ s.setProperty("--ruler-active-border", "rgba(255,200,50,0.8)");
104
+ s.setProperty("--ruler-active-bg", "rgba(255,200,50,0.2)");
105
+ s.setProperty("--ruler-active-color", "rgba(255,200,50,0.95)");
106
+ s.setProperty("--ruler-clear-border", "rgba(255,255,255,0.18)");
107
+ s.setProperty("--ruler-clear-bg", "rgba(30,30,32,0.95)");
108
+ s.setProperty("--ruler-clear-color", "rgba(200,200,200,0.9)");
109
+ } else {
110
+ s.setProperty("--ruler-btn-border", "rgba(0,0,0,0.15)");
111
+ s.setProperty("--ruler-btn-bg", "rgba(255,255,255,0.92)");
112
+ s.setProperty("--ruler-btn-color", "rgba(80,80,80,0.9)");
113
+ s.setProperty("--ruler-active-border", "rgba(180,100,0,0.8)");
114
+ s.setProperty("--ruler-active-bg", "rgba(180,100,0,0.12)");
115
+ s.setProperty("--ruler-active-color", "rgba(180,100,0,0.95)");
116
+ s.setProperty("--ruler-clear-border", "rgba(0,0,0,0.13)");
117
+ s.setProperty("--ruler-clear-bg", "rgba(255,255,255,0.97)");
118
+ s.setProperty("--ruler-clear-color", "rgba(80,80,80,0.9)");
119
+ }
120
+ }
121
+ function setWrapperPosition(el, pos = "bottom-right") {
122
+ const m = MARGIN;
123
+ const pad = 8;
124
+ el.style.removeProperty("top");
125
+ el.style.removeProperty("bottom");
126
+ el.style.removeProperty("left");
127
+ el.style.removeProperty("right");
128
+ switch (pos) {
129
+ case "top-left":
130
+ el.style.top = `${m.top + pad}px`;
131
+ el.style.left = `${m.left + pad}px`;
132
+ break;
133
+ case "top-right":
134
+ el.style.top = `${m.top + pad}px`;
135
+ el.style.right = `${m.right + pad}px`;
136
+ break;
137
+ case "bottom-right":
138
+ el.style.bottom = `${m.bottom + pad}px`;
139
+ el.style.right = `${m.right + pad}px`;
140
+ break;
141
+ default:
142
+ el.style.bottom = `${m.bottom + pad}px`;
143
+ el.style.left = `${m.left + pad}px`;
144
+ }
145
+ }
146
+ function drawPill(ctx, text, cx, cy, color, fontFamily, dark) {
147
+ ctx.font = `500 11px ${fontFamily}`;
148
+ const tw = ctx.measureText(text).width;
149
+ const pw = tw + 16;
150
+ const ph = 20;
151
+ const px = cx - pw / 2;
152
+ const py = cy - ph / 2;
153
+ ctx.beginPath();
154
+ ctx.roundRect(px, py, pw, ph, 5);
155
+ ctx.fillStyle = dark ? "rgba(20,20,22,0.90)" : "rgba(255,255,255,0.95)";
156
+ ctx.fill();
157
+ ctx.strokeStyle = color;
158
+ ctx.lineWidth = 1.5;
159
+ ctx.stroke();
160
+ ctx.fillStyle = color;
161
+ ctx.textAlign = "center";
162
+ ctx.textBaseline = "middle";
163
+ ctx.fillText(text, cx, cy);
164
+ }
165
+ function drawEndpoint(ctx, x, y, color, dark) {
166
+ ctx.beginPath();
167
+ ctx.arc(x, y, 5, 0, Math.PI * 2);
168
+ ctx.fillStyle = color;
169
+ ctx.fill();
170
+ ctx.strokeStyle = dark ? "rgba(0,0,0,0.5)" : "rgba(255,255,255,0.8)";
171
+ ctx.lineWidth = 1.5;
172
+ ctx.stroke();
173
+ }
174
+ function drawRulerShape(ctx, a, b, axis, chart, w, h, color, formatX, formatY, fontFamily, dark) {
175
+ const sa = dataToScreen(a.dataX, a.dataY, chart, w, h);
176
+ const sb = dataToScreen(b.dataX, b.dataY, chart, w, h);
177
+ ctx.save();
178
+ ctx.strokeStyle = color;
179
+ ctx.lineWidth = 1.5;
180
+ ctx.setLineDash([5, 4]);
181
+ const TICK = 6;
182
+ if (axis === "x") {
183
+ const midY = (sa.y + sb.y) / 2;
184
+ ctx.beginPath();
185
+ ctx.moveTo(sa.x, midY);
186
+ ctx.lineTo(sb.x, midY);
187
+ ctx.stroke();
188
+ ctx.setLineDash([]);
189
+ ctx.beginPath();
190
+ ctx.moveTo(sa.x, midY - TICK);
191
+ ctx.lineTo(sa.x, midY + TICK);
192
+ ctx.moveTo(sb.x, midY - TICK);
193
+ ctx.lineTo(sb.x, midY + TICK);
194
+ ctx.stroke();
195
+ drawEndpoint(ctx, sa.x, sa.y, color, dark);
196
+ drawEndpoint(ctx, sb.x, sb.y, color, dark);
197
+ const labelX = (sa.x + sb.x) / 2;
198
+ const labelY = midY - 16;
199
+ const dx = Math.abs(b.dataX - a.dataX);
200
+ drawPill(ctx, `ΔX: ${formatX(dx)}`, labelX, labelY, color, fontFamily, dark);
201
+ } else if (axis === "y") {
202
+ const midX = (sa.x + sb.x) / 2;
203
+ ctx.beginPath();
204
+ ctx.moveTo(midX, sa.y);
205
+ ctx.lineTo(midX, sb.y);
206
+ ctx.stroke();
207
+ ctx.setLineDash([]);
208
+ ctx.beginPath();
209
+ ctx.moveTo(midX - TICK, sa.y);
210
+ ctx.lineTo(midX + TICK, sa.y);
211
+ ctx.moveTo(midX - TICK, sb.y);
212
+ ctx.lineTo(midX + TICK, sb.y);
213
+ ctx.stroke();
214
+ drawEndpoint(ctx, sa.x, sa.y, color, dark);
215
+ drawEndpoint(ctx, sb.x, sb.y, color, dark);
216
+ const labelX = midX + 20;
217
+ const labelY = (sa.y + sb.y) / 2;
218
+ const dy = Math.abs(b.dataY - a.dataY);
219
+ drawPill(ctx, `ΔY: ${formatY(dy)}`, labelX, labelY, color, fontFamily, dark);
220
+ } else {
221
+ ctx.beginPath();
222
+ ctx.moveTo(sa.x, sa.y);
223
+ ctx.lineTo(sb.x, sb.y);
224
+ ctx.stroke();
225
+ drawEndpoint(ctx, sa.x, sa.y, color, dark);
226
+ drawEndpoint(ctx, sb.x, sb.y, color, dark);
227
+ const labelX = (sa.x + sb.x) / 2;
228
+ const labelY = (sa.y + sb.y) / 2 - 18;
229
+ const dx = Math.abs(b.dataX - a.dataX);
230
+ const dy = Math.abs(b.dataY - a.dataY);
231
+ drawPill(ctx, `ΔX: ${formatX(dx)} ΔY: ${formatY(dy)}`, labelX, labelY, color, fontFamily, dark);
232
+ }
233
+ ctx.restore();
234
+ }
235
+ var rulerPlugin = {
236
+ name: "ruler",
237
+ install(chart, el) {
238
+ injectRulerStyles();
239
+ const ac = new AbortController;
240
+ const cfg = chart.config;
241
+ const dark = ChartManager.isDark;
242
+ const wrapper = document.createElement("div");
243
+ wrapper.className = "chart-ruler-wrapper";
244
+ setWrapperPosition(wrapper, cfg.rulerPosition);
245
+ applyTheme(wrapper, dark);
246
+ const button = document.createElement("button");
247
+ button.type = "button";
248
+ button.className = "chart-ruler-btn";
249
+ button.title = "Toggle ruler tool";
250
+ button.innerHTML = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="4.5" width="12" height="5" rx="1" stroke="currentColor" stroke-width="1.3"/><line x1="3.5" y1="4.5" x2="3.5" y2="7" stroke="currentColor" stroke-width="1.2"/><line x1="5.5" y1="4.5" x2="5.5" y2="6" stroke="currentColor" stroke-width="1.2"/><line x1="7" y1="4.5" x2="7" y2="7" stroke="currentColor" stroke-width="1.2"/><line x1="8.5" y1="4.5" x2="8.5" y2="6" stroke="currentColor" stroke-width="1.2"/><line x1="10.5" y1="4.5" x2="10.5" y2="7" stroke="currentColor" stroke-width="1.2"/></svg>`;
251
+ const clearBtn = document.createElement("button");
252
+ clearBtn.type = "button";
253
+ clearBtn.className = "chart-ruler-clear";
254
+ clearBtn.title = "Clear all rulers";
255
+ clearBtn.innerHTML = `<svg width="5" height="5" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg"><line x1="1" y1="1" x2="5" y2="5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><line x1="5" y1="1" x2="1" y2="5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>`;
256
+ wrapper.appendChild(button);
257
+ wrapper.appendChild(clearBtn);
258
+ el.appendChild(wrapper);
259
+ const state = {
260
+ rulers: [],
261
+ pending: null,
262
+ cursorDataX: 0,
263
+ cursorDataY: 0,
264
+ active: false,
265
+ justToggledButton: false,
266
+ abort: ac,
267
+ button,
268
+ clearBtn,
269
+ wrapper
270
+ };
271
+ states.set(chart, state);
272
+ button.addEventListener("pointerdown", (e) => {
273
+ e.stopPropagation();
274
+ e.stopImmediatePropagation();
275
+ e.preventDefault();
276
+ state.active = !state.active;
277
+ if (!state.active)
278
+ state.pending = null;
279
+ state.justToggledButton = true;
280
+ if (state.active)
281
+ button.dataset.active = "";
282
+ else
283
+ delete button.dataset.active;
284
+ ChartManager.drawChart(chart);
285
+ }, { signal: ac.signal });
286
+ clearBtn.addEventListener("pointerdown", (e) => {
287
+ e.stopPropagation();
288
+ e.stopImmediatePropagation();
289
+ e.preventDefault();
290
+ clearBtn.style.transform = "scale(0.78)";
291
+ state.rulers = [];
292
+ state.pending = null;
293
+ state.justToggledButton = true;
294
+ ChartManager.drawChart(chart);
295
+ }, { signal: ac.signal });
296
+ clearBtn.addEventListener("click", (e) => {
297
+ e.stopPropagation();
298
+ e.stopImmediatePropagation();
299
+ e.preventDefault();
300
+ }, { signal: ac.signal });
301
+ el.addEventListener("mousemove", (e) => {
302
+ const r = el.getBoundingClientRect();
303
+ const { x, y } = screenToData(e.clientX - r.left, e.clientY - r.top, chart, r.width, r.height);
304
+ state.cursorDataX = x;
305
+ state.cursorDataY = y;
306
+ if (state.active || state.pending !== null) {
307
+ ChartManager.drawChart(chart);
308
+ }
309
+ e.preventDefault();
310
+ }, { signal: ac.signal });
311
+ el.addEventListener("click", (e) => {
312
+ if (!state.active)
313
+ return;
314
+ if (state.justToggledButton) {
315
+ state.justToggledButton = false;
316
+ return;
317
+ }
318
+ if (chart.dragging)
319
+ return;
320
+ e.preventDefault();
321
+ const r = el.getBoundingClientRect();
322
+ const sx = e.clientX - r.left;
323
+ const sy = e.clientY - r.top;
324
+ const { x: dataX, y: dataY } = screenToData(sx, sy, chart, r.width, r.height);
325
+ for (let i = state.rulers.length - 1;i >= 0; i--) {
326
+ const ruler = state.rulers[i];
327
+ const sa = dataToScreen(ruler.a.dataX, ruler.a.dataY, chart, r.width, r.height);
328
+ const sb = dataToScreen(ruler.b.dataX, ruler.b.dataY, chart, r.width, r.height);
329
+ const dA = Math.hypot(sa.x - sx, sa.y - sy);
330
+ const dB = Math.hypot(sb.x - sx, sb.y - sy);
331
+ if (dA <= ENDPOINT_HIT_PX || dB <= ENDPOINT_HIT_PX) {
332
+ state.rulers.splice(i, 1);
333
+ ChartManager.drawChart(chart);
334
+ return;
335
+ }
336
+ }
337
+ if (state.pending === null) {
338
+ state.pending = { dataX, dataY };
339
+ } else {
340
+ const rulerMax = chart.config.rulerMax ?? 10;
341
+ if (state.rulers.length >= rulerMax)
342
+ state.rulers.shift();
343
+ state.rulers.push({ a: state.pending, b: { dataX, dataY } });
344
+ state.pending = null;
345
+ ChartManager.drawChart(chart);
346
+ }
347
+ }, { signal: ac.signal });
348
+ el.addEventListener("contextmenu", (e) => {
349
+ e.preventDefault();
350
+ if (state.pending !== null) {
351
+ state.pending = null;
352
+ } else {
353
+ const r = el.getBoundingClientRect();
354
+ const sx = e.clientX - r.left;
355
+ const sy = e.clientY - r.top;
356
+ let bestIdx = -1;
357
+ let bestDist = Infinity;
358
+ for (let i = 0;i < state.rulers.length; i++) {
359
+ const ruler = state.rulers[i];
360
+ const sa = dataToScreen(ruler.a.dataX, ruler.a.dataY, chart, r.width, r.height);
361
+ const sb = dataToScreen(ruler.b.dataX, ruler.b.dataY, chart, r.width, r.height);
362
+ const dA = Math.hypot(sa.x - sx, sa.y - sy);
363
+ const dB = Math.hypot(sb.x - sx, sb.y - sy);
364
+ const d = Math.min(dA, dB);
365
+ if (d < bestDist) {
366
+ bestDist = d;
367
+ bestIdx = i;
368
+ }
369
+ }
370
+ if (bestIdx !== -1)
371
+ state.rulers.splice(bestIdx, 1);
372
+ }
373
+ ChartManager.drawChart(chart);
374
+ }, { signal: ac.signal });
375
+ window.addEventListener("keydown", (e) => {
376
+ if (e.key === "Escape" && state.active) {
377
+ state.pending = null;
378
+ ChartManager.drawChart(chart);
379
+ }
380
+ }, { signal: ac.signal });
381
+ },
382
+ afterDraw(ctx, chart) {
383
+ const state = states.get(chart);
384
+ if (!state)
385
+ return;
386
+ const w = chart.width;
387
+ const h = chart.height;
388
+ const dark = ChartManager.isDark;
389
+ const cfg = chart.config;
390
+ const axis = cfg.rulerAxis ?? "x";
391
+ const color = cfg.rulerColor ?? (dark ? "rgba(255,200,50,0.9)" : "rgba(180,100,0,0.9)");
392
+ const formatX = cfg.formatX ?? String;
393
+ const formatY = cfg.formatY ?? String;
394
+ const fontFamily = cfg.fontFamily ?? DEFAULT_FONT;
395
+ setWrapperPosition(state.wrapper, cfg.rulerPosition);
396
+ applyTheme(state.wrapper, dark);
397
+ if (state.active)
398
+ state.button.dataset.active = "";
399
+ else
400
+ delete state.button.dataset.active;
401
+ showClearBtn(state.clearBtn, state.rulers.length > 0);
402
+ ctx.save();
403
+ for (const ruler of state.rulers) {
404
+ drawRulerShape(ctx, ruler.a, ruler.b, axis, chart, w, h, color, formatX, formatY, fontFamily, dark);
405
+ }
406
+ if (state.pending !== null) {
407
+ const cursor = {
408
+ dataX: state.cursorDataX,
409
+ dataY: state.cursorDataY
410
+ };
411
+ drawRulerShape(ctx, state.pending, cursor, axis, chart, w, h, color, formatX, formatY, fontFamily, dark);
412
+ const sc = dataToScreen(state.cursorDataX, state.cursorDataY, chart, w, h);
413
+ ctx.save();
414
+ ctx.beginPath();
415
+ ctx.arc(sc.x, sc.y, 5, 0, Math.PI * 2);
416
+ ctx.strokeStyle = color;
417
+ ctx.lineWidth = 1.5;
418
+ ctx.stroke();
419
+ ctx.restore();
420
+ }
421
+ ctx.restore();
422
+ },
423
+ uninstall(chart) {
424
+ const state = states.get(chart);
425
+ if (!state)
426
+ return;
427
+ state.abort.abort();
428
+ state.wrapper.remove();
429
+ states.delete(chart);
430
+ }
431
+ };
432
+ export {
433
+ rulerPlugin
434
+ };