velo-plot 2.20.0 → 3.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 (287) hide show
  1. package/README.md +26 -12
  2. package/dist/ChartCore-AnR_3Xkb.js +7338 -0
  3. package/dist/ChartCore-AnR_3Xkb.js.map +1 -0
  4. package/dist/GpuChartRenderer-Dxc0hrQA.js +75 -0
  5. package/dist/GpuChartRenderer-Dxc0hrQA.js.map +1 -0
  6. package/dist/{index.core-COREBDNq.js → PluginRegistry-CzMHebYP.js} +24 -38
  7. package/dist/PluginRegistry-CzMHebYP.js.map +1 -0
  8. package/dist/a11y-Bw6urYpr.js +105 -0
  9. package/dist/a11y-Bw6urYpr.js.map +1 -0
  10. package/dist/angular/hooks-entry.d.ts +1 -0
  11. package/dist/angular/hooks.d.ts +26 -0
  12. package/dist/angular/index.d.ts +8 -0
  13. package/dist/angular/velo-plot.component.d.ts +46 -0
  14. package/dist/angular-hooks.d.ts +1 -0
  15. package/dist/angular-hooks.js +9 -0
  16. package/dist/angular-hooks.js.map +1 -0
  17. package/dist/angular.d.ts +1 -0
  18. package/dist/angular.js +166 -0
  19. package/dist/angular.js.map +1 -0
  20. package/dist/assets/indicator.worker-bv0zC4EX.js.map +1 -1
  21. package/dist/astro/StackedPlot.astro +19 -0
  22. package/dist/astro/VeloPlot.astro +23 -0
  23. package/dist/{axisFormat-CzumxpCL.js → axisFormat-Sz4WIJfQ.js} +2 -2
  24. package/dist/{axisFormat-CzumxpCL.js.map → axisFormat-Sz4WIJfQ.js.map} +1 -1
  25. package/dist/bindings/shared/a11y.d.ts +26 -0
  26. package/dist/bindings/shared/chartLifecycle.d.ts +16 -0
  27. package/dist/bindings/shared/chartSyncBridge.d.ts +8 -0
  28. package/dist/bindings/shared/dimensions.d.ts +1 -0
  29. package/dist/bindings/shared/index.d.ts +9 -0
  30. package/dist/bindings/shared/indicatorBridge.d.ts +11 -0
  31. package/dist/bindings/shared/optionsSync.d.ts +7 -0
  32. package/dist/bindings/shared/seriesDiff.d.ts +8 -0
  33. package/dist/bindings/shared/stackedSync.d.ts +9 -0
  34. package/dist/bindings/shared/types.d.ts +46 -0
  35. package/dist/bindings/test-utils/index.d.ts +35 -0
  36. package/dist/bindings/test-utils/mockLifecycle.d.ts +120 -0
  37. package/dist/bindings/test-utils/vitestSveltePlugin.d.ts +3 -0
  38. package/dist/chartSyncBridge-D_rCtbFq.js +58 -0
  39. package/dist/chartSyncBridge-D_rCtbFq.js.map +1 -0
  40. package/dist/core/Chart.d.ts +1 -1
  41. package/dist/core/ChartStatistics.d.ts +1 -1
  42. package/dist/core/InteractionManager.d.ts +7 -1
  43. package/dist/core/OverlayRenderer.d.ts +5 -0
  44. package/dist/core/annotations/types.d.ts +1 -1
  45. package/dist/core/chart/ChartCore.d.ts +14 -19
  46. package/dist/core/chart/ChartFeatureHooks.d.ts +7 -0
  47. package/dist/core/chart/chartExportPatch.d.ts +2 -0
  48. package/dist/core/chart/overlaySeriesRegistry.d.ts +7 -0
  49. package/dist/core/chart/series/SeriesActions.d.ts +1 -0
  50. package/dist/core/chart/series/SeriesBufferLite.d.ts +13 -0
  51. package/dist/core/chart/series/seriesOptionsRegistry.d.ts +8 -0
  52. package/dist/core/chart/series/seriesTypeRegistry.d.ts +5 -0
  53. package/dist/core/chart/types.d.ts +1 -1
  54. package/dist/core/clipboard/index.d.ts +1 -1
  55. package/dist/core/debug/index.d.ts +1 -1
  56. package/dist/core/keybindings/index.d.ts +1 -1
  57. package/dist/core/loading/index.d.ts +1 -1
  58. package/dist/core/locale/index.d.ts +1 -1
  59. package/dist/core/stacked/paneResize.d.ts +3 -0
  60. package/dist/core/stacked/types.d.ts +10 -0
  61. package/dist/core/theme-editor/index.d.ts +1 -1
  62. package/dist/createStackedChart-DqcLCFGS.js +1338 -0
  63. package/dist/createStackedChart-DqcLCFGS.js.map +1 -0
  64. package/dist/examples/PluginDemonstration.d.ts +1 -1
  65. package/dist/hooks-DH-3bewj.js +69 -0
  66. package/dist/hooks-DH-3bewj.js.map +1 -0
  67. package/dist/{index-BBpJwOct.js → index-0InEyj0q.js} +119 -121
  68. package/dist/index-0InEyj0q.js.map +1 -0
  69. package/dist/{index-qunX30Xu.js → index-B7PTX94B.js} +2133 -2133
  70. package/dist/index-B7PTX94B.js.map +1 -0
  71. package/dist/{index-BMiZoKmm.js → index-BgrvqltV.js} +67 -67
  72. package/dist/index-BgrvqltV.js.map +1 -0
  73. package/dist/{index-DoYxf1cV.js → index-CWxIHdwX.js} +38 -98
  74. package/dist/index-CWxIHdwX.js.map +1 -0
  75. package/dist/index-CaXWSOTk.js.map +1 -1
  76. package/dist/{mockDatafeed-927foCM4.js → index-Cv6ZDLWf.js} +227 -217
  77. package/dist/index-Cv6ZDLWf.js.map +1 -0
  78. package/dist/index-Dag88bW4.js.map +1 -1
  79. package/dist/index-qhscKTDy.js.map +1 -1
  80. package/dist/index.core.d.ts +9 -7
  81. package/dist/index.d.ts +2 -15
  82. package/dist/indicators--eJGmVox.js.map +1 -1
  83. package/dist/optionsSync-QUiN4Inw.js +119 -0
  84. package/dist/optionsSync-QUiN4Inw.js.map +1 -0
  85. package/dist/plugins/3d/index.d.ts +2 -2
  86. package/dist/plugins/3d.d.ts +2 -2
  87. package/dist/plugins/3d.js +3 -1
  88. package/dist/plugins/3d.js.map +1 -1
  89. package/dist/plugins/analysis/index.d.ts +1 -1
  90. package/dist/plugins/analysis/indicators.d.ts +1 -1
  91. package/dist/plugins/analysis.d.ts +2 -2
  92. package/dist/plugins/analysis.js +2 -2
  93. package/dist/plugins/analysis.js.map +1 -1
  94. package/dist/plugins/annotations/index.d.ts +1 -1
  95. package/dist/plugins/annotations.d.ts +2 -2
  96. package/dist/plugins/annotations.js.map +1 -1
  97. package/dist/plugins/anomaly-detection.js +1 -1
  98. package/dist/plugins/anomaly-detection.js.map +1 -1
  99. package/dist/plugins/broken-axis.d.ts +2 -2
  100. package/dist/plugins/builtins/crosshair/CrosshairPlugin.d.ts +1 -1
  101. package/dist/plugins/builtins/index.d.ts +1 -1
  102. package/dist/plugins/caching.d.ts +2 -2
  103. package/dist/plugins/clipboard/index.d.ts +2 -2
  104. package/dist/plugins/clipboard.d.ts +2 -2
  105. package/dist/plugins/clipboard.js.map +1 -1
  106. package/dist/plugins/context-menu/index.d.ts +1 -1
  107. package/dist/plugins/context-menu.d.ts +2 -2
  108. package/dist/plugins/context-menu.js +1 -1
  109. package/dist/plugins/context-menu.js.map +1 -1
  110. package/dist/plugins/createPlugin.d.ts +8 -0
  111. package/dist/plugins/data-export/index.d.ts +1 -1
  112. package/dist/plugins/data-export.d.ts +2 -2
  113. package/dist/plugins/data-export.js +2 -2
  114. package/dist/plugins/data-export.js.map +1 -1
  115. package/dist/plugins/data-transform.d.ts +2 -2
  116. package/dist/plugins/data-transform.js +19 -19
  117. package/dist/plugins/data-transform.js.map +1 -1
  118. package/dist/plugins/debug/index.d.ts +2 -2
  119. package/dist/plugins/debug.d.ts +2 -2
  120. package/dist/plugins/debug.js +33 -33
  121. package/dist/plugins/debug.js.map +1 -1
  122. package/dist/plugins/forecasting/index.d.ts +1 -1
  123. package/dist/plugins/forecasting.js.map +1 -1
  124. package/dist/plugins/gpu/index.d.ts +2 -2
  125. package/dist/plugins/gpu.d.ts +2 -2
  126. package/dist/plugins/gpu.js +2033 -209
  127. package/dist/plugins/gpu.js.map +1 -1
  128. package/dist/plugins/i18n/index.d.ts +2 -2
  129. package/dist/plugins/i18n.d.ts +2 -2
  130. package/dist/plugins/i18n.js +286 -23
  131. package/dist/plugins/i18n.js.map +1 -1
  132. package/dist/plugins/index.d.ts +1 -1
  133. package/dist/plugins/keyboard.d.ts +2 -2
  134. package/dist/plugins/keyboard.js +65 -5
  135. package/dist/plugins/keyboard.js.map +1 -1
  136. package/dist/plugins/latex.js +766 -207
  137. package/dist/plugins/latex.js.map +1 -1
  138. package/dist/plugins/lazy-load.d.ts +2 -2
  139. package/dist/plugins/loading/index.d.ts +2 -3
  140. package/dist/plugins/loading.d.ts +2 -2
  141. package/dist/plugins/loading.js +3 -4
  142. package/dist/plugins/ml-integration.d.ts +2 -2
  143. package/dist/plugins/ml-integration.js +5 -5
  144. package/dist/plugins/ml-integration.js.map +1 -1
  145. package/dist/plugins/offscreen.d.ts +2 -2
  146. package/dist/plugins/offscreen.js +11 -10
  147. package/dist/plugins/offscreen.js.map +1 -1
  148. package/dist/plugins/pattern-recognition.d.ts +2 -2
  149. package/dist/plugins/pattern-recognition.js +1 -1
  150. package/dist/plugins/pattern-recognition.js.map +1 -1
  151. package/dist/plugins/radar.d.ts +2 -2
  152. package/dist/plugins/regression.d.ts +2 -2
  153. package/dist/plugins/regression.js +255 -257
  154. package/dist/plugins/regression.js.map +1 -1
  155. package/dist/plugins/roi.js +9 -9
  156. package/dist/plugins/roi.js.map +1 -1
  157. package/dist/plugins/snapshot/index.d.ts +1 -1
  158. package/dist/plugins/snapshot/types.d.ts +1 -1
  159. package/dist/plugins/snapshot.d.ts +2 -2
  160. package/dist/plugins/snapshot.js.map +1 -1
  161. package/dist/plugins/streaming/index.d.ts +2 -2
  162. package/dist/plugins/streaming.d.ts +2 -2
  163. package/dist/plugins/streaming.js +1 -1
  164. package/dist/plugins/sync/index.d.ts +3 -2
  165. package/dist/plugins/sync.d.ts +2 -2
  166. package/dist/plugins/sync.js.map +1 -1
  167. package/dist/plugins/theme-editor/index.d.ts +1 -1
  168. package/dist/plugins/theme-editor.d.ts +2 -2
  169. package/dist/plugins/tools/index.d.ts +1 -1
  170. package/dist/plugins/tools/tooltip/index.d.ts +1 -1
  171. package/dist/plugins/tools.d.ts +2 -2
  172. package/dist/plugins/tools.js +1 -1
  173. package/dist/plugins/types.d.ts +1 -1
  174. package/dist/plugins/video-recorder.d.ts +2 -2
  175. package/dist/plugins/virtualization.d.ts +2 -2
  176. package/dist/plugins/virtualization.js +1 -1
  177. package/dist/pool-DZ8shSqv.js +109 -0
  178. package/dist/pool-DZ8shSqv.js.map +1 -0
  179. package/dist/react/StackedPlot.d.ts +31 -0
  180. package/dist/react/VeloPlot.d.ts +29 -0
  181. package/dist/react/index.d.ts +25 -3
  182. package/dist/react/useChartSync.d.ts +8 -0
  183. package/dist/react/useIndicator.d.ts +12 -0
  184. package/dist/react/useVeloPlot.d.ts +19 -0
  185. package/dist/react.js +325 -24
  186. package/dist/react.js.map +1 -1
  187. package/dist/renderer/NativeWebGLRenderer.d.ts +2 -1
  188. package/dist/renderer/native/NativeWebGLRenderer.d.ts +5 -1
  189. package/dist/renderer/native/draw.d.ts +2 -47
  190. package/dist/renderer/native/drawCore.d.ts +27 -0
  191. package/dist/renderer/native/drawExtended.d.ts +29 -0
  192. package/dist/renderer/native/frameRenderExtended.d.ts +1 -0
  193. package/dist/renderer/native/frameRenderRegistry.d.ts +27 -0
  194. package/dist/renderer/native/programFactoryCore.d.ts +6 -0
  195. package/dist/renderer/native/programFactoryExtended.d.ts +4 -0
  196. package/dist/renderer/native/utils.d.ts +3 -29
  197. package/dist/renderer/native/utilsCore.d.ts +14 -0
  198. package/dist/renderer/native/utilsExtended.d.ts +17 -0
  199. package/dist/renderer/overlaySeriesExtended.d.ts +1 -0
  200. package/dist/renderer/registerExtendedSeries.d.ts +1 -0
  201. package/dist/renderer/registerWebGPU.d.ts +2 -0
  202. package/dist/renderer/seriesBufferExtended.d.ts +4 -0
  203. package/dist/scientific/index.d.ts +44 -0
  204. package/dist/scientific/registerScientific.d.ts +1 -0
  205. package/dist/scientific.d.ts +1 -0
  206. package/dist/scientific.js +91 -0
  207. package/dist/scientific.js.map +1 -0
  208. package/dist/solid/VeloPlot.d.ts +16 -0
  209. package/dist/solid/index.d.ts +13 -0
  210. package/dist/solid/useChartSync.d.ts +6 -0
  211. package/dist/solid/useIndicator.d.ts +11 -0
  212. package/dist/solid/useStackedPlot.d.ts +14 -0
  213. package/dist/solid/useVeloPlot.d.ts +18 -0
  214. package/dist/solid.d.ts +1 -0
  215. package/dist/solid.js +169 -0
  216. package/dist/solid.js.map +1 -0
  217. package/dist/stackedSync-tnrunZMt.js +100 -0
  218. package/dist/stackedSync-tnrunZMt.js.map +1 -0
  219. package/dist/svelte/StackedPlot.svelte +29 -0
  220. package/dist/svelte/StackedPlot.test.ts +114 -0
  221. package/dist/svelte/VeloPlot.svelte +44 -0
  222. package/dist/svelte/VeloPlot.test.ts +105 -0
  223. package/dist/svelte/index.d.ts +11 -0
  224. package/dist/svelte/index.test.ts +20 -0
  225. package/dist/svelte/index.ts +15 -0
  226. package/dist/svelte/useChartSync.d.ts +8 -0
  227. package/dist/svelte/useChartSync.test.ts +49 -0
  228. package/dist/svelte/useChartSync.ts +29 -0
  229. package/dist/svelte/useIndicator.d.ts +13 -0
  230. package/dist/svelte/useIndicator.test.ts +67 -0
  231. package/dist/svelte/useIndicator.ts +43 -0
  232. package/dist/svelte/useStackedPlot.d.ts +16 -0
  233. package/dist/svelte/useStackedPlot.test.ts +76 -0
  234. package/dist/svelte/useStackedPlot.ts +54 -0
  235. package/dist/svelte/useVeloPlot.d.ts +21 -0
  236. package/dist/svelte/useVeloPlot.test.ts +100 -0
  237. package/dist/svelte/useVeloPlot.ts +81 -0
  238. package/dist/svelte.d.ts +1 -0
  239. package/dist/svelte.js +140 -0
  240. package/dist/svelte.js.map +1 -0
  241. package/dist/testing/index.d.ts +1 -1
  242. package/dist/theme/index.d.ts +2 -1
  243. package/dist/trading/index.d.ts +1 -4
  244. package/dist/trading/registerTrading.d.ts +3 -0
  245. package/dist/trading.js +42 -42
  246. package/dist/types.d.ts +4 -4
  247. package/dist/{utils-BK5UUXxj.js → utils-Dx0h0bgQ.js} +9 -9
  248. package/dist/{utils-BK5UUXxj.js.map → utils-Dx0h0bgQ.js.map} +1 -1
  249. package/dist/velo-plot.full.js +2216 -1346
  250. package/dist/velo-plot.full.js.map +1 -1
  251. package/dist/velo-plot.js +33 -28
  252. package/dist/velo-plot.js.map +1 -1
  253. package/dist/vue/StackedPlot.vue.d.ts +48 -0
  254. package/dist/vue/VeloPlot.vue.d.ts +64 -0
  255. package/dist/vue/index.d.ts +250 -0
  256. package/dist/vue/useChartSync.d.ts +52 -0
  257. package/dist/vue/useIndicator.d.ts +23 -0
  258. package/dist/vue/useStackedPlot.d.ts +15 -0
  259. package/dist/vue/useVeloPlot.d.ts +20 -0
  260. package/dist/vue.d.ts +1 -0
  261. package/dist/vue.js +309 -0
  262. package/dist/vue.js.map +1 -0
  263. package/dist/workers/indicatorsAsync.d.ts +2 -0
  264. package/dist/workers/pool.d.ts +9 -0
  265. package/package.json +96 -10
  266. package/dist/ChartCore-ivzLqxVb.js +0 -8350
  267. package/dist/ChartCore-ivzLqxVb.js.map +0 -1
  268. package/dist/SciPlot-CEU4ApgD.js +0 -463
  269. package/dist/SciPlot-CEU4ApgD.js.map +0 -1
  270. package/dist/createStackedChart-DxNW0VkF.js +0 -705
  271. package/dist/createStackedChart-DxNW0VkF.js.map +0 -1
  272. package/dist/gpuRenderer-BpaMYz-k.js +0 -1848
  273. package/dist/gpuRenderer-BpaMYz-k.js.map +0 -1
  274. package/dist/index-BBpJwOct.js.map +0 -1
  275. package/dist/index-BMiZoKmm.js.map +0 -1
  276. package/dist/index-DoYxf1cV.js.map +0 -1
  277. package/dist/index-qunX30Xu.js.map +0 -1
  278. package/dist/index-x4stH3jD.js +0 -282
  279. package/dist/index-x4stH3jD.js.map +0 -1
  280. package/dist/index.core-COREBDNq.js.map +0 -1
  281. package/dist/mockDatafeed-927foCM4.js.map +0 -1
  282. package/dist/pool-15HMCNv8.js +0 -88
  283. package/dist/pool-15HMCNv8.js.map +0 -1
  284. package/dist/react/SciPlot.d.ts +0 -60
  285. package/dist/react/useSciPlot.d.ts +0 -56
  286. package/dist/symbols-DT0eLlpW.js +0 -568
  287. package/dist/symbols-DT0eLlpW.js.map +0 -1
@@ -1,2393 +1,2393 @@
1
1
  var dt = Object.defineProperty;
2
2
  var ct = (c, t, i) => t in c ? dt(c, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : c[t] = i;
3
3
  var u = (c, t, i) => ct(c, typeof t != "symbol" ? t + "" : t, i);
4
- import { d as O, e as N, E as ut } from "./axisFormat-CzumxpCL.js";
5
- class ft {
6
- constructor(t, i) {
7
- u(this, "ctx");
8
- u(this, "options");
9
- u(this, "state", {
10
- enabled: !1,
11
- selectionState: "idle",
12
- point1: null,
13
- point2: null,
14
- hoveredPoint: null,
15
- lastMeasurement: null
16
- });
17
- u(this, "overlayCanvas", null);
18
- u(this, "overlayCtx", null);
19
- // Label dragging
20
- u(this, "labelOffset", { x: 15, y: 0 });
21
- u(this, "isDraggingLabel", !1);
22
- u(this, "labelBounds", null);
23
- u(this, "dragStart", { x: 0, y: 0 });
24
- u(this, "crosshairPosition", null);
25
- // Bound handlers
26
- u(this, "boundMouseMove");
27
- u(this, "boundClick");
28
- u(this, "boundKeyDown");
29
- u(this, "boundMouseDown");
30
- u(this, "boundMouseUp");
31
- u(this, "boundResize");
32
- this.ctx = t, this.options = {
33
- lineColor: "#ff6b6b",
34
- lineWidth: 2,
35
- showLabels: !0,
36
- labelFontSize: 12,
37
- labelBackground: "rgba(0, 0, 0, 0.85)",
38
- labelColor: "#ffffff",
39
- showDelta: !0,
40
- precision: 4,
41
- showSlope: !0,
42
- showDistance: !0,
43
- className: "velo-plot-delta-tool",
44
- highlightSize: 12,
45
- highlightColor: "#00f2ff",
46
- snapRadius: 30,
47
- ...i
48
- }, this.boundMouseMove = this.handleMouseMove.bind(this), this.boundClick = this.handleClick.bind(this), this.boundKeyDown = this.handleKeyDown.bind(this), this.boundMouseDown = this.handleLabelMouseDown.bind(this), this.boundMouseUp = this.handleLabelMouseUp.bind(this), this.boundResize = this.resizeOverlay.bind(this);
49
- }
50
- /**
51
- * Enable the delta measurement tool
52
- */
53
- enable() {
54
- this.state.enabled || (this.state.enabled = !0, this.state.selectionState = "idle", this.createOverlay(), this.attachListeners(), this.ctx.container.style.cursor = "crosshair");
55
- }
56
- /**
57
- * Disable the delta measurement tool
58
- */
59
- disable() {
60
- this.state.enabled && (this.state.enabled = !1, this.state.selectionState = "idle", this.state.point1 = null, this.state.point2 = null, this.state.hoveredPoint = null, this.detachListeners(), this.destroyOverlay(), this.ctx.container.style.cursor = "");
61
- }
62
- /**
63
- * Toggle the tool on/off
64
- */
65
- toggle() {
66
- this.state.enabled ? this.disable() : this.enable();
67
- }
68
- /**
69
- * Check if tool is enabled
70
- */
71
- isEnabled() {
72
- return this.state.enabled;
73
- }
74
- /**
75
- * Get the current state
76
- */
77
- getState() {
78
- return { ...this.state };
79
- }
80
- /**
81
- * Get the last completed measurement
82
- */
83
- getMeasurement() {
84
- return this.state.lastMeasurement;
85
- }
86
- /**
87
- * Clear the current measurement
88
- */
89
- clear() {
90
- this.state.point1 = null, this.state.point2 = null, this.state.selectionState = "idle", this.state.lastMeasurement = null, this.labelBounds = null, this.labelOffset = { x: 15, y: 0 }, this.renderOverlay();
91
- }
92
- /**
93
- * Destroy the tool and cleanup
94
- */
95
- destroy() {
96
- this.disable(), this.state.lastMeasurement = null;
97
- }
98
- /**
99
- * Recalculate measurements (useful if data changes)
100
- */
101
- recalculate() {
102
- if (!this.state.point1 || !this.state.point2) return;
103
- const t = this.ctx.getSeries ? this.ctx.getSeries() : [], i = (s) => {
104
- const o = t.find((l) => l.id === s.seriesId);
105
- if (o && s.index < o.x.length) {
106
- s.x = o.x[s.index], s.y = o.y[s.index], s.yAxisId = o.yAxisId;
107
- const l = this.ctx.getPlotArea(), h = this.ctx.getViewBounds(), a = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: h.yMin, yMax: h.yMax };
108
- return s.pixelX = l.x + (s.x - h.xMin) / (h.xMax - h.xMin) * l.width, s.pixelY = l.y + (1 - (s.y - a.yMin) / (a.yMax - a.yMin)) * l.height, !0;
109
- }
110
- return !1;
111
- }, e = i(this.state.point1), n = i(this.state.point2);
112
- e && n ? (this.state.lastMeasurement = this.calculateMeasurement(), this.ctx.onMeasure && this.ctx.onMeasure(this.state.lastMeasurement)) : this.clear(), this.renderOverlay();
113
- }
114
- // ============================================
115
- // Private Methods
116
- // ============================================
117
- createOverlay() {
118
- this.overlayCanvas || (this.overlayCanvas = document.createElement("canvas"), this.overlayCanvas.className = this.options.className, this.overlayCanvas.style.cssText = `
119
- position: absolute;
120
- top: 0;
121
- left: 0;
122
- width: 100%;
123
- height: 100%;
124
- pointer-events: none;
125
- z-index: 1000;
126
- `, this.resizeOverlay(), this.overlayCtx = this.overlayCanvas.getContext("2d"), this.ctx.container.appendChild(this.overlayCanvas));
127
- }
128
- resizeOverlay() {
129
- if (!this.overlayCanvas) return;
130
- const t = this.ctx.container.getBoundingClientRect(), i = window.devicePixelRatio || 1;
131
- this.overlayCanvas.width = t.width * i, this.overlayCanvas.height = t.height * i, this.overlayCtx && (this.overlayCtx.setTransform(1, 0, 0, 1, 0, 0), this.overlayCtx.scale(i, i));
132
- }
133
- destroyOverlay() {
134
- this.overlayCanvas && this.overlayCanvas.parentNode && this.overlayCanvas.parentNode.removeChild(this.overlayCanvas), this.overlayCanvas = null, this.overlayCtx = null;
135
- }
136
- attachListeners() {
137
- this.ctx.container.addEventListener("mousemove", this.boundMouseMove, !0), this.ctx.container.addEventListener("mousedown", this.boundClick, !0), this.ctx.container.addEventListener("mousedown", this.boundMouseDown, !0), this.ctx.container.addEventListener("mouseup", this.boundMouseUp, !0), window.addEventListener("resize", this.boundResize), document.addEventListener("keydown", this.boundKeyDown);
138
- }
139
- detachListeners() {
140
- this.ctx.container.removeEventListener("mousemove", this.boundMouseMove, !0), this.ctx.container.removeEventListener("mousedown", this.boundClick, !0), this.ctx.container.removeEventListener("mousedown", this.boundMouseDown, !0), this.ctx.container.removeEventListener("mouseup", this.boundMouseUp, !0), window.removeEventListener("resize", this.boundResize), document.removeEventListener("keydown", this.boundKeyDown);
141
- }
142
- handleMouseMove(t) {
143
- if (!this.state.enabled) return;
144
- const i = this.ctx.container.getBoundingClientRect(), e = this.ctx.getPlotArea(), n = this.ctx.getViewBounds(), s = t.clientX - i.left, o = t.clientY - i.top;
145
- if (this.isDraggingLabel) {
146
- this.labelOffset.x += s - this.dragStart.x, this.labelOffset.y += o - this.dragStart.y, this.dragStart = { x: s, y: o }, this.renderOverlay();
147
- return;
148
- }
149
- if (this.labelBounds && s >= this.labelBounds.x && s <= this.labelBounds.x + this.labelBounds.width && o >= this.labelBounds.y && o <= this.labelBounds.y + this.labelBounds.height ? this.ctx.container.style.cursor = "move" : this.ctx.container.style.cursor = "crosshair", s < e.x || s > e.x + e.width || o < e.y || o > e.y + e.height) {
150
- this.state.hoveredPoint = null, this.crosshairPosition = null, this.renderOverlay();
151
- return;
152
- }
153
- this.crosshairPosition = { x: s, y: o };
154
- const l = this.findNearestPoint(s, o, e, n);
155
- this.state.hoveredPoint = l, this.renderOverlay();
156
- }
157
- handleLabelMouseDown(t) {
158
- if (!this.state.enabled || !this.labelBounds) return;
159
- const i = this.ctx.container.getBoundingClientRect(), e = t.clientX - i.left, n = t.clientY - i.top;
160
- e >= this.labelBounds.x && e <= this.labelBounds.x + this.labelBounds.width && n >= this.labelBounds.y && n <= this.labelBounds.y + this.labelBounds.height && (this.isDraggingLabel = !0, this.dragStart = { x: e, y: n }, t.stopPropagation(), t.preventDefault());
161
- }
162
- handleLabelMouseUp() {
163
- this.isDraggingLabel = !1;
164
- }
165
- async handleClick(t) {
166
- var h;
167
- if (!this.state.enabled) return;
168
- (h = this.ctx.animationEngine) != null && h.isAnimating() && await this.ctx.animationEngine.waitForIdle(), t.stopPropagation(), t.preventDefault();
169
- const i = this.ctx.container.getBoundingClientRect(), e = this.ctx.getPlotArea(), n = this.ctx.getViewBounds(), s = t.clientX - i.left, o = t.clientY - i.top;
170
- if (s < e.x || s > e.x + e.width || o < e.y || o > e.y + e.height)
171
- return;
172
- const l = this.findNearestPoint(s, o, e, n);
173
- if (l) {
174
- if (t.preventDefault(), t.stopPropagation(), this.state.selectionState === "idle" || this.state.selectionState === "complete")
175
- this.state.point1 = l, this.state.point2 = null, this.state.selectionState = "waitingSecond", this.state.lastMeasurement = null;
176
- else if (this.state.selectionState === "waitingSecond") {
177
- this.state.point2 = l, this.state.selectionState = "complete";
178
- const a = this.calculateMeasurement();
179
- this.state.lastMeasurement = a, this.ctx.onMeasure && this.ctx.onMeasure(a);
180
- }
181
- this.renderOverlay();
182
- }
183
- }
184
- handleKeyDown(t) {
185
- t.key === "Escape" && this.state.enabled && this.clear();
186
- }
187
- findNearestPoint(t, i, e, n) {
188
- if (!this.ctx.getSeries) return null;
189
- const s = this.ctx.getSeries();
190
- let o = null, l = this.options.snapRadius;
191
- for (const h of s) {
192
- const a = h.x, r = h.y, d = Math.min(a.length, r.length), f = this.ctx.getYBounds ? this.ctx.getYBounds(h.yAxisId) : { yMin: n.yMin, yMax: n.yMax };
193
- for (let p = 0; p < d; p++) {
194
- const g = a[p], y = r[p], b = e.x + (g - n.xMin) / (n.xMax - n.xMin) * e.width, x = e.y + (1 - (y - f.yMin) / (f.yMax - f.yMin)) * e.height;
195
- if (b < e.x || b > e.x + e.width || x < e.y || x > e.y + e.height) continue;
196
- const S = Math.sqrt((t - b) ** 2 + (i - x) ** 2);
197
- S < l && (l = S, o = {
198
- x: g,
199
- y,
200
- seriesId: h.id,
201
- yAxisId: h.yAxisId,
202
- index: p,
203
- pixelX: b,
204
- pixelY: x
205
- });
206
- }
207
- }
208
- return o;
209
- }
210
- calculateMeasurement() {
211
- const t = this.state.point1, i = this.state.point2, e = i.x - t.x, n = i.y - t.y, s = e !== 0 ? n / e : 1 / 0, o = Math.sqrt(e * e + n * n);
212
- return {
213
- x1: t.x,
214
- y1: t.y,
215
- x2: i.x,
216
- y2: i.y,
217
- deltaX: e,
218
- deltaY: n,
219
- slope: s,
220
- distance: o,
221
- pixelX1: t.pixelX,
222
- pixelY1: t.pixelY,
223
- pixelX2: i.pixelX,
224
- pixelY2: i.pixelY
225
- };
226
- }
227
- renderOverlay() {
228
- if (!this.overlayCtx || !this.overlayCanvas) return;
229
- this.resizeOverlay();
230
- const t = this.ctx.container.getBoundingClientRect();
231
- this.overlayCtx.clearRect(0, 0, t.width, t.height);
232
- const i = this.options, e = this.overlayCtx, n = this.ctx.getPlotArea(), s = this.ctx.getViewBounds();
233
- if (this.state.point1) {
234
- const o = this.state.point1, l = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: s.yMin, yMax: s.yMax };
235
- o.pixelX = n.x + (o.x - s.xMin) / (s.xMax - s.xMin) * n.width, o.pixelY = n.y + (1 - (o.y - l.yMin) / (l.yMax - l.yMin)) * n.height;
236
- }
237
- if (this.state.point2) {
238
- const o = this.state.point2, l = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: s.yMin, yMax: s.yMax };
239
- o.pixelX = n.x + (o.x - s.xMin) / (s.xMax - s.xMin) * n.width, o.pixelY = n.y + (1 - (o.y - l.yMin) / (l.yMax - l.yMin)) * n.height;
240
- }
241
- if (this.crosshairPosition && this.state.selectionState !== "complete") {
242
- const o = this.crosshairPosition.x, l = this.crosshairPosition.y;
243
- e.save(), e.setLineDash([4, 4]), e.strokeStyle = "rgba(150, 150, 150, 0.7)", e.lineWidth = 1, e.beginPath(), e.moveTo(o, n.y), e.lineTo(o, n.y + n.height), e.stroke(), e.beginPath(), e.moveTo(n.x, l), e.lineTo(n.x + n.width, l), e.stroke(), e.restore();
244
- }
245
- if (this.state.hoveredPoint && this.state.selectionState !== "complete") {
246
- const o = this.state.hoveredPoint;
247
- e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize + 4, 0, Math.PI * 2), e.fillStyle = i.highlightColor + "30", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.strokeStyle = i.highlightColor, e.lineWidth = 2, e.stroke(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 4, 0, Math.PI * 2), e.fillStyle = i.highlightColor, e.fill(), this.drawPointTooltip(e, o, "Hover");
248
- }
249
- if (this.state.point1) {
250
- const o = this.state.point1;
251
- e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.fillStyle = i.lineColor, e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize - 3, 0, Math.PI * 2), e.fillStyle = "#ffffff", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 3, 0, Math.PI * 2), e.fillStyle = i.lineColor, e.fill(), this.drawPointLabel(e, o.pixelX, o.pixelY - i.highlightSize - 8, "P1", i.lineColor);
252
- }
253
- if (this.state.point2) {
254
- const o = this.state.point2;
255
- e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.fillStyle = "#4ade80", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize - 3, 0, Math.PI * 2), e.fillStyle = "#ffffff", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 3, 0, Math.PI * 2), e.fillStyle = "#4ade80", e.fill(), this.drawPointLabel(e, o.pixelX, o.pixelY - i.highlightSize - 8, "P2", "#4ade80");
256
- }
257
- if (this.state.point1 && this.state.point2) {
258
- const o = this.state.point1, l = this.state.point2;
259
- e.beginPath(), e.moveTo(o.pixelX, o.pixelY), e.lineTo(l.pixelX, l.pixelY), e.strokeStyle = i.lineColor, e.lineWidth = i.lineWidth, e.setLineDash([6, 4]), e.stroke(), e.setLineDash([]), e.beginPath(), e.moveTo(o.pixelX, o.pixelY), e.lineTo(l.pixelX, o.pixelY), e.lineTo(l.pixelX, l.pixelY), e.strokeStyle = "rgba(255, 107, 107, 0.4)", e.lineWidth = 1, e.setLineDash([3, 3]), e.stroke(), e.setLineDash([]), i.showLabels && this.state.lastMeasurement && this.drawMeasurementLabel(e, o, l, this.state.lastMeasurement);
260
- }
261
- this.drawStatusIndicator(e);
262
- }
263
- drawPointTooltip(t, i, e) {
264
- const n = this.options, s = `${e}: (${this.formatValue(i.x)}, ${this.formatValue(i.y)})`;
265
- t.font = `${n.labelFontSize}px system-ui, sans-serif`;
266
- const o = t.measureText(s), l = 6, h = o.width + l * 2, a = n.labelFontSize + l * 2;
267
- let r = i.pixelX + 15, d = i.pixelY - a / 2;
268
- const f = this.ctx.container.getBoundingClientRect();
269
- r + h > f.width && (r = i.pixelX - h - 15), d < 0 && (d = 5), d + a > f.height && (d = f.height - a - 5), t.fillStyle = n.labelBackground, this.roundRect(t, r, d, h, a, 4), t.fill(), t.fillStyle = n.labelColor, t.textAlign = "left", t.textBaseline = "middle", t.fillText(s, r + l, d + a / 2);
270
- }
271
- drawPointLabel(t, i, e, n, s) {
272
- const o = this.options;
273
- t.font = `bold ${o.labelFontSize}px system-ui, sans-serif`, t.textAlign = "center", t.textBaseline = "bottom";
274
- const l = t.measureText(n), h = 4, a = l.width + h * 2, r = o.labelFontSize + h;
275
- t.fillStyle = s, this.roundRect(t, i - a / 2, e - r, a, r, 3), t.fill(), t.fillStyle = "#ffffff", t.fillText(n, i, e - 2);
276
- }
277
- drawMeasurementLabel(t, i, e, n) {
278
- const s = this.options, o = (i.pixelX + e.pixelX) / 2, l = (i.pixelY + e.pixelY) / 2, h = [];
279
- s.showDelta && (h.push(`ΔX: ${this.formatValue(n.deltaX)}`), h.push(`ΔY: ${this.formatValue(n.deltaY)}`)), s.showSlope && isFinite(n.slope) && h.push(`Slope: ${this.formatValue(n.slope)}`), s.showDistance && h.push(`Distance: ${this.formatValue(n.distance)}`);
280
- const a = s.labelFontSize + 4, r = 10, d = 170, f = h.length * a + r * 2;
281
- let p = o + this.labelOffset.x, g = l + this.labelOffset.y;
282
- const y = this.ctx.container.getBoundingClientRect();
283
- p < 0 && (p = 0), p + d > y.width && (p = y.width - d), g < 0 && (g = 0), g + f > y.height && (g = y.height - f), this.labelBounds = { x: p, y: g, width: d, height: f }, t.fillStyle = s.labelBackground, t.strokeStyle = s.lineColor, t.lineWidth = 2, this.roundRect(t, p, g, d, f, 8), t.fill(), t.stroke(), t.fillStyle = s.labelColor, t.font = `${s.labelFontSize}px system-ui, sans-serif`, t.textAlign = "left", t.textBaseline = "top", h.forEach((b, x) => {
284
- t.fillText(b, p + r, g + r + x * a);
285
- });
286
- }
287
- drawStatusIndicator(t) {
288
- const i = this.options, e = this.ctx.getPlotArea();
289
- let n = "", s = "#666";
290
- if (this.state.selectionState === "idle" ? (n = "📏 Click a point to start measuring", s = i.highlightColor) : this.state.selectionState === "waitingSecond" ? (n = "📏 Click second point to complete", s = "#f59e0b") : this.state.selectionState === "complete" && (n = "📏 Measurement complete (ESC to clear)", s = "#4ade80"), !n) return;
291
- t.font = "12px system-ui, sans-serif";
292
- const h = t.measureText(n).width + 8 * 2, a = 24, r = e.x + 10, d = e.y + 10;
293
- t.fillStyle = "rgba(0, 0, 0, 0.75)", this.roundRect(t, r, d, h, a, 4), t.fill(), t.beginPath(), t.arc(r + 16, d + a / 2, 4, 0, Math.PI * 2), t.fillStyle = s, t.fill(), t.fillStyle = "#fff", t.textAlign = "left", t.textBaseline = "middle", t.fillText(n, r + 26, d + a / 2);
294
- }
295
- roundRect(t, i, e, n, s, o) {
296
- t.beginPath(), t.moveTo(i + o, e), t.lineTo(i + n - o, e), t.quadraticCurveTo(i + n, e, i + n, e + o), t.lineTo(i + n, e + s - o), t.quadraticCurveTo(i + n, e + s, i + n - o, e + s), t.lineTo(i + o, e + s), t.quadraticCurveTo(i, e + s, i, e + s - o), t.lineTo(i, e + o), t.quadraticCurveTo(i, e, i + o, e), t.closePath();
297
- }
298
- formatValue(t) {
299
- const i = Math.abs(t);
300
- return i === 0 ? "0" : i >= 1e6 || i < 1e-3 ? t.toExponential(this.options.precision - 1) : t.toPrecision(this.options.precision);
301
- }
4
+ import { d as O, e as N, E as ut } from "./axisFormat-Sz4WIJfQ.js";
5
+ const V = {
6
+ top: 10,
7
+ right: 12,
8
+ bottom: 10,
9
+ left: 12
10
+ }, L = {
11
+ // Container
12
+ backgroundColor: "rgba(15, 18, 25, 0.95)",
13
+ backgroundGradient: "rgba(25, 30, 40, 0.90)",
14
+ borderColor: "rgba(255, 255, 255, 0.12)",
15
+ borderWidth: 1,
16
+ borderRadius: 8,
17
+ backdropBlur: 12,
18
+ shadow: {
19
+ color: "rgba(0, 0, 0, 0.4)",
20
+ offsetX: 0,
21
+ offsetY: 4,
22
+ blur: 16
23
+ },
24
+ // Typography
25
+ fontFamily: "Inter, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
26
+ textColor: "#ffffff",
27
+ textSecondaryColor: "rgba(255, 255, 255, 0.6)",
28
+ titleFontSize: 12,
29
+ titleFontWeight: 600,
30
+ contentFontSize: 11,
31
+ lineHeight: 1.4,
32
+ // Spacing
33
+ padding: V,
34
+ itemGap: 4,
35
+ headerGap: 8,
36
+ // Decorations
37
+ showSeriesIndicator: !0,
38
+ seriesIndicatorSize: 8,
39
+ showHeaderSeparator: !0,
40
+ separatorColor: "rgba(255, 255, 255, 0.1)",
41
+ showArrow: !0,
42
+ arrowSize: 6
43
+ }, et = {
44
+ // Container
45
+ backgroundColor: "rgba(255, 255, 255, 0.98)",
46
+ borderColor: "rgba(0, 0, 0, 0.12)",
47
+ borderWidth: 1,
48
+ borderRadius: 8,
49
+ backdropBlur: 8,
50
+ shadow: {
51
+ color: "rgba(0, 0, 0, 0.12)",
52
+ offsetX: 0,
53
+ offsetY: 4,
54
+ blur: 12
55
+ },
56
+ // Typography
57
+ fontFamily: "Inter, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
58
+ textColor: "#1a1a1a",
59
+ textSecondaryColor: "rgba(0, 0, 0, 0.55)",
60
+ titleFontSize: 12,
61
+ titleFontWeight: 600,
62
+ contentFontSize: 11,
63
+ lineHeight: 1.4,
64
+ // Spacing
65
+ padding: V,
66
+ itemGap: 4,
67
+ headerGap: 8,
68
+ // Decorations
69
+ showSeriesIndicator: !0,
70
+ seriesIndicatorSize: 8,
71
+ showHeaderSeparator: !0,
72
+ separatorColor: "rgba(0, 0, 0, 0.08)",
73
+ showArrow: !0,
74
+ arrowSize: 6
75
+ }, ft = {
76
+ // Container
77
+ backgroundColor: "rgba(255, 255, 255, 0.08)",
78
+ backgroundGradient: "rgba(255, 255, 255, 0.04)",
79
+ borderColor: "rgba(255, 255, 255, 0.2)",
80
+ borderWidth: 1,
81
+ borderRadius: 12,
82
+ backdropBlur: 20,
83
+ shadow: {
84
+ color: "rgba(0, 0, 0, 0.3)",
85
+ offsetX: 0,
86
+ offsetY: 8,
87
+ blur: 32
88
+ },
89
+ // Typography
90
+ fontFamily: "Inter, system-ui, sans-serif",
91
+ textColor: "rgba(255, 255, 255, 0.95)",
92
+ textSecondaryColor: "rgba(255, 255, 255, 0.6)",
93
+ titleFontSize: 12,
94
+ titleFontWeight: 600,
95
+ contentFontSize: 11,
96
+ lineHeight: 1.5,
97
+ // Spacing
98
+ padding: { top: 12, right: 14, bottom: 12, left: 14 },
99
+ itemGap: 5,
100
+ headerGap: 10,
101
+ // Decorations
102
+ showSeriesIndicator: !0,
103
+ seriesIndicatorSize: 8,
104
+ showHeaderSeparator: !0,
105
+ separatorColor: "rgba(255, 255, 255, 0.15)",
106
+ showArrow: !0,
107
+ arrowSize: 7
108
+ }, it = {
109
+ // Container
110
+ backgroundColor: "rgba(10, 15, 30, 0.95)",
111
+ backgroundGradient: "rgba(20, 25, 50, 0.90)",
112
+ borderColor: "rgba(100, 120, 255, 0.25)",
113
+ borderWidth: 1,
114
+ borderRadius: 8,
115
+ backdropBlur: 15,
116
+ shadow: {
117
+ color: "rgba(50, 70, 150, 0.25)",
118
+ offsetX: 0,
119
+ offsetY: 4,
120
+ blur: 20
121
+ },
122
+ // Typography
123
+ fontFamily: "Inter, system-ui, sans-serif",
124
+ textColor: "#e0e8ff",
125
+ textSecondaryColor: "rgba(180, 190, 255, 0.6)",
126
+ titleFontSize: 12,
127
+ titleFontWeight: 600,
128
+ contentFontSize: 11,
129
+ lineHeight: 1.4,
130
+ // Spacing
131
+ padding: V,
132
+ itemGap: 4,
133
+ headerGap: 8,
134
+ // Decorations
135
+ showSeriesIndicator: !0,
136
+ seriesIndicatorSize: 8,
137
+ showHeaderSeparator: !0,
138
+ separatorColor: "rgba(100, 120, 255, 0.15)",
139
+ showArrow: !0,
140
+ arrowSize: 6
141
+ }, st = {
142
+ // Container
143
+ backgroundColor: "rgba(8, 20, 40, 0.96)",
144
+ backgroundGradient: "rgba(15, 35, 60, 0.92)",
145
+ borderColor: "rgba(30, 136, 229, 0.35)",
146
+ borderWidth: 1,
147
+ borderRadius: 6,
148
+ backdropBlur: 10,
149
+ shadow: {
150
+ color: "rgba(30, 136, 229, 0.2)",
151
+ offsetX: 0,
152
+ offsetY: 4,
153
+ blur: 16
154
+ },
155
+ // Typography
156
+ fontFamily: '"JetBrains Mono", "Fira Code", monospace',
157
+ textColor: "#bbdefb",
158
+ textSecondaryColor: "rgba(144, 202, 249, 0.7)",
159
+ titleFontSize: 11,
160
+ titleFontWeight: 600,
161
+ contentFontSize: 11,
162
+ lineHeight: 1.5,
163
+ // Spacing
164
+ padding: { top: 8, right: 10, bottom: 8, left: 10 },
165
+ itemGap: 3,
166
+ headerGap: 6,
167
+ // Decorations
168
+ showSeriesIndicator: !0,
169
+ seriesIndicatorSize: 6,
170
+ showHeaderSeparator: !0,
171
+ separatorColor: "rgba(30, 136, 229, 0.2)",
172
+ showArrow: !0,
173
+ arrowSize: 5
174
+ }, pt = {
175
+ // Container
176
+ backgroundColor: "rgba(0, 0, 0, 0.92)",
177
+ borderColor: "#00ffff",
178
+ borderWidth: 1,
179
+ borderRadius: 4,
180
+ backdropBlur: 0,
181
+ shadow: {
182
+ color: "rgba(0, 255, 255, 0.35)",
183
+ offsetX: 0,
184
+ offsetY: 0,
185
+ blur: 20
186
+ },
187
+ // Typography
188
+ fontFamily: '"Orbitron", "Exo 2", sans-serif',
189
+ textColor: "#00ffff",
190
+ textSecondaryColor: "rgba(0, 255, 255, 0.6)",
191
+ titleFontSize: 11,
192
+ titleFontWeight: 700,
193
+ contentFontSize: 10,
194
+ lineHeight: 1.4,
195
+ // Spacing
196
+ padding: { top: 8, right: 12, bottom: 8, left: 12 },
197
+ itemGap: 4,
198
+ headerGap: 6,
199
+ // Decorations
200
+ showSeriesIndicator: !0,
201
+ seriesIndicatorSize: 6,
202
+ showHeaderSeparator: !0,
203
+ separatorColor: "rgba(0, 255, 255, 0.3)",
204
+ showArrow: !1,
205
+ arrowSize: 0
206
+ }, gt = {
207
+ // Container
208
+ backgroundColor: "rgba(30, 35, 45, 0.9)",
209
+ borderColor: "transparent",
210
+ borderWidth: 0,
211
+ borderRadius: 4,
212
+ backdropBlur: 8,
213
+ shadow: {
214
+ color: "rgba(0, 0, 0, 0.3)",
215
+ offsetX: 0,
216
+ offsetY: 2,
217
+ blur: 8
218
+ },
219
+ // Typography
220
+ fontFamily: "Inter, system-ui, sans-serif",
221
+ textColor: "#ffffff",
222
+ textSecondaryColor: "rgba(255, 255, 255, 0.7)",
223
+ titleFontSize: 10,
224
+ titleFontWeight: 500,
225
+ contentFontSize: 10,
226
+ lineHeight: 1.3,
227
+ // Spacing
228
+ padding: { top: 6, right: 8, bottom: 6, left: 8 },
229
+ itemGap: 2,
230
+ headerGap: 4,
231
+ // Decorations
232
+ showSeriesIndicator: !1,
233
+ seriesIndicatorSize: 0,
234
+ showHeaderSeparator: !1,
235
+ separatorColor: "transparent",
236
+ showArrow: !1,
237
+ arrowSize: 0
238
+ }, yt = {
239
+ dark: L,
240
+ light: et,
241
+ glass: ft,
242
+ midnight: it,
243
+ electrochemistry: st,
244
+ neon: pt,
245
+ minimal: gt
246
+ };
247
+ function xt(c) {
248
+ const t = yt[c];
249
+ return t || (console.warn(`[Tooltip] Unknown theme "${c}", using dark`), L);
302
250
  }
303
- class pt {
304
- constructor(t, i) {
305
- u(this, "ctx");
306
- u(this, "options");
307
- u(this, "state", {
308
- enabled: !1,
309
- selectionState: "idle",
310
- point1: null,
311
- point2: null,
312
- hoveredPoint: null,
313
- lastMeasurement: null
314
- });
315
- u(this, "overlayCanvas", null);
316
- u(this, "overlayCtx", null);
317
- // Label dragging
318
- u(this, "labelOffset", { x: 15, y: 0 });
319
- u(this, "isDraggingLabel", !1);
320
- u(this, "labelBounds", null);
321
- u(this, "dragStart", { x: 0, y: 0 });
322
- u(this, "crosshairPosition", null);
323
- // Bound handlers
324
- u(this, "boundMouseMove");
325
- u(this, "boundClick");
326
- u(this, "boundKeyDown");
327
- u(this, "boundMouseDown");
328
- u(this, "boundMouseUp");
329
- u(this, "boundResize");
330
- this.ctx = t, this.options = {
331
- lineColor: "#ff6b6b",
332
- lineWidth: 2,
333
- showLabels: !0,
334
- labelFontSize: 12,
335
- labelBackground: "rgba(0, 0, 0, 0.85)",
336
- labelColor: "#ffffff",
337
- showPeak: !0,
338
- precision: 4,
339
- className: "velo-plot-peak-tool",
340
- highlightSize: 12,
341
- highlightColor: "#00f2ff",
342
- snapRadius: 30,
343
- baselineWindow: 10,
344
- areaFill: "rgba(255, 107, 107, 0.3)",
345
- ...i
346
- }, this.boundMouseMove = this.handleMouseMove.bind(this), this.boundClick = this.handleClick.bind(this), this.boundKeyDown = this.handleKeyDown.bind(this), this.boundMouseDown = this.handleLabelMouseDown.bind(this), this.boundMouseUp = this.handleLabelMouseUp.bind(this), this.boundResize = this.resizeOverlay.bind(this);
347
- }
348
- /**
349
- * Enable the peak measurement tool
350
- */
351
- enable() {
352
- this.state.enabled || (this.state.enabled = !0, this.state.selectionState = "idle", this.createOverlay(), this.attachListeners(), this.ctx.container.style.cursor = "crosshair");
251
+ function bt(c, t) {
252
+ const i = typeof c == "string" ? xt(c) : c;
253
+ return {
254
+ ...i,
255
+ ...t,
256
+ padding: { ...i.padding, ...t.padding },
257
+ shadow: { ...i.shadow, ...t.shadow }
258
+ };
259
+ }
260
+ function B(c) {
261
+ switch (c.toLowerCase()) {
262
+ case "light":
263
+ return et;
264
+ case "midnight":
265
+ return it;
266
+ case "electrochemistry":
267
+ case "electrochem":
268
+ return st;
269
+ case "dark":
270
+ default:
271
+ return L;
353
272
  }
354
- /**
355
- * Disable the peak measurement tool
356
- */
357
- disable() {
358
- this.state.enabled && (this.state.enabled = !1, this.state.selectionState = "idle", this.state.point1 = null, this.state.point2 = null, this.state.hoveredPoint = null, this.detachListeners(), this.destroyOverlay(), this.ctx.container.style.cursor = "");
273
+ }
274
+ const St = L, mt = {
275
+ offset: { x: 12, y: 12 },
276
+ preferredPosition: "auto",
277
+ constrainToPlotArea: !0,
278
+ constrainToContainer: !0,
279
+ autoFlip: !0,
280
+ showArrow: !0,
281
+ arrowSize: 6
282
+ };
283
+ class Tt {
284
+ constructor(t = {}) {
285
+ u(this, "config");
286
+ u(this, "containerWidth", 0);
287
+ u(this, "containerHeight", 0);
288
+ u(this, "plotArea", null);
289
+ this.config = { ...mt, ...t };
359
290
  }
360
291
  /**
361
- * Toggle the tool on/off
292
+ * Update container dimensions
362
293
  */
363
- toggle() {
364
- this.state.enabled ? this.disable() : this.enable();
294
+ setContainerSize(t, i) {
295
+ this.containerWidth = t, this.containerHeight = i;
365
296
  }
366
297
  /**
367
- * Check if tool is enabled
298
+ * Update plot area bounds
368
299
  */
369
- isEnabled() {
370
- return this.state.enabled;
300
+ setPlotArea(t) {
301
+ this.plotArea = t;
371
302
  }
372
303
  /**
373
- * Get the current state
304
+ * Update configuration
374
305
  */
375
- getState() {
376
- return { ...this.state };
306
+ configure(t) {
307
+ this.config = { ...this.config, ...t };
377
308
  }
378
309
  /**
379
- * Get the last completed measurement
310
+ * Calculate optimal tooltip position
311
+ *
312
+ * @param targetX - Target X coordinate (where tooltip points to)
313
+ * @param targetY - Target Y coordinate
314
+ * @param measurement - Tooltip dimensions
315
+ * @returns Calculated position with arrow configuration
380
316
  */
381
- getMeasurement() {
382
- return this.state.lastMeasurement;
317
+ calculatePosition(t, i, e) {
318
+ const { offset: n, preferredPosition: s, autoFlip: o, showArrow: l, arrowSize: h } = this.config, a = e.width + e.padding.left + e.padding.right, r = e.height + e.padding.top + e.padding.bottom, d = this.determinePosition(
319
+ t,
320
+ i,
321
+ a,
322
+ r,
323
+ s
324
+ );
325
+ let { x: f, y: p, arrowPosition: g } = this.calculateCoordinates(
326
+ t,
327
+ i,
328
+ a,
329
+ r,
330
+ d,
331
+ n,
332
+ l ? h : 0
333
+ ), y = !1;
334
+ if (o && !this.fitsInBounds(f, p, a, r)) {
335
+ const S = this.flipPosition(d), T = this.calculateCoordinates(
336
+ t,
337
+ i,
338
+ a,
339
+ r,
340
+ S,
341
+ n,
342
+ l ? h : 0
343
+ );
344
+ this.fitsInBounds(T.x, T.y, a, r) && (f = T.x, p = T.y, g = T.arrowPosition, y = !0);
345
+ }
346
+ const b = this.constrainToBounds(f, p, a, r);
347
+ let x = 0;
348
+ if (l) {
349
+ if (g === "top" || g === "bottom") {
350
+ const S = t - b.x;
351
+ x = Math.max(h + 4, Math.min(a - h - 4, S));
352
+ } else if (g === "left" || g === "right") {
353
+ const S = i - b.y;
354
+ x = Math.max(h + 4, Math.min(r - h - 4, S));
355
+ }
356
+ }
357
+ return {
358
+ x: b.x,
359
+ y: b.y,
360
+ arrowPosition: l ? g : "none",
361
+ arrowOffset: x,
362
+ wasFlipped: y
363
+ };
383
364
  }
384
365
  /**
385
- * Clear the current measurement
366
+ * Determine best position based on available space
386
367
  */
387
- clear() {
388
- this.state.point1 = null, this.state.point2 = null, this.state.selectionState = "idle", this.state.lastMeasurement = null, this.labelBounds = null, this.labelOffset = { x: 15, y: 0 }, this.renderOverlay();
368
+ determinePosition(t, i, e, n, s) {
369
+ if (s !== "auto")
370
+ return s;
371
+ const o = this.getConstraintBounds(), l = i - o.top, h = o.bottom - i, a = t - o.left, r = o.right - t, d = l >= n + this.config.offset.y, f = h >= n + this.config.offset.y, p = r >= e + this.config.offset.x, g = a >= e + this.config.offset.x;
372
+ return d && l >= h ? "top" : f ? "bottom" : p && r >= a ? "right" : g ? "left" : [
373
+ { pos: "top", space: l },
374
+ { pos: "bottom", space: h },
375
+ { pos: "right", space: r },
376
+ { pos: "left", space: a }
377
+ ].sort((b, x) => x.space - b.space)[0].pos;
389
378
  }
390
379
  /**
391
- * Destroy the tool and cleanup
380
+ * Calculate tooltip coordinates for a given position
392
381
  */
393
- destroy() {
394
- this.disable(), this.state.lastMeasurement = null;
382
+ calculateCoordinates(t, i, e, n, s, o, l) {
383
+ let h, a, r;
384
+ switch (s) {
385
+ case "top":
386
+ h = t - e / 2, a = i - n - o.y - l, r = "bottom";
387
+ break;
388
+ case "bottom":
389
+ h = t - e / 2, a = i + o.y + l, r = "top";
390
+ break;
391
+ case "right":
392
+ h = t + o.x + l, a = i - n / 2, r = "left";
393
+ break;
394
+ case "left":
395
+ h = t - e - o.x - l, a = i - n / 2, r = "right";
396
+ break;
397
+ }
398
+ return { x: h, y: a, arrowPosition: r };
395
399
  }
396
400
  /**
397
- * Recalculate measurements (useful if data changes)
401
+ * Get the opposite position for flipping
398
402
  */
399
- recalculate() {
400
- if (!this.state.point1 || !this.state.point2) return;
401
- const t = this.ctx.getSeries ? this.ctx.getSeries() : [], i = (s) => {
402
- const o = t.find((l) => l.id === s.seriesId);
403
- if (o && s.index < o.x.length) {
404
- s.x = o.x[s.index], s.y = o.y[s.index], s.yAxisId = o.yAxisId;
405
- const l = this.ctx.getPlotArea(), h = this.ctx.getViewBounds(), a = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: h.yMin, yMax: h.yMax };
406
- return s.pixelX = l.x + (s.x - h.xMin) / (h.xMax - h.xMin) * l.width, s.pixelY = l.y + (1 - (s.y - a.yMin) / (a.yMax - a.yMin)) * l.height, !0;
407
- }
408
- return !1;
409
- }, e = i(this.state.point1), n = i(this.state.point2);
410
- e && n ? (this.state.lastMeasurement = this.calculateMeasurement(), this.ctx.onMeasure && this.ctx.onMeasure(this.state.lastMeasurement)) : this.clear(), this.renderOverlay();
411
- }
412
- // ============================================
413
- // Private Methods
414
- // ============================================
415
- createOverlay() {
416
- this.overlayCanvas || (this.overlayCanvas = document.createElement("canvas"), this.overlayCanvas.className = this.options.className, this.overlayCanvas.style.cssText = `
417
- position: absolute;
418
- top: 0;
419
- left: 0;
420
- width: 100%;
421
- height: 100%;
422
- pointer-events: none;
423
- z-index: 1000;
424
- `, this.resizeOverlay(), this.overlayCtx = this.overlayCanvas.getContext("2d"), this.ctx.container.appendChild(this.overlayCanvas));
425
- }
426
- resizeOverlay() {
427
- if (!this.overlayCanvas) return;
428
- const t = this.ctx.container.getBoundingClientRect(), i = window.devicePixelRatio || 1;
429
- this.overlayCanvas.width = t.width * i, this.overlayCanvas.height = t.height * i, this.overlayCtx && (this.overlayCtx.setTransform(1, 0, 0, 1, 0, 0), this.overlayCtx.scale(i, i));
430
- }
431
- destroyOverlay() {
432
- this.overlayCanvas && this.overlayCanvas.parentNode && this.overlayCanvas.parentNode.removeChild(this.overlayCanvas), this.overlayCanvas = null, this.overlayCtx = null;
433
- }
434
- attachListeners() {
435
- this.ctx.container.addEventListener("mousemove", this.boundMouseMove, !0), this.ctx.container.addEventListener("mousedown", this.boundClick, !0), this.ctx.container.addEventListener("mousedown", this.boundMouseDown, !0), this.ctx.container.addEventListener("mouseup", this.boundMouseUp, !0), window.addEventListener("resize", this.boundResize), document.addEventListener("keydown", this.boundKeyDown);
436
- }
437
- detachListeners() {
438
- this.ctx.container.removeEventListener("mousemove", this.boundMouseMove, !0), this.ctx.container.removeEventListener("mousedown", this.boundClick, !0), this.ctx.container.removeEventListener("mousedown", this.boundMouseDown, !0), this.ctx.container.removeEventListener("mouseup", this.boundMouseUp, !0), window.removeEventListener("resize", this.boundResize), document.removeEventListener("keydown", this.boundKeyDown);
439
- }
440
- handleMouseMove(t) {
441
- if (!this.state.enabled) return;
442
- const i = this.ctx.container.getBoundingClientRect(), e = this.ctx.getPlotArea(), n = this.ctx.getViewBounds(), s = t.clientX - i.left, o = t.clientY - i.top;
443
- if (this.isDraggingLabel) {
444
- this.labelOffset.x += s - this.dragStart.x, this.labelOffset.y += o - this.dragStart.y, this.dragStart = { x: s, y: o }, this.renderOverlay();
445
- return;
446
- }
447
- if (this.labelBounds && s >= this.labelBounds.x && s <= this.labelBounds.x + this.labelBounds.width && o >= this.labelBounds.y && o <= this.labelBounds.y + this.labelBounds.height ? this.ctx.container.style.cursor = "move" : this.ctx.container.style.cursor = "crosshair", s < e.x || s > e.x + e.width || o < e.y || o > e.y + e.height) {
448
- this.state.hoveredPoint = null, this.crosshairPosition = null, this.renderOverlay();
449
- return;
450
- }
451
- this.crosshairPosition = { x: s, y: o };
452
- const l = this.state.selectionState === "waitingSecond" && this.state.point1 ? this.state.point1.seriesId : void 0, h = this.findNearestPoint(s, o, e, n, l);
453
- this.state.hoveredPoint = h, this.renderOverlay();
454
- }
455
- handleLabelMouseDown(t) {
456
- if (!this.state.enabled || !this.labelBounds) return;
457
- const i = this.ctx.container.getBoundingClientRect(), e = t.clientX - i.left, n = t.clientY - i.top;
458
- e >= this.labelBounds.x && e <= this.labelBounds.x + this.labelBounds.width && n >= this.labelBounds.y && n <= this.labelBounds.y + this.labelBounds.height && (this.isDraggingLabel = !0, this.dragStart = { x: e, y: n }, t.stopPropagation(), t.preventDefault());
459
- }
460
- handleLabelMouseUp() {
461
- this.isDraggingLabel = !1;
462
- }
463
- async handleClick(t) {
464
- var a;
465
- if (!this.state.enabled) return;
466
- (a = this.ctx.animationEngine) != null && a.isAnimating() && await this.ctx.animationEngine.waitForIdle(), t.stopPropagation(), t.preventDefault();
467
- const i = this.ctx.container.getBoundingClientRect(), e = this.ctx.getPlotArea(), n = this.ctx.getViewBounds(), s = t.clientX - i.left, o = t.clientY - i.top;
468
- if (s < e.x || s > e.x + e.width || o < e.y || o > e.y + e.height)
469
- return;
470
- const l = this.state.selectionState === "waitingSecond" && this.state.point1 ? this.state.point1.seriesId : void 0, h = this.findNearestPoint(s, o, e, n, l);
471
- if (h) {
472
- if (t.preventDefault(), t.stopPropagation(), this.state.selectionState === "idle" || this.state.selectionState === "complete")
473
- this.state.point1 = h, this.state.point2 = null, this.state.selectionState = "waitingSecond", this.state.lastMeasurement = null;
474
- else if (this.state.selectionState === "waitingSecond") {
475
- if (this.state.point1 && h.seriesId !== this.state.point1.seriesId)
476
- return;
477
- this.state.point2 = h, this.state.selectionState = "complete";
478
- const r = this.calculateMeasurement();
479
- this.state.lastMeasurement = r, this.ctx.onMeasure && this.ctx.onMeasure(r);
480
- }
481
- this.renderOverlay();
482
- }
483
- }
484
- handleKeyDown(t) {
485
- t.key === "Escape" && this.state.enabled && this.clear();
486
- }
487
- findNearestPoint(t, i, e, n, s) {
488
- if (!this.ctx.getSeries) return null;
489
- const o = this.ctx.getSeries();
490
- let l = null, h = this.options.snapRadius;
491
- for (const a of o) {
492
- if (s && a.id !== s) continue;
493
- const r = a.x, d = a.y, f = Math.min(r.length, d.length), p = this.ctx.getYBounds ? this.ctx.getYBounds(a.yAxisId) : { yMin: n.yMin, yMax: n.yMax };
494
- for (let g = 0; g < f; g++) {
495
- const y = r[g], b = d[g], x = e.x + (y - n.xMin) / (n.xMax - n.xMin) * e.width, S = e.y + (1 - (b - p.yMin) / (p.yMax - p.yMin)) * e.height;
496
- if (x < e.x || x > e.x + e.width || S < e.y || S > e.y + e.height) continue;
497
- const T = Math.sqrt((t - x) ** 2 + (i - S) ** 2);
498
- T < h && (h = T, l = {
499
- x: y,
500
- y: b,
501
- seriesId: a.id,
502
- yAxisId: a.yAxisId,
503
- index: g,
504
- pixelX: x,
505
- pixelY: S
506
- });
507
- }
403
+ flipPosition(t) {
404
+ switch (t) {
405
+ case "top":
406
+ return "bottom";
407
+ case "bottom":
408
+ return "top";
409
+ case "left":
410
+ return "right";
411
+ case "right":
412
+ return "left";
508
413
  }
509
- return l;
510
414
  }
511
- calculateMeasurement() {
512
- const t = this.state.point1, i = this.state.point2, n = (this.ctx.getSeries ? this.ctx.getSeries() : []).find((m) => m.id === t.seriesId);
513
- if (!n)
514
- return {
515
- x1: t.x,
516
- y1: t.y,
517
- x2: i.x,
518
- y2: i.y,
519
- area: 0,
520
- integral: 0,
521
- baselineSlope: 0,
522
- baselineIntercept: 0,
523
- peakHeight: 0,
524
- peakX: 0,
525
- peakY: 0,
526
- pixelX1: t.pixelX,
527
- pixelY1: t.pixelY,
528
- pixelX2: i.pixelX,
529
- pixelY2: i.pixelY
530
- };
531
- const s = n.x, o = n.y, l = Math.min(t.index, i.index), h = Math.max(t.index, i.index), a = this.options.baselineWindow, r = [], d = [];
532
- for (let m = Math.max(0, l - Math.floor(a / 2)); m <= Math.min(s.length - 1, l + Math.floor(a / 2)); m++)
533
- r.push(s[m]), d.push(o[m]);
534
- for (let m = Math.max(0, h - Math.floor(a / 2)); m <= Math.min(s.length - 1, h + Math.floor(a / 2)); m++)
535
- r.push(s[m]), d.push(o[m]);
536
- const f = r.length;
537
- let p = 0, g = 0, y = 0, b = 0;
538
- for (let m = 0; m < f; m++)
539
- p += r[m], g += d[m], y += r[m] * d[m], b += r[m] * r[m];
540
- const x = (f * y - p * g) / (f * b - p * p), S = (g - x * p) / f;
541
- let T = 0, v = 0, Y = -1 / 0, w = 0, C = 0;
542
- for (let m = l; m < h; m++) {
543
- const I = s[m], A = o[m], $ = s[m + 1], R = o[m + 1], D = $ - I, q = (A + R) / 2;
544
- v += q * D;
545
- const j = x * I + S, rt = x * $ + S, ht = (j + rt) / 2;
546
- T += (q - ht) * D;
547
- const J = A - j;
548
- J > Y && (Y = J, w = I, C = A);
549
- }
415
+ /**
416
+ * Check if tooltip fits within bounds
417
+ */
418
+ fitsInBounds(t, i, e, n) {
419
+ const s = this.getConstraintBounds();
420
+ return t >= s.left && i >= s.top && t + e <= s.right && i + n <= s.bottom;
421
+ }
422
+ /**
423
+ * Constrain position to stay within bounds
424
+ */
425
+ constrainToBounds(t, i, e, n) {
426
+ const s = this.getConstraintBounds(), o = 4;
427
+ let l = t, h = i;
428
+ return l < s.left + o ? l = s.left + o : l + e > s.right - o && (l = s.right - e - o), h < s.top + o ? h = s.top + o : h + n > s.bottom - o && (h = s.bottom - n - o), { x: l, y: h };
429
+ }
430
+ /**
431
+ * Get the bounds to constrain tooltip within
432
+ */
433
+ getConstraintBounds() {
434
+ let t = {
435
+ top: 0,
436
+ left: 0,
437
+ right: this.containerWidth,
438
+ bottom: this.containerHeight
439
+ };
440
+ return this.config.constrainToPlotArea && this.plotArea && (t = {
441
+ top: Math.max(t.top, this.plotArea.y),
442
+ left: Math.max(t.left, this.plotArea.x),
443
+ right: Math.min(t.right, this.plotArea.x + this.plotArea.width),
444
+ bottom: Math.min(t.bottom, this.plotArea.y + this.plotArea.height)
445
+ }), t;
446
+ }
447
+ /**
448
+ * Calculate position for crosshair tooltip (follows cursor vertically)
449
+ */
450
+ calculateCrosshairPosition(t, i, e) {
451
+ const { offset: n } = this.config, s = this.getConstraintBounds(), o = e.width + e.padding.left + e.padding.right, l = e.height + e.padding.top + e.padding.bottom;
452
+ let h = t + n.x, a = "left";
453
+ h + o > s.right - 4 && (h = t - o - n.x, a = "right");
454
+ let r = i - l / 2;
455
+ r = Math.max(s.top + 4, Math.min(s.bottom - l - 4, r));
456
+ const d = i - r;
550
457
  return {
551
- x1: t.x,
552
- y1: t.y,
553
- x2: i.x,
554
- y2: i.y,
555
- area: T,
556
- integral: v,
557
- baselineSlope: x,
558
- baselineIntercept: S,
559
- peakHeight: Y,
560
- peakX: w,
561
- peakY: C,
562
- pixelX1: t.pixelX,
563
- pixelY1: t.pixelY,
564
- pixelX2: i.pixelX,
565
- pixelY2: i.pixelY
458
+ x: h,
459
+ y: r,
460
+ arrowPosition: a,
461
+ arrowOffset: Math.max(8, Math.min(l - 8, d)),
462
+ wasFlipped: a === "right"
566
463
  };
567
464
  }
568
- renderOverlay() {
569
- if (!this.overlayCtx || !this.overlayCanvas) return;
570
- this.resizeOverlay();
571
- const t = this.ctx.container.getBoundingClientRect();
572
- this.overlayCtx.clearRect(0, 0, t.width, t.height);
573
- const i = this.options, e = this.overlayCtx, n = this.ctx.getPlotArea(), s = this.ctx.getViewBounds();
574
- if (this.state.point1) {
575
- const o = this.state.point1, l = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: s.yMin, yMax: s.yMax };
576
- o.pixelX = n.x + (o.x - s.xMin) / (s.xMax - s.xMin) * n.width, o.pixelY = n.y + (1 - (o.y - l.yMin) / (l.yMax - l.yMin)) * n.height;
577
- }
578
- if (this.state.point2) {
579
- const o = this.state.point2, l = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: s.yMin, yMax: s.yMax };
580
- o.pixelX = n.x + (o.x - s.xMin) / (s.xMax - s.xMin) * n.width, o.pixelY = n.y + (1 - (o.y - l.yMin) / (l.yMax - l.yMin)) * n.height;
581
- }
582
- if (this.crosshairPosition && this.state.selectionState !== "complete") {
583
- const o = this.crosshairPosition.x, l = this.crosshairPosition.y;
584
- e.save(), e.setLineDash([4, 4]), e.strokeStyle = "rgba(150, 150, 150, 0.7)", e.lineWidth = 1, e.beginPath(), e.moveTo(o, n.y), e.lineTo(o, n.y + n.height), e.stroke(), e.beginPath(), e.moveTo(n.x, l), e.lineTo(n.x + n.width, l), e.stroke(), e.restore();
585
- }
586
- if (this.state.hoveredPoint && this.state.selectionState !== "complete") {
587
- const o = this.state.hoveredPoint;
588
- e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize + 4, 0, Math.PI * 2), e.fillStyle = i.highlightColor + "30", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.strokeStyle = i.highlightColor, e.lineWidth = 2, e.stroke(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 4, 0, Math.PI * 2), e.fillStyle = i.highlightColor, e.fill(), this.drawPointTooltip(e, o, "Hover");
589
- }
590
- if (this.state.point1) {
591
- const o = this.state.point1;
592
- e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.fillStyle = i.lineColor, e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize - 3, 0, Math.PI * 2), e.fillStyle = "#ffffff", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 3, 0, Math.PI * 2), e.fillStyle = i.lineColor, e.fill(), this.drawPointLabel(e, o.pixelX, o.pixelY - i.highlightSize - 8, "P1", i.lineColor);
593
- }
594
- if (this.state.point2) {
595
- const o = this.state.point2;
596
- e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.fillStyle = "#4ade80", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize - 3, 0, Math.PI * 2), e.fillStyle = "#ffffff", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 3, 0, Math.PI * 2), e.fillStyle = "#4ade80", e.fill(), this.drawPointLabel(e, o.pixelX, o.pixelY - i.highlightSize - 8, "P2", "#4ade80");
597
- }
598
- if (this.state.point1 && this.state.point2 && this.state.lastMeasurement) {
599
- const o = this.state.point1, l = this.state.point2, h = this.state.lastMeasurement, a = this.ctx.getPlotArea(), r = this.ctx.getViewBounds(), f = (this.ctx.getSeries ? this.ctx.getSeries() : []).find((S) => S.id === o.seriesId);
600
- if (f) {
601
- const S = f.x, T = f.y, v = Math.min(o.index, l.index), Y = Math.max(o.index, l.index), w = this.ctx.getYBounds ? this.ctx.getYBounds(f.yAxisId) : { yMin: r.yMin, yMax: r.yMax };
602
- e.beginPath(), e.moveTo(o.pixelX, o.pixelY);
603
- for (let C = v; C <= Y; C++) {
604
- const m = a.x + (S[C] - r.xMin) / (r.xMax - r.xMin) * a.width, I = a.y + (1 - (T[C] - w.yMin) / (w.yMax - w.yMin)) * a.height;
605
- e.lineTo(m, I);
606
- }
607
- e.lineTo(l.pixelX, l.pixelY), e.closePath(), e.fillStyle = i.areaFill, e.fill();
608
- }
609
- e.beginPath(), e.moveTo(o.pixelX, o.pixelY), e.lineTo(l.pixelX, l.pixelY), e.strokeStyle = i.lineColor, e.lineWidth = 1, e.stroke();
610
- const p = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: r.yMin, yMax: r.yMax }, g = a.x + (h.peakX - r.xMin) / (r.xMax - r.xMin) * a.width, y = a.y + (1 - (h.peakY - p.yMin) / (p.yMax - p.yMin)) * a.height, b = h.baselineSlope * h.peakX + h.baselineIntercept, x = a.y + (1 - (b - p.yMin) / (p.yMax - p.yMin)) * a.height;
611
- e.beginPath(), e.moveTo(g, y), e.lineTo(g, x), e.strokeStyle = i.lineColor, e.setLineDash([2, 2]), e.stroke(), e.setLineDash([]), i.showLabels && this.drawMeasurementLabel(e, o, l, h);
612
- }
613
- this.drawStatusIndicator(e);
465
+ }
466
+ class wt {
467
+ constructor(t, i) {
468
+ u(this, "ctx");
469
+ u(this, "theme");
470
+ this.ctx = t, this.theme = i;
614
471
  }
615
- drawPointTooltip(t, i, e) {
616
- const n = this.options, s = `${e}: (${this.formatValue(i.x)}, ${this.formatValue(i.y)})`;
617
- t.font = `${n.labelFontSize}px system-ui, sans-serif`;
618
- const o = t.measureText(s), l = 6, h = o.width + l * 2, a = n.labelFontSize + l * 2;
619
- let r = i.pixelX + 15, d = i.pixelY - a / 2;
620
- const f = this.ctx.container.getBoundingClientRect();
621
- r + h > f.width && (r = i.pixelX - h - 15), d < 0 && (d = 5), d + a > f.height && (d = f.height - a - 5), t.fillStyle = n.labelBackground, this.roundRect(t, r, d, h, a, 4), t.fill(), t.fillStyle = n.labelColor, t.textAlign = "left", t.textBaseline = "middle", t.fillText(s, r + l, d + a / 2);
472
+ /**
473
+ * Update the theme
474
+ */
475
+ setTheme(t) {
476
+ this.theme = t;
622
477
  }
623
- drawPointLabel(t, i, e, n, s) {
624
- const o = this.options;
625
- t.font = `bold ${o.labelFontSize}px system-ui, sans-serif`, t.textAlign = "center", t.textBaseline = "bottom";
626
- const l = t.measureText(n), h = 4, a = l.width + h * 2, r = o.labelFontSize + h;
627
- t.fillStyle = s, this.roundRect(t, i - a / 2, e - r, a, r, 3), t.fill(), t.fillStyle = "#ffffff", t.fillText(n, i, e - 2);
478
+ /**
479
+ * Render a tooltip with the given template
480
+ */
481
+ render(t, i, e) {
482
+ const n = e.measure(this.ctx, t, this.theme), s = n.width + n.padding.left + n.padding.right, o = n.height + n.padding.top + n.padding.bottom;
483
+ this.ctx.save(), this.drawBackground(i.x, i.y, s, o, i.arrowPosition || "none"), i.arrowPosition && i.arrowPosition !== "none" && this.drawArrow(
484
+ i.x,
485
+ i.y,
486
+ s,
487
+ o,
488
+ i.arrowPosition,
489
+ i.arrowOffset || 0
490
+ ), e.render(this.ctx, t, i, n, this.theme), this.ctx.restore();
628
491
  }
629
- drawMeasurementLabel(t, i, e, n) {
630
- const s = this.options, o = (i.pixelX + e.pixelX) / 2, l = (i.pixelY + e.pixelY) / 2, h = [];
631
- s.showPeak && (h.push(`Peak Area: ${this.formatValue(n.area)}`), h.push(`Peak Height: ${this.formatValue(n.peakHeight)}`), h.push(`Integral: ${this.formatValue(n.integral)}`));
632
- const a = s.labelFontSize + 4, r = 10, d = 170, f = h.length * a + r * 2;
633
- let p = o + this.labelOffset.x, g = l + this.labelOffset.y;
634
- const y = this.ctx.container.getBoundingClientRect();
635
- p < 0 && (p = 0), p + d > y.width && (p = y.width - d), g < 0 && (g = 0), g + f > y.height && (g = y.height - f), this.labelBounds = { x: p, y: g, width: d, height: f }, t.fillStyle = s.labelBackground, t.strokeStyle = s.lineColor, t.lineWidth = 2, this.roundRect(t, p, g, d, f, 8), t.fill(), t.stroke(), t.fillStyle = s.labelColor, t.font = `${s.labelFontSize}px system-ui, sans-serif`, t.textAlign = "left", t.textBaseline = "top", h.forEach((b, x) => {
636
- t.fillText(b, p + r, g + r + x * a);
637
- });
492
+ /**
493
+ * Draw tooltip background with all effects
494
+ */
495
+ drawBackground(t, i, e, n, s) {
496
+ const { ctx: o, theme: l } = this, h = l.borderRadius;
497
+ if (o.save(), l.shadow.blur > 0 && (o.shadowColor = l.shadow.color, o.shadowOffsetX = l.shadow.offsetX, o.shadowOffsetY = l.shadow.offsetY, o.shadowBlur = l.shadow.blur), o.beginPath(), this.roundRect(t, i, e, n, h), l.backgroundGradient) {
498
+ const a = o.createLinearGradient(t, i, t, i + n);
499
+ a.addColorStop(0, l.backgroundColor), a.addColorStop(1, l.backgroundGradient), o.fillStyle = a;
500
+ } else
501
+ o.fillStyle = l.backgroundColor;
502
+ o.fill(), o.shadowColor = "transparent", l.borderWidth > 0 && (o.strokeStyle = l.borderColor, o.lineWidth = l.borderWidth, o.stroke()), o.restore();
638
503
  }
639
- drawStatusIndicator(t) {
640
- const i = this.options, e = this.ctx.getPlotArea();
641
- let n = "", s = "#666";
642
- if (this.state.selectionState === "idle" ? (n = "📏 Click a point to start measuring", s = i.highlightColor) : this.state.selectionState === "waitingSecond" ? (n = "📏 Click second point to complete", s = "#f59e0b") : this.state.selectionState === "complete" && (n = "📏 Measurement complete (ESC to clear)", s = "#4ade80"), !n) return;
643
- t.font = "12px system-ui, sans-serif";
644
- const h = t.measureText(n).width + 8 * 2, a = 24, r = e.x + 10, d = e.y + 10;
645
- t.fillStyle = "rgba(0, 0, 0, 0.75)", this.roundRect(t, r, d, h, a, 4), t.fill(), t.beginPath(), t.arc(r + 16, d + a / 2, 4, 0, Math.PI * 2), t.fillStyle = s, t.fill(), t.fillStyle = "#fff", t.textAlign = "left", t.textBaseline = "middle", t.fillText(n, r + 26, d + a / 2);
504
+ /**
505
+ * Draw arrow pointing to target
506
+ */
507
+ drawArrow(t, i, e, n, s, o) {
508
+ const { ctx: l, theme: h } = this, a = h.arrowSize;
509
+ if (!(a <= 0 || s === "none")) {
510
+ switch (l.save(), l.fillStyle = h.backgroundColor, l.strokeStyle = h.borderColor, l.lineWidth = h.borderWidth, l.beginPath(), s) {
511
+ case "top":
512
+ const r = t + o;
513
+ l.moveTo(r - a, i), l.lineTo(r, i - a), l.lineTo(r + a, i);
514
+ break;
515
+ case "bottom":
516
+ const d = t + o, f = i + n;
517
+ l.moveTo(d - a, f), l.lineTo(d, f + a), l.lineTo(d + a, f);
518
+ break;
519
+ case "left":
520
+ const p = i + o;
521
+ l.moveTo(t, p - a), l.lineTo(t - a, p), l.lineTo(t, p + a);
522
+ break;
523
+ case "right":
524
+ const g = t + e, y = i + o;
525
+ l.moveTo(g, y - a), l.lineTo(g + a, y), l.lineTo(g, y + a);
526
+ break;
527
+ }
528
+ l.closePath(), l.fill(), h.borderWidth > 0 && l.stroke(), l.restore();
529
+ }
646
530
  }
647
- roundRect(t, i, e, n, s, o) {
648
- t.beginPath(), t.moveTo(i + o, e), t.lineTo(i + n - o, e), t.quadraticCurveTo(i + n, e, i + n, e + o), t.lineTo(i + n, e + s - o), t.quadraticCurveTo(i + n, e + s, i + n - o, e + s), t.lineTo(i + o, e + s), t.quadraticCurveTo(i, e + s, i, e + s - o), t.lineTo(i, e + o), t.quadraticCurveTo(i, e, i + o, e), t.closePath();
531
+ /**
532
+ * Draw a rounded rectangle path
533
+ */
534
+ roundRect(t, i, e, n, s) {
535
+ const { ctx: o } = this, l = Math.min(s, e / 2, n / 2);
536
+ o.moveTo(t + l, i), o.lineTo(t + e - l, i), o.arcTo(t + e, i, t + e, i + l, l), o.lineTo(t + e, i + n - l), o.arcTo(t + e, i + n, t + e - l, i + n, l), o.lineTo(t + l, i + n), o.arcTo(t, i + n, t, i + n - l, l), o.lineTo(t, i + l), o.arcTo(t, i, t + l, i, l), o.closePath();
537
+ }
538
+ /**
539
+ * Draw a horizontal separator line
540
+ */
541
+ drawSeparator(t, i, e) {
542
+ const { ctx: n, theme: s } = this;
543
+ s.showHeaderSeparator && (n.save(), n.strokeStyle = s.separatorColor, n.lineWidth = 1, n.beginPath(), n.moveTo(t, i), n.lineTo(t + e, i), n.stroke(), n.restore());
544
+ }
545
+ /**
546
+ * Draw a series color indicator (small circle or square)
547
+ */
548
+ drawSeriesIndicator(t, i, e) {
549
+ const { ctx: n, theme: s } = this;
550
+ if (!s.showSeriesIndicator) return;
551
+ const l = s.seriesIndicatorSize / 2;
552
+ n.save(), n.fillStyle = e, n.beginPath(), n.arc(t + l, i, l, 0, Math.PI * 2), n.fill(), n.restore();
649
553
  }
650
- formatValue(t) {
651
- const i = Math.abs(t);
652
- return i === 0 ? "0" : i >= 1e6 || i < 1e-3 ? t.toExponential(this.options.precision - 1) : t.toPrecision(this.options.precision);
554
+ /**
555
+ * Measure text width
556
+ */
557
+ measureText(t, i, e = 400) {
558
+ const { ctx: n, theme: s } = this;
559
+ return n.font = `${e} ${i}px ${s.fontFamily}`, n.measureText(t).width;
560
+ }
561
+ /**
562
+ * Draw text with theme styling
563
+ */
564
+ drawText(t, i, e, n = {}) {
565
+ const { ctx: s, theme: o } = this, {
566
+ fontSize: l = o.contentFontSize,
567
+ fontWeight: h = 400,
568
+ color: a = o.textColor,
569
+ align: r = "left",
570
+ baseline: d = "top"
571
+ } = n;
572
+ s.save(), s.font = `${h} ${l}px ${o.fontFamily}`, s.fillStyle = a, s.textAlign = r, s.textBaseline = d, s.fillText(t, i, e), s.restore();
573
+ }
574
+ /**
575
+ * Draw a label-value pair
576
+ */
577
+ drawLabelValue(t, i, e, n, s) {
578
+ const { theme: o } = this;
579
+ this.drawText(e, t, i, {
580
+ color: o.textSecondaryColor,
581
+ fontSize: o.contentFontSize
582
+ }), this.drawText(n, t + s, i, {
583
+ color: o.textColor,
584
+ fontSize: o.contentFontSize,
585
+ align: "right"
586
+ });
587
+ }
588
+ /**
589
+ * Get theme for external use
590
+ */
591
+ getTheme() {
592
+ return this.theme;
653
593
  }
654
594
  }
655
- const V = {
656
- top: 10,
657
- right: 12,
658
- bottom: 10,
659
- left: 12
660
- }, L = {
661
- // Container
662
- backgroundColor: "rgba(15, 18, 25, 0.95)",
663
- backgroundGradient: "rgba(25, 30, 40, 0.90)",
664
- borderColor: "rgba(255, 255, 255, 0.12)",
665
- borderWidth: 1,
666
- borderRadius: 8,
667
- backdropBlur: 12,
668
- shadow: {
669
- color: "rgba(0, 0, 0, 0.4)",
670
- offsetX: 0,
671
- offsetY: 4,
672
- blur: 16
673
- },
674
- // Typography
675
- fontFamily: "Inter, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
676
- textColor: "#ffffff",
677
- textSecondaryColor: "rgba(255, 255, 255, 0.6)",
678
- titleFontSize: 12,
679
- titleFontWeight: 600,
680
- contentFontSize: 11,
681
- lineHeight: 1.4,
682
- // Spacing
683
- padding: V,
684
- itemGap: 4,
685
- headerGap: 8,
686
- // Decorations
687
- showSeriesIndicator: !0,
688
- seriesIndicatorSize: 8,
689
- showHeaderSeparator: !0,
690
- separatorColor: "rgba(255, 255, 255, 0.1)",
691
- showArrow: !0,
692
- arrowSize: 6
693
- }, et = {
694
- // Container
695
- backgroundColor: "rgba(255, 255, 255, 0.98)",
696
- borderColor: "rgba(0, 0, 0, 0.12)",
697
- borderWidth: 1,
698
- borderRadius: 8,
699
- backdropBlur: 8,
700
- shadow: {
701
- color: "rgba(0, 0, 0, 0.12)",
702
- offsetX: 0,
703
- offsetY: 4,
704
- blur: 12
705
- },
706
- // Typography
707
- fontFamily: "Inter, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
708
- textColor: "#1a1a1a",
709
- textSecondaryColor: "rgba(0, 0, 0, 0.55)",
710
- titleFontSize: 12,
711
- titleFontWeight: 600,
712
- contentFontSize: 11,
713
- lineHeight: 1.4,
714
- // Spacing
715
- padding: V,
716
- itemGap: 4,
717
- headerGap: 8,
718
- // Decorations
719
- showSeriesIndicator: !0,
720
- seriesIndicatorSize: 8,
721
- showHeaderSeparator: !0,
722
- separatorColor: "rgba(0, 0, 0, 0.08)",
723
- showArrow: !0,
724
- arrowSize: 6
725
- }, gt = {
726
- // Container
727
- backgroundColor: "rgba(255, 255, 255, 0.08)",
728
- backgroundGradient: "rgba(255, 255, 255, 0.04)",
729
- borderColor: "rgba(255, 255, 255, 0.2)",
730
- borderWidth: 1,
731
- borderRadius: 12,
732
- backdropBlur: 20,
733
- shadow: {
734
- color: "rgba(0, 0, 0, 0.3)",
735
- offsetX: 0,
736
- offsetY: 8,
737
- blur: 32
738
- },
739
- // Typography
740
- fontFamily: "Inter, system-ui, sans-serif",
741
- textColor: "rgba(255, 255, 255, 0.95)",
742
- textSecondaryColor: "rgba(255, 255, 255, 0.6)",
743
- titleFontSize: 12,
744
- titleFontWeight: 600,
745
- contentFontSize: 11,
746
- lineHeight: 1.5,
747
- // Spacing
748
- padding: { top: 12, right: 14, bottom: 12, left: 14 },
749
- itemGap: 5,
750
- headerGap: 10,
751
- // Decorations
752
- showSeriesIndicator: !0,
753
- seriesIndicatorSize: 8,
754
- showHeaderSeparator: !0,
755
- separatorColor: "rgba(255, 255, 255, 0.15)",
756
- showArrow: !0,
757
- arrowSize: 7
758
- }, it = {
759
- // Container
760
- backgroundColor: "rgba(10, 15, 30, 0.95)",
761
- backgroundGradient: "rgba(20, 25, 50, 0.90)",
762
- borderColor: "rgba(100, 120, 255, 0.25)",
763
- borderWidth: 1,
764
- borderRadius: 8,
765
- backdropBlur: 15,
766
- shadow: {
767
- color: "rgba(50, 70, 150, 0.25)",
768
- offsetX: 0,
769
- offsetY: 4,
770
- blur: 20
771
- },
772
- // Typography
773
- fontFamily: "Inter, system-ui, sans-serif",
774
- textColor: "#e0e8ff",
775
- textSecondaryColor: "rgba(180, 190, 255, 0.6)",
776
- titleFontSize: 12,
777
- titleFontWeight: 600,
778
- contentFontSize: 11,
779
- lineHeight: 1.4,
780
- // Spacing
781
- padding: V,
782
- itemGap: 4,
783
- headerGap: 8,
784
- // Decorations
785
- showSeriesIndicator: !0,
786
- seriesIndicatorSize: 8,
787
- showHeaderSeparator: !0,
788
- separatorColor: "rgba(100, 120, 255, 0.15)",
789
- showArrow: !0,
790
- arrowSize: 6
791
- }, st = {
792
- // Container
793
- backgroundColor: "rgba(8, 20, 40, 0.96)",
794
- backgroundGradient: "rgba(15, 35, 60, 0.92)",
795
- borderColor: "rgba(30, 136, 229, 0.35)",
796
- borderWidth: 1,
797
- borderRadius: 6,
798
- backdropBlur: 10,
799
- shadow: {
800
- color: "rgba(30, 136, 229, 0.2)",
801
- offsetX: 0,
802
- offsetY: 4,
803
- blur: 16
804
- },
805
- // Typography
806
- fontFamily: '"JetBrains Mono", "Fira Code", monospace',
807
- textColor: "#bbdefb",
808
- textSecondaryColor: "rgba(144, 202, 249, 0.7)",
809
- titleFontSize: 11,
810
- titleFontWeight: 600,
811
- contentFontSize: 11,
812
- lineHeight: 1.5,
813
- // Spacing
814
- padding: { top: 8, right: 10, bottom: 8, left: 10 },
815
- itemGap: 3,
816
- headerGap: 6,
817
- // Decorations
818
- showSeriesIndicator: !0,
819
- seriesIndicatorSize: 6,
820
- showHeaderSeparator: !0,
821
- separatorColor: "rgba(30, 136, 229, 0.2)",
822
- showArrow: !0,
823
- arrowSize: 5
824
- }, yt = {
825
- // Container
826
- backgroundColor: "rgba(0, 0, 0, 0.92)",
827
- borderColor: "#00ffff",
828
- borderWidth: 1,
829
- borderRadius: 4,
830
- backdropBlur: 0,
831
- shadow: {
832
- color: "rgba(0, 255, 255, 0.35)",
833
- offsetX: 0,
834
- offsetY: 0,
835
- blur: 20
836
- },
837
- // Typography
838
- fontFamily: '"Orbitron", "Exo 2", sans-serif',
839
- textColor: "#00ffff",
840
- textSecondaryColor: "rgba(0, 255, 255, 0.6)",
841
- titleFontSize: 11,
842
- titleFontWeight: 700,
843
- contentFontSize: 10,
844
- lineHeight: 1.4,
845
- // Spacing
846
- padding: { top: 8, right: 12, bottom: 8, left: 12 },
847
- itemGap: 4,
848
- headerGap: 6,
849
- // Decorations
850
- showSeriesIndicator: !0,
851
- seriesIndicatorSize: 6,
852
- showHeaderSeparator: !0,
853
- separatorColor: "rgba(0, 255, 255, 0.3)",
854
- showArrow: !1,
855
- arrowSize: 0
856
- }, xt = {
857
- // Container
858
- backgroundColor: "rgba(30, 35, 45, 0.9)",
859
- borderColor: "transparent",
860
- borderWidth: 0,
861
- borderRadius: 4,
862
- backdropBlur: 8,
863
- shadow: {
864
- color: "rgba(0, 0, 0, 0.3)",
865
- offsetX: 0,
866
- offsetY: 2,
867
- blur: 8
868
- },
869
- // Typography
870
- fontFamily: "Inter, system-ui, sans-serif",
871
- textColor: "#ffffff",
872
- textSecondaryColor: "rgba(255, 255, 255, 0.7)",
873
- titleFontSize: 10,
874
- titleFontWeight: 500,
875
- contentFontSize: 10,
876
- lineHeight: 1.3,
877
- // Spacing
878
- padding: { top: 6, right: 8, bottom: 6, left: 8 },
879
- itemGap: 2,
880
- headerGap: 4,
881
- // Decorations
882
- showSeriesIndicator: !1,
883
- seriesIndicatorSize: 0,
884
- showHeaderSeparator: !1,
885
- separatorColor: "transparent",
886
- showArrow: !1,
887
- arrowSize: 0
888
- }, bt = {
889
- dark: L,
890
- light: et,
891
- glass: gt,
892
- midnight: it,
893
- electrochemistry: st,
894
- neon: yt,
895
- minimal: xt
896
- };
897
- function St(c) {
898
- const t = bt[c];
899
- return t || (console.warn(`[Tooltip] Unknown theme "${c}", using dark`), L);
900
- }
901
- function mt(c, t) {
902
- const i = typeof c == "string" ? St(c) : c;
903
- return {
904
- ...i,
905
- ...t,
906
- padding: { ...i.padding, ...t.padding },
907
- shadow: { ...i.shadow, ...t.shadow }
908
- };
595
+ function ot(c) {
596
+ return c.axisFormat ? O(c.dataX, c.axisFormat) : W(c.dataX);
909
597
  }
910
- function B(c) {
911
- switch (c.toLowerCase()) {
912
- case "light":
913
- return et;
914
- case "midnight":
915
- return it;
916
- case "electrochemistry":
917
- case "electrochem":
918
- return st;
919
- case "dark":
920
- default:
921
- return L;
598
+ function nt(c) {
599
+ return c.axisFormat ? N(c.dataY, c.axisFormat) : W(c.dataY);
600
+ }
601
+ function Mt(c) {
602
+ return c.axisFormat ? O(c.dataX, c.axisFormat) : W(c.dataX);
603
+ }
604
+ function vt(c, t) {
605
+ return t.axisFormat ? N(c, t.axisFormat) : W(c);
606
+ }
607
+ function W(c) {
608
+ if (c == null || isNaN(c)) return "N/A";
609
+ const t = Math.abs(c);
610
+ return t === 0 ? "0" : t < 1e-4 || t >= 1e4 ? c.toExponential(2) : t < 0.01 ? c.toPrecision(4) : t < 1 ? c.toFixed(4) : t < 100 ? c.toFixed(3) : c.toFixed(1);
611
+ }
612
+ function k(c, t) {
613
+ const i = t === "x" ? c.dataX : c.dataY;
614
+ if (c.axisFormat)
615
+ return t === "x" ? O(i, c.axisFormat) : N(i, c.axisFormat);
616
+ if (i == null || isNaN(i)) return "N/A";
617
+ const e = Math.abs(i);
618
+ return e === 0 ? "0" : e >= 1e9 ? (i / 1e9).toFixed(1) + "G" : e >= 1e6 ? (i / 1e6).toFixed(1) + "M" : e >= 1e3 ? (i / 1e3).toFixed(1) + "k" : e >= 1 ? i.toFixed(2) : e >= 1e-3 ? (i * 1e3).toFixed(1) + "m" : e >= 1e-6 ? (i * 1e6).toFixed(1) + "µ" : e >= 1e-9 ? (i * 1e9).toFixed(1) + "n" : i.toExponential(1);
619
+ }
620
+ function z(c, t, i) {
621
+ if (t && i === "x") return ot(t);
622
+ if (t && i === "y") return nt(t);
623
+ if (c == null || isNaN(c)) return "N/A";
624
+ const e = Math.abs(c);
625
+ return e !== 0 && (e < 1e-4 || e >= 1e4) ? c.toExponential(2) : e < 0.01 ? c.toPrecision(4) : e < 1 ? c.toFixed(4) : e < 100 ? c.toFixed(3) : c.toFixed(1);
626
+ }
627
+ class Ct {
628
+ constructor() {
629
+ u(this, "id", "default");
630
+ u(this, "name", "Default Data Point");
631
+ u(this, "supportedTypes", ["datapoint"]);
632
+ // Cached measurements
633
+ u(this, "cachedKey", "");
634
+ u(this, "cachedMeasurement", null);
635
+ }
636
+ /**
637
+ * Measure tooltip dimensions
638
+ */
639
+ measure(t, i, e) {
640
+ const n = `${i.seriesId}-${i.dataIndex}-${i.dataX}-${i.dataY}`;
641
+ if (n === this.cachedKey && this.cachedMeasurement)
642
+ return this.cachedMeasurement;
643
+ t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
644
+ const s = t.measureText(i.seriesName).width;
645
+ t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
646
+ const o = 35, l = t.measureText(z(i.dataX, i, "x")).width, h = t.measureText(z(i.dataY, i, "y")).width;
647
+ let a = o + Math.max(l, h), r = e.contentFontSize * e.lineHeight * 2;
648
+ if (i.yError) {
649
+ const g = `± ${z(i.yError[0])} / ${z(i.yError[1])}`;
650
+ a = Math.max(a, t.measureText(g).width), r += e.contentFontSize * e.lineHeight;
651
+ }
652
+ if (i.cycle !== void 0) {
653
+ const g = `Cycle: ${i.cycle}`;
654
+ a = Math.max(a, t.measureText(g).width), r += e.contentFontSize * e.lineHeight;
655
+ }
656
+ const d = e.showSeriesIndicator ? e.seriesIndicatorSize + 6 : 0, f = Math.max(s + d, a), p = e.titleFontSize * e.lineHeight + e.headerGap + r;
657
+ return this.cachedMeasurement = {
658
+ width: f + 2,
659
+ // Tiny buffer
660
+ height: p + 4,
661
+ padding: e.padding,
662
+ arrow: e.showArrow ? { width: e.arrowSize * 2, height: e.arrowSize } : void 0
663
+ }, this.cachedKey = n, this.cachedMeasurement;
664
+ }
665
+ /**
666
+ * Render the tooltip
667
+ */
668
+ render(t, i, e, n, s) {
669
+ const { x: o, y: l } = e, { padding: h } = s;
670
+ let a = o + h.left, r = l + h.top;
671
+ if (t.save(), t.textAlign = "left", t.textBaseline = "top", s.showSeriesIndicator) {
672
+ const f = r + s.titleFontSize / 2;
673
+ t.save(), t.fillStyle = i.seriesColor, t.beginPath(), t.arc(a + s.seriesIndicatorSize / 2, f, s.seriesIndicatorSize / 2, 0, Math.PI * 2), t.fill(), t.restore(), a += s.seriesIndicatorSize + 6;
674
+ }
675
+ if (t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor, t.fillText(i.seriesName, a, r), r += s.titleFontSize * s.lineHeight + 2, s.showHeaderSeparator) {
676
+ const f = n.width;
677
+ t.save(), t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(o + h.left, r + s.headerGap / 2), t.lineTo(o + h.left + f, r + s.headerGap / 2), t.stroke(), t.restore();
678
+ }
679
+ r += s.headerGap + 2, a = o + h.left, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`, t.fillStyle = s.textSecondaryColor, t.fillText("X:", a, r), t.fillStyle = s.textColor;
680
+ const d = 35;
681
+ if (t.fillText(z(i.dataX, i, "x"), a + d, r), r += s.contentFontSize * s.lineHeight + 2, t.fillStyle = s.textSecondaryColor, t.fillText("Y:", a, r), t.fillStyle = s.textColor, t.fillText(z(i.dataY, i, "y"), a + d, r), r += s.contentFontSize * s.lineHeight + 2, i.yError) {
682
+ t.fillStyle = s.textSecondaryColor;
683
+ const f = `± ${z(i.yError[0])} / +${z(i.yError[1])}`;
684
+ t.fillText(f, a, r), r += s.contentFontSize * s.lineHeight;
685
+ }
686
+ i.cycle !== void 0 && (t.fillStyle = s.textSecondaryColor, t.fillText("Cycle:", a, r), t.fillStyle = s.textColor, t.fillText(String(i.cycle), a + 45, r)), t.restore();
922
687
  }
923
688
  }
924
- const Tt = L, wt = {
925
- offset: { x: 12, y: 12 },
926
- preferredPosition: "auto",
927
- constrainToPlotArea: !0,
928
- constrainToContainer: !0,
929
- autoFlip: !0,
930
- showArrow: !0,
931
- arrowSize: 6
689
+ const H = new Ct();
690
+ class Pt {
691
+ constructor() {
692
+ u(this, "id", "minimal");
693
+ u(this, "name", "Minimal");
694
+ u(this, "supportedTypes", ["datapoint"]);
695
+ // Cached measurements
696
+ u(this, "cachedKey", "");
697
+ u(this, "cachedMeasurement", null);
698
+ }
699
+ /**
700
+ * Measure tooltip dimensions
701
+ */
702
+ measure(t, i, e) {
703
+ const n = `${i.seriesId}-${i.dataIndex}-${i.dataX}-${i.dataY}-${e.fontFamily}`;
704
+ if (n === this.cachedKey && this.cachedMeasurement)
705
+ return this.cachedMeasurement;
706
+ t.font = `${e.contentFontSize}px ${e.fontFamily}`;
707
+ const s = `${k(i, "x")} ▪ ${k(i, "y")}`, o = t.measureText(s).width;
708
+ return this.cachedMeasurement = {
709
+ width: o,
710
+ height: e.contentFontSize * e.lineHeight,
711
+ padding: {
712
+ top: 6,
713
+ right: 8,
714
+ bottom: 6,
715
+ left: 8
716
+ }
717
+ }, this.cachedKey = n, this.cachedMeasurement;
718
+ }
719
+ /**
720
+ * Render the tooltip
721
+ */
722
+ render(t, i, e, n, s) {
723
+ const { padding: o } = n, l = e.x + o.left, h = e.y + o.top, a = k(i, "x"), r = k(i, "y");
724
+ t.save(), t.font = `${s.contentFontSize}px ${s.fontFamily}`, t.textAlign = "left", t.textBaseline = "top", t.fillStyle = s.textSecondaryColor, t.fillText(a, l, h);
725
+ const d = t.measureText(a).width;
726
+ t.fillStyle = i.seriesColor, t.fillText(" ▪ ", l + d, h);
727
+ const f = t.measureText(" ▪ ").width;
728
+ t.fillStyle = s.textColor, t.fillText(r, l + d + f, h), t.restore();
729
+ }
730
+ }
731
+ const at = new Pt(), F = {
732
+ INDICATOR_MARGIN: 8,
733
+ MIN_LABEL_WIDTH: 60,
734
+ LINE_SPACING: 2,
735
+ SAFETY_BUFFER: 4,
736
+ HEADER_SPACING: 2,
737
+ GAP_SPACING: 2
932
738
  };
933
- class Mt {
934
- constructor(t = {}) {
935
- u(this, "config");
936
- u(this, "containerWidth", 0);
937
- u(this, "containerHeight", 0);
938
- u(this, "plotArea", null);
939
- this.config = { ...wt, ...t };
739
+ class Ft {
740
+ constructor() {
741
+ u(this, "id", "crosshair");
742
+ u(this, "name", "Multi-Series Crosshair");
743
+ u(this, "supportedTypes", ["crosshair"]);
744
+ // Cached measurements
745
+ u(this, "cachedKey", "");
746
+ u(this, "cachedMeasurement", null);
747
+ u(this, "formattedData", null);
940
748
  }
941
749
  /**
942
- * Update container dimensions
750
+ * Generate cache key from data and theme
943
751
  */
944
- setContainerSize(t, i) {
945
- this.containerWidth = t, this.containerHeight = i;
752
+ getCacheKey(t, i) {
753
+ const e = t.interpolatedValues.map((n) => `${n.seriesName}-${n.y}`).join("|");
754
+ return [
755
+ t.dataX,
756
+ e,
757
+ i.fontFamily,
758
+ i.titleFontSize,
759
+ i.contentFontSize,
760
+ i.showSeriesIndicator,
761
+ i.seriesIndicatorSize,
762
+ `${i.padding.top}-${i.padding.right}-${i.padding.bottom}-${i.padding.left}`
763
+ ].join(":");
764
+ }
765
+ /**
766
+ * Format data for rendering
767
+ */
768
+ formatData(t) {
769
+ return {
770
+ header: `⌖ X = ${Mt(t)}`,
771
+ series: t.interpolatedValues.map((i) => ({
772
+ name: i.seriesName,
773
+ value: vt(i.y, t),
774
+ color: i.seriesColor,
775
+ isInterpolated: i.isInterpolated
776
+ }))
777
+ };
778
+ }
779
+ /**
780
+ * Measure tooltip dimensions
781
+ */
782
+ measure(t, i, e) {
783
+ if (!i.interpolatedValues || i.interpolatedValues.length === 0)
784
+ return {
785
+ width: 100,
786
+ height: e.titleFontSize * e.lineHeight,
787
+ padding: e.padding
788
+ };
789
+ const n = this.getCacheKey(i, e);
790
+ if (n === this.cachedKey && this.cachedMeasurement)
791
+ return this.cachedMeasurement;
792
+ this.formattedData = this.formatData(i), t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
793
+ let s = t.measureText(this.formattedData.header).width;
794
+ t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
795
+ const o = e.showSeriesIndicator ? e.seriesIndicatorSize + F.INDICATOR_MARGIN : 0;
796
+ for (const r of this.formattedData.series) {
797
+ const d = t.measureText(`${r.name}:`).width, f = t.measureText(r.value).width, p = o + Math.max(d, F.MIN_LABEL_WIDTH) + f;
798
+ s = Math.max(s, p);
799
+ }
800
+ const l = e.titleFontSize * e.lineHeight, a = this.formattedData.series.length * (e.contentFontSize * e.lineHeight + F.LINE_SPACING);
801
+ return this.cachedMeasurement = {
802
+ width: s + F.SAFETY_BUFFER,
803
+ height: l + e.headerGap + a,
804
+ padding: e.padding
805
+ }, this.cachedKey = n, this.cachedMeasurement;
946
806
  }
947
807
  /**
948
- * Update plot area bounds
808
+ * Render the tooltip
949
809
  */
950
- setPlotArea(t) {
951
- this.plotArea = t;
810
+ render(t, i, e, n, s) {
811
+ if (!i.interpolatedValues || i.interpolatedValues.length === 0)
812
+ return;
813
+ const o = this.formattedData || this.formatData(i), { x: l, y: h } = e, { padding: a } = s;
814
+ let r = h + a.top;
815
+ const d = l + a.left;
816
+ t.save(), t.textAlign = "left", t.textBaseline = "top", t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor, t.fillText(o.header, d, r), r += s.titleFontSize * s.lineHeight + F.HEADER_SPACING, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(d, r + s.headerGap / 2), t.lineTo(d + n.width, r + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), r += s.headerGap + F.GAP_SPACING, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
817
+ for (const f of o.series) {
818
+ let p = d;
819
+ if (s.showSeriesIndicator && s.seriesIndicatorSize > 0) {
820
+ const b = r + s.contentFontSize / 2, x = s.seriesIndicatorSize / 2;
821
+ t.save(), t.beginPath(), t.arc(p + x, b, x, 0, Math.PI * 2), f.isInterpolated ? (t.strokeStyle = f.color, t.lineWidth = 1.5, t.stroke()) : (t.fillStyle = f.color, t.fill()), t.restore(), p += s.seriesIndicatorSize + F.INDICATOR_MARGIN;
822
+ }
823
+ t.fillStyle = s.textSecondaryColor, t.fillText(`${f.name}:`, p, r);
824
+ const g = t.measureText(`${f.name}: `).width, y = Math.max(g, F.MIN_LABEL_WIDTH);
825
+ t.fillStyle = s.textColor, t.fillText(f.value, p + y, r), r += s.contentFontSize * s.lineHeight + F.LINE_SPACING;
826
+ }
827
+ t.restore();
828
+ }
829
+ }
830
+ const G = new Ft();
831
+ function P(c) {
832
+ if (c == null || isNaN(c))
833
+ return "N/A";
834
+ const t = Math.abs(c);
835
+ return t === 0 ? "0" : t < 1e-4 || t >= 1e4 ? c.toExponential(3) : t < 0.01 ? c.toPrecision(4) : t < 1 ? c.toFixed(4) : t < 100 ? c.toFixed(3) : c.toFixed(2);
836
+ }
837
+ class It {
838
+ constructor() {
839
+ u(this, "id", "heatmap");
840
+ u(this, "name", "Heatmap Value");
841
+ u(this, "supportedTypes", ["heatmap"]);
842
+ // Color swatch dimensions
843
+ u(this, "swatchSize", 14);
844
+ u(this, "swatchGap", 8);
845
+ // Cached measurements
846
+ u(this, "cachedKey", "");
847
+ u(this, "cachedMeasurement", null);
952
848
  }
953
849
  /**
954
- * Update configuration
850
+ * Measure tooltip dimensions
955
851
  */
956
- configure(t) {
957
- this.config = { ...this.config, ...t };
852
+ measure(t, i, e) {
853
+ const n = `${i.seriesId}-${i.xIndex}-${i.yIndex}-${e.fontFamily}-${e.titleFontSize}`;
854
+ if (n === this.cachedKey && this.cachedMeasurement)
855
+ return this.cachedMeasurement;
856
+ t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
857
+ const s = `Grid [${i.xIndex}, ${i.yIndex}]`;
858
+ let o = t.measureText(s).width;
859
+ t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
860
+ const l = `Value: ${P(i.zValue)}`, h = this.swatchSize + this.swatchGap + t.measureText(l).width;
861
+ if (o = Math.max(o, h), i.colorScale) {
862
+ const y = `Min: ${P(i.colorScale.min)} Max: ${P(i.colorScale.max)}`;
863
+ o = Math.max(o, this.swatchSize + this.swatchGap + t.measureText(y).width);
864
+ }
865
+ const a = 25, r = a + t.measureText(P(i.dataX)).width, d = a + t.measureText(P(i.dataY)).width;
866
+ o = Math.max(o, r, d);
867
+ const f = e.titleFontSize * e.lineHeight, g = (i.colorScale ? 4 : 3) * e.contentFontSize * e.lineHeight;
868
+ return this.cachedMeasurement = {
869
+ width: o + 4,
870
+ // Small buffer
871
+ height: f + e.headerGap + g + e.itemGap,
872
+ padding: e.padding
873
+ }, this.cachedKey = n, this.cachedMeasurement;
958
874
  }
959
875
  /**
960
- * Calculate optimal tooltip position
961
- *
962
- * @param targetX - Target X coordinate (where tooltip points to)
963
- * @param targetY - Target Y coordinate
964
- * @param measurement - Tooltip dimensions
965
- * @returns Calculated position with arrow configuration
876
+ * Render the tooltip
966
877
  */
967
- calculatePosition(t, i, e) {
968
- const { offset: n, preferredPosition: s, autoFlip: o, showArrow: l, arrowSize: h } = this.config, a = e.width + e.padding.left + e.padding.right, r = e.height + e.padding.top + e.padding.bottom;
969
- let d = this.determinePosition(
970
- t,
971
- i,
972
- a,
973
- r,
974
- s
975
- ), { x: f, y: p, arrowPosition: g } = this.calculateCoordinates(
976
- t,
977
- i,
978
- a,
979
- r,
980
- d,
981
- n,
982
- l ? h : 0
983
- ), y = !1;
984
- if (o && !this.fitsInBounds(f, p, a, r)) {
985
- const S = this.flipPosition(d), T = this.calculateCoordinates(
986
- t,
987
- i,
988
- a,
989
- r,
990
- S,
991
- n,
992
- l ? h : 0
993
- );
994
- this.fitsInBounds(T.x, T.y, a, r) && (f = T.x, p = T.y, g = T.arrowPosition, y = !0);
995
- }
996
- const b = this.constrainToBounds(f, p, a, r);
997
- let x = 0;
998
- if (l) {
999
- if (g === "top" || g === "bottom") {
1000
- const S = t - b.x;
1001
- x = Math.max(h + 4, Math.min(a - h - 4, S));
1002
- } else if (g === "left" || g === "right") {
1003
- const S = i - b.y;
1004
- x = Math.max(h + 4, Math.min(r - h - 4, S));
1005
- }
878
+ render(t, i, e, n, s) {
879
+ const { x: o, y: l } = e, { padding: h } = s;
880
+ let a = l + h.top;
881
+ const r = o + h.left;
882
+ t.save(), t.textAlign = "left", t.textBaseline = "top", t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor;
883
+ const d = `Grid [${i.xIndex}, ${i.yIndex}]`;
884
+ t.fillText(d, r, a), a += s.titleFontSize * s.lineHeight + 2, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(r, a + s.headerGap / 2), t.lineTo(r + n.width, a + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), a += s.headerGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
885
+ const f = a + (s.contentFontSize * s.lineHeight - this.swatchSize) / 2;
886
+ t.fillStyle = i.mappedColor || s.textColor, t.fillRect(r, f, this.swatchSize, this.swatchSize), t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.strokeRect(r, f, this.swatchSize, this.swatchSize), t.fillStyle = s.textSecondaryColor, t.fillText("Value:", r + this.swatchSize + this.swatchGap, a), t.fillStyle = s.textColor;
887
+ const p = t.measureText("Value: ").width;
888
+ if (t.fillText(P(i.zValue), r + this.swatchSize + this.swatchGap + p, a), a += s.contentFontSize * s.lineHeight, i.colorScale) {
889
+ t.fillStyle = s.textSecondaryColor, t.font = `400 ${s.contentFontSize - 1}px ${s.fontFamily}`;
890
+ const g = `Min: ${P(i.colorScale.min)} Max: ${P(i.colorScale.max)}`;
891
+ t.fillText(g, r + this.swatchSize + this.swatchGap, a), a += s.contentFontSize * s.lineHeight;
1006
892
  }
1007
- return {
1008
- x: b.x,
1009
- y: b.y,
1010
- arrowPosition: l ? g : "none",
1011
- arrowOffset: x,
1012
- wasFlipped: y
1013
- };
893
+ a += s.itemGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`, t.fillStyle = s.textSecondaryColor, t.fillText("X:", r, a), t.fillStyle = s.textColor, t.fillText(P(i.dataX), r + 25, a), a += s.contentFontSize * s.lineHeight + 2, t.fillStyle = s.textSecondaryColor, t.fillText("Y:", r, a), t.fillStyle = s.textColor, t.fillText(P(i.dataY), r + 25, a), t.restore();
894
+ }
895
+ }
896
+ const _ = new It();
897
+ function Q(c) {
898
+ var t, i;
899
+ return ((i = (t = c.axisFormat) == null ? void 0 : t.x) == null ? void 0 : i.type) === "time" ? ot(c) : E(c.dataX);
900
+ }
901
+ function Z(c) {
902
+ var t, i;
903
+ return ((i = (t = c.axisFormat) == null ? void 0 : t.y) == null ? void 0 : i.scientific) === !1 ? nt(c) : E(c.dataY);
904
+ }
905
+ function E(c, t = 3) {
906
+ if (c == null || isNaN(c))
907
+ return "N/A";
908
+ if (c === 0) return "0";
909
+ const i = Math.floor(Math.log10(Math.abs(c))), e = c / Math.pow(10, i);
910
+ if (Math.abs(i) <= 2)
911
+ return c.toPrecision(t);
912
+ const n = {
913
+ 0: "⁰",
914
+ 1: "¹",
915
+ 2: "²",
916
+ 3: "³",
917
+ 4: "⁴",
918
+ 5: "⁵",
919
+ 6: "⁶",
920
+ 7: "⁷",
921
+ 8: "⁸",
922
+ 9: "⁹",
923
+ "-": "⁻",
924
+ "+": "⁺"
925
+ }, s = i.toString();
926
+ let o = "";
927
+ for (const l of s)
928
+ o += n[l] || l;
929
+ return `${e.toFixed(t - 1)} × 10${o}`;
930
+ }
931
+ class zt {
932
+ constructor() {
933
+ u(this, "id", "scientific");
934
+ u(this, "name", "Scientific Notation");
935
+ u(this, "supportedTypes", ["datapoint"]);
936
+ // Cached measurements
937
+ u(this, "cachedKey", "");
938
+ u(this, "cachedMeasurement", null);
1014
939
  }
1015
940
  /**
1016
- * Determine best position based on available space
941
+ * Measure tooltip dimensions
1017
942
  */
1018
- determinePosition(t, i, e, n, s) {
1019
- if (s !== "auto")
1020
- return s;
1021
- const o = this.getConstraintBounds(), l = i - o.top, h = o.bottom - i, a = t - o.left, r = o.right - t, d = l >= n + this.config.offset.y, f = h >= n + this.config.offset.y, p = r >= e + this.config.offset.x, g = a >= e + this.config.offset.x;
1022
- return d && l >= h ? "top" : f ? "bottom" : p && r >= a ? "right" : g ? "left" : [
1023
- { pos: "top", space: l },
1024
- { pos: "bottom", space: h },
1025
- { pos: "right", space: r },
1026
- { pos: "left", space: a }
1027
- ].sort((b, x) => x.space - b.space)[0].pos;
943
+ measure(t, i, e) {
944
+ const n = `${i.seriesId}-${i.dataIndex}-${i.dataX}-${i.dataY}-${e.fontFamily}-${e.titleFontSize}`;
945
+ if (n === this.cachedKey && this.cachedMeasurement)
946
+ return this.cachedMeasurement;
947
+ t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
948
+ const s = t.measureText(i.seriesName).width + (e.showSeriesIndicator ? e.seriesIndicatorSize + 8 : 0);
949
+ t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
950
+ const o = 28, l = Q(i);
951
+ let h = Math.max(s, o + t.measureText(l).width);
952
+ const a = Z(i);
953
+ let r = o + t.measureText(a).width;
954
+ i.yError && (r += t.measureText(` ± ${E(i.yError[0])}`).width), h = Math.max(h, r);
955
+ let d = `Index: ${i.dataIndex}`;
956
+ i.cycle !== void 0 && (d += ` │ Cycle: ${i.cycle}`), h = Math.max(h, t.measureText(d).width);
957
+ let f = 3;
958
+ (i.cycle !== void 0 || i.yError) && (f += 1);
959
+ const p = e.titleFontSize * e.lineHeight + e.headerGap, g = (f - 1) * e.contentFontSize * e.lineHeight, y = i.cycle !== void 0 ? e.itemGap : 0;
960
+ return this.cachedMeasurement = {
961
+ width: h,
962
+ height: p + g + y,
963
+ padding: e.padding
964
+ }, this.cachedKey = n, this.cachedMeasurement;
1028
965
  }
1029
966
  /**
1030
- * Calculate tooltip coordinates for a given position
967
+ * Render the tooltip
1031
968
  */
1032
- calculateCoordinates(t, i, e, n, s, o, l) {
1033
- let h, a, r;
1034
- switch (s) {
1035
- case "top":
1036
- h = t - e / 2, a = i - n - o.y - l, r = "bottom";
1037
- break;
1038
- case "bottom":
1039
- h = t - e / 2, a = i + o.y + l, r = "top";
1040
- break;
1041
- case "right":
1042
- h = t + o.x + l, a = i - n / 2, r = "left";
1043
- break;
1044
- case "left":
1045
- h = t - e - o.x - l, a = i - n / 2, r = "right";
1046
- break;
969
+ render(t, i, e, n, s) {
970
+ const { x: o, y: l } = e, { padding: h } = s;
971
+ let a = l + h.top, r = o + h.left;
972
+ if (t.save(), t.textAlign = "left", t.textBaseline = "top", s.showSeriesIndicator) {
973
+ const p = a + s.titleFontSize / 2;
974
+ t.fillStyle = i.seriesColor, t.beginPath(), t.arc(r + s.seriesIndicatorSize / 2, p, s.seriesIndicatorSize / 2, 0, Math.PI * 2), t.fill(), r += s.seriesIndicatorSize + 8;
1047
975
  }
1048
- return { x: h, y: a, arrowPosition: r };
976
+ t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor, t.fillText(i.seriesName, r, a), a += s.titleFontSize * s.lineHeight + 2, r = o + h.left, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(r, a + s.headerGap / 2), t.lineTo(r + n.width, a + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), a += s.headerGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
977
+ const d = 28;
978
+ t.fillStyle = s.textSecondaryColor, t.fillText("X:", r, a), t.fillStyle = s.textColor, t.fillText(Q(i), r + d, a), a += s.contentFontSize * s.lineHeight + 2, t.fillStyle = s.textSecondaryColor, t.fillText("Y:", r, a), t.fillStyle = s.textColor;
979
+ const f = Z(i);
980
+ if (t.fillText(f, r + d, a), i.yError) {
981
+ const p = t.measureText(f).width;
982
+ t.fillStyle = s.textSecondaryColor, t.fillText(` ± ${E(i.yError[0])}`, r + d + p, a);
983
+ }
984
+ if (a += s.contentFontSize * s.lineHeight + 2, i.cycle !== void 0) {
985
+ a += s.itemGap, t.strokeStyle = s.separatorColor, t.globalAlpha = 0.3, t.beginPath(), t.moveTo(r, a - s.itemGap / 2), t.lineTo(r + n.width, a - s.itemGap / 2), t.stroke(), t.globalAlpha = 1, t.fillStyle = s.textSecondaryColor, t.font = `400 ${s.contentFontSize - 1}px ${s.fontFamily}`;
986
+ let p = `Index: ${i.dataIndex}`;
987
+ i.cycle !== void 0 && (p += ` │ Cycle: ${i.cycle}`), t.fillText(p, r, a);
988
+ }
989
+ t.restore();
990
+ }
991
+ }
992
+ const lt = new zt();
993
+ function X(c) {
994
+ if (c == null || isNaN(c))
995
+ return "N/A";
996
+ const t = Math.abs(c);
997
+ return t === 0 ? "0" : t < 1e-4 || t >= 1e4 ? c.toExponential(2) : t < 1 ? c.toFixed(4) : c.toFixed(2);
998
+ }
999
+ class Yt {
1000
+ constructor() {
1001
+ u(this, "id", "annotation");
1002
+ u(this, "name", "Annotation Info");
1003
+ u(this, "supportedTypes", ["annotation"]);
1004
+ // Cache
1005
+ u(this, "cachedKey", "");
1006
+ u(this, "cachedMeasurement", null);
1049
1007
  }
1050
1008
  /**
1051
- * Get the opposite position for flipping
1009
+ * Measure tooltip dimensions
1052
1010
  */
1053
- flipPosition(t) {
1054
- switch (t) {
1055
- case "top":
1056
- return "bottom";
1057
- case "bottom":
1058
- return "top";
1059
- case "left":
1060
- return "right";
1061
- case "right":
1062
- return "left";
1011
+ measure(t, i, e) {
1012
+ const n = `${i.annotationId}-${i.label}-${e.fontFamily}-${e.titleFontSize}`;
1013
+ if (n === this.cachedKey && this.cachedMeasurement)
1014
+ return this.cachedMeasurement;
1015
+ t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
1016
+ const s = i.label || `Annotation: ${i.annotationType}`;
1017
+ let o = t.measureText(s).width;
1018
+ t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
1019
+ let l = 0;
1020
+ const h = 50;
1021
+ if (i.value !== void 0) {
1022
+ const r = X(i.value);
1023
+ o = Math.max(o, h + t.measureText(r).width), l += e.contentFontSize * e.lineHeight;
1024
+ }
1025
+ if (i.valueX !== void 0 && i.valueY !== void 0) {
1026
+ const r = X(i.valueX), d = X(i.valueY);
1027
+ o = Math.max(o, h + t.measureText(r).width, h + t.measureText(d).width), l += e.contentFontSize * e.lineHeight * 2;
1063
1028
  }
1029
+ const a = e.titleFontSize * e.lineHeight + e.headerGap;
1030
+ return this.cachedMeasurement = {
1031
+ width: o + 10,
1032
+ height: a + l + 4,
1033
+ padding: e.padding
1034
+ }, this.cachedKey = n, this.cachedMeasurement;
1064
1035
  }
1065
1036
  /**
1066
- * Check if tooltip fits within bounds
1037
+ * Render the tooltip
1067
1038
  */
1068
- fitsInBounds(t, i, e, n) {
1069
- const s = this.getConstraintBounds();
1070
- return t >= s.left && i >= s.top && t + e <= s.right && i + n <= s.bottom;
1039
+ render(t, i, e, n, s) {
1040
+ const { x: o, y: l } = e, { padding: h } = s;
1041
+ let a = l + h.top;
1042
+ const r = o + h.left;
1043
+ t.save(), t.textAlign = "left", t.textBaseline = "top", t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor;
1044
+ const d = i.label || `${i.annotationType.toUpperCase()}`;
1045
+ t.fillText(d, r, a), a += s.titleFontSize * s.lineHeight + 2, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(r, a + s.headerGap / 2), t.lineTo(r + n.width, a + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), a += s.headerGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
1046
+ const f = 50;
1047
+ i.value !== void 0 && (t.fillStyle = s.textSecondaryColor, t.fillText("Value:", r, a), t.fillStyle = s.textColor, t.fillText(X(i.value), r + f, a), a += s.contentFontSize * s.lineHeight), i.valueX !== void 0 && i.valueY !== void 0 && (t.fillStyle = s.textSecondaryColor, t.fillText("X:", r, a), t.fillStyle = s.textColor, t.fillText(X(i.valueX), r + f, a), a += s.contentFontSize * s.lineHeight, t.fillStyle = s.textSecondaryColor, t.fillText("Y:", r, a), t.fillStyle = s.textColor, t.fillText(X(i.valueY), r + f, a)), t.restore();
1071
1048
  }
1072
- /**
1073
- * Constrain position to stay within bounds
1074
- */
1075
- constrainToBounds(t, i, e, n) {
1076
- const s = this.getConstraintBounds(), o = 4;
1077
- let l = t, h = i;
1078
- return l < s.left + o ? l = s.left + o : l + e > s.right - o && (l = s.right - e - o), h < s.top + o ? h = s.top + o : h + n > s.bottom - o && (h = s.bottom - n - o), { x: l, y: h };
1049
+ }
1050
+ const K = new Yt();
1051
+ function M(c) {
1052
+ if (c == null || isNaN(c))
1053
+ return "N/A";
1054
+ const t = Math.abs(c);
1055
+ return t === 0 ? "0" : t < 1e-4 || t >= 1e4 ? c.toExponential(3) : t < 1 ? c.toFixed(4) : c.toFixed(3);
1056
+ }
1057
+ class At {
1058
+ constructor() {
1059
+ u(this, "id", "range");
1060
+ u(this, "name", "Range Statistics");
1061
+ u(this, "supportedTypes", ["range"]);
1062
+ // Cache
1063
+ u(this, "cachedKey", "");
1064
+ u(this, "cachedMeasurement", null);
1079
1065
  }
1080
1066
  /**
1081
- * Get the bounds to constrain tooltip within
1067
+ * Measure tooltip dimensions
1082
1068
  */
1083
- getConstraintBounds() {
1084
- let t = {
1085
- top: 0,
1086
- left: 0,
1087
- right: this.containerWidth,
1088
- bottom: this.containerHeight
1089
- };
1090
- return this.config.constrainToPlotArea && this.plotArea && (t = {
1091
- top: Math.max(t.top, this.plotArea.y),
1092
- left: Math.max(t.left, this.plotArea.x),
1093
- right: Math.min(t.right, this.plotArea.x + this.plotArea.width),
1094
- bottom: Math.min(t.bottom, this.plotArea.y + this.plotArea.height)
1095
- }), t;
1069
+ measure(t, i, e) {
1070
+ const n = i.statistics, s = `${i.xMin}-${i.xMax}-${n.count}-${n.mean}-${e.fontFamily}`;
1071
+ if (s === this.cachedKey && this.cachedMeasurement)
1072
+ return this.cachedMeasurement;
1073
+ t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
1074
+ let l = t.measureText("Range Statistics").width;
1075
+ t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
1076
+ let h = e.contentFontSize * e.lineHeight * 2;
1077
+ const a = [
1078
+ `Range: ${M(i.xMin)} to ${M(i.xMax)}`,
1079
+ `Count: ${n.count}`
1080
+ ];
1081
+ n.min !== void 0 && a.push(`Min: ${M(n.min)}`), n.max !== void 0 && a.push(`Max: ${M(n.max)}`), n.mean !== void 0 && a.push(`Mean: ${M(n.mean)}`), n.area !== void 0 && a.push(`Area: ${M(n.area)}`);
1082
+ for (const d of a)
1083
+ l = Math.max(l, t.measureText(d).width + 10);
1084
+ h = a.length * e.contentFontSize * e.lineHeight;
1085
+ const r = e.titleFontSize * e.lineHeight + e.headerGap;
1086
+ return this.cachedMeasurement = {
1087
+ width: Math.max(l, 140),
1088
+ height: r + h + 10,
1089
+ padding: e.padding
1090
+ }, this.cachedKey = s, this.cachedMeasurement;
1096
1091
  }
1097
1092
  /**
1098
- * Calculate position for crosshair tooltip (follows cursor vertically)
1093
+ * Render the tooltip
1099
1094
  */
1100
- calculateCrosshairPosition(t, i, e) {
1101
- const { offset: n } = this.config, s = this.getConstraintBounds(), o = e.width + e.padding.left + e.padding.right, l = e.height + e.padding.top + e.padding.bottom;
1102
- let h = t + n.x, a = "left";
1103
- h + o > s.right - 4 && (h = t - o - n.x, a = "right");
1104
- let r = i - l / 2;
1105
- r = Math.max(s.top + 4, Math.min(s.bottom - l - 4, r));
1106
- const d = i - r;
1107
- return {
1108
- x: h,
1109
- y: r,
1110
- arrowPosition: a,
1111
- arrowOffset: Math.max(8, Math.min(l - 8, d)),
1112
- wasFlipped: a === "right"
1095
+ render(t, i, e, n, s) {
1096
+ const { x: o, y: l } = e, { padding: h } = s, a = i.statistics;
1097
+ let r = l + h.top;
1098
+ const d = o + h.left;
1099
+ t.save(), t.textAlign = "left", t.textBaseline = "top", t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor, t.fillText("Range Statistics", d, r), r += s.titleFontSize * s.lineHeight + 2, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(d, r + s.headerGap / 2), t.lineTo(d + n.width, r + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), r += s.headerGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
1100
+ const f = 60, p = (g, y) => {
1101
+ t.fillStyle = s.textSecondaryColor, t.fillText(g, d, r), t.fillStyle = s.textColor, t.fillText(String(y), d + f, r), r += s.contentFontSize * s.lineHeight;
1113
1102
  };
1103
+ p("Points:", a.count), a.min !== void 0 && p("Min:", M(a.min)), a.max !== void 0 && p("Max:", M(a.max)), a.mean !== void 0 && p("Mean:", M(a.mean)), a.stdDev !== void 0 && p("StdDev:", M(a.stdDev)), a.area !== void 0 && p("Area:", M(a.area)), a.peakX !== void 0 && (r += 4, t.fillStyle = s.textSecondaryColor, t.font = `italic ${s.contentFontSize - 1}px ${s.fontFamily}`, t.fillText(`Peak: ${M(a.peakY ?? 0)} at X=${M(a.peakX)}`, d, r)), t.restore();
1114
1104
  }
1115
1105
  }
1116
- class vt {
1117
- constructor(t, i) {
1106
+ const U = new At(), Xt = [
1107
+ H,
1108
+ at,
1109
+ G,
1110
+ _,
1111
+ lt,
1112
+ K,
1113
+ U
1114
+ ];
1115
+ function $t(c) {
1116
+ return Xt.find((t) => t.id === c);
1117
+ }
1118
+ function tt(c) {
1119
+ switch (c) {
1120
+ case "datapoint":
1121
+ return H;
1122
+ case "crosshair":
1123
+ return G;
1124
+ case "heatmap":
1125
+ return _;
1126
+ case "annotation":
1127
+ return K;
1128
+ case "range":
1129
+ return U;
1130
+ default:
1131
+ return H;
1132
+ }
1133
+ }
1134
+ class Dt {
1135
+ constructor(t) {
1118
1136
  u(this, "ctx");
1137
+ u(this, "getPlotArea");
1138
+ u(this, "getSeries");
1139
+ u(this, "pixelToDataX");
1140
+ u(this, "pixelToDataY");
1141
+ u(this, "getXScale");
1142
+ u(this, "getYScales");
1143
+ u(this, "getViewBounds");
1144
+ u(this, "getXAxisOptions");
1145
+ u(this, "getYAxisOptions");
1146
+ u(this, "options");
1119
1147
  u(this, "theme");
1120
- this.ctx = t, this.theme = i;
1148
+ u(this, "positioner");
1149
+ u(this, "renderer");
1150
+ u(this, "templates", /* @__PURE__ */ new Map());
1151
+ u(this, "events", new ut());
1152
+ u(this, "activeTooltips", /* @__PURE__ */ new Map());
1153
+ u(this, "tooltipIdCounter", 0);
1154
+ u(this, "showTimeoutId", null);
1155
+ u(this, "hideTimeoutId", null);
1156
+ u(this, "lastCursorX", null);
1157
+ u(this, "lastCursorY", null);
1158
+ u(this, "hoveredSeriesId", null);
1159
+ u(this, "hoveredDataIndex", null);
1160
+ u(this, "cachedNearestResult", null);
1161
+ // Large dataset optimizations
1162
+ u(this, "snapMode", "auto");
1163
+ u(this, "largeDatasetThreshold", 5e4);
1164
+ u(this, "lastKnownDataSize", 0);
1165
+ // Hysteresis factor: new point must be this much closer to switch (2 = 2x closer)
1166
+ u(this, "hysteresisRatio", 2);
1167
+ // Suspended state - used during drag operations to hide tooltip
1168
+ u(this, "suspended", !1);
1169
+ var i;
1170
+ this.ctx = t.overlayCtx, this.getPlotArea = t.getPlotArea, this.getSeries = t.getSeries, this.pixelToDataX = t.pixelToDataX, this.pixelToDataY = t.pixelToDataY, this.getXScale = t.getXScale, this.getYScales = t.getYScales, this.getViewBounds = t.getViewBounds, this.getXAxisOptions = t.getXAxisOptions, this.getYAxisOptions = t.getYAxisOptions, this.options = {
1171
+ enabled: !0,
1172
+ showDelay: 50,
1173
+ hideDelay: 100,
1174
+ followCursor: !1,
1175
+ offset: { x: 12, y: 12 },
1176
+ dataPoint: {
1177
+ enabled: !0,
1178
+ templateId: "default",
1179
+ snapToPoint: !0,
1180
+ hitRadius: 20
1181
+ },
1182
+ crosshair: {
1183
+ enabled: !1,
1184
+ templateId: "crosshair",
1185
+ interpolate: !0,
1186
+ visibleSeriesOnly: !0
1187
+ },
1188
+ range: {
1189
+ enabled: !1,
1190
+ templateId: "default",
1191
+ calculateStats: !0
1192
+ },
1193
+ annotation: {
1194
+ enabled: !0,
1195
+ templateId: "default"
1196
+ },
1197
+ heatmap: {
1198
+ enabled: !0,
1199
+ templateId: "heatmap",
1200
+ showColorInfo: !0
1201
+ },
1202
+ positioning: "auto",
1203
+ preferredPosition: "auto",
1204
+ constrainToPlotArea: !0,
1205
+ constrainToContainer: !0,
1206
+ autoFlip: !0
1207
+ }, this.theme = (i = t.options) != null && i.theme ? typeof t.options.theme == "string" ? B(t.options.theme) : bt(St, t.options.theme) : B(t.chartTheme.name), this.positioner = new Tt({
1208
+ offset: this.options.offset,
1209
+ preferredPosition: this.options.preferredPosition,
1210
+ constrainToPlotArea: this.options.constrainToPlotArea,
1211
+ constrainToContainer: this.options.constrainToContainer,
1212
+ autoFlip: this.options.autoFlip,
1213
+ showArrow: this.theme.showArrow,
1214
+ arrowSize: this.theme.arrowSize
1215
+ }), this.renderer = new wt(this.ctx, this.theme), this.registerBuiltinTemplates(), t.options && this.configure(t.options);
1216
+ }
1217
+ /**
1218
+ * Register built-in templates
1219
+ */
1220
+ registerBuiltinTemplates() {
1221
+ this.templates.set(
1222
+ "default",
1223
+ H
1224
+ ), this.templates.set(
1225
+ "minimal",
1226
+ at
1227
+ ), this.templates.set(
1228
+ "crosshair",
1229
+ G
1230
+ ), this.templates.set(
1231
+ "heatmap",
1232
+ _
1233
+ ), this.templates.set(
1234
+ "scientific",
1235
+ lt
1236
+ ), this.templates.set(
1237
+ "annotation",
1238
+ K
1239
+ ), this.templates.set(
1240
+ "range",
1241
+ U
1242
+ );
1121
1243
  }
1122
1244
  /**
1123
- * Update the theme
1245
+ * Configure tooltip options
1124
1246
  */
1125
- setTheme(t) {
1126
- this.theme = t;
1247
+ configure(t) {
1248
+ t.enabled !== void 0 && (this.options.enabled = t.enabled), t.showDelay !== void 0 && (this.options.showDelay = t.showDelay), t.hideDelay !== void 0 && (this.options.hideDelay = t.hideDelay), t.followCursor !== void 0 && (this.options.followCursor = t.followCursor), t.offset && (this.options.offset = { ...this.options.offset, ...t.offset }), t.dataPoint && (this.options.dataPoint = {
1249
+ ...this.options.dataPoint,
1250
+ ...t.dataPoint
1251
+ }), t.crosshair && (this.options.crosshair = {
1252
+ ...this.options.crosshair,
1253
+ ...t.crosshair
1254
+ }), t.heatmap && (this.options.heatmap = { ...this.options.heatmap, ...t.heatmap }), t.preferredPosition && (this.options.preferredPosition = t.preferredPosition), t.constrainToPlotArea !== void 0 && (this.options.constrainToPlotArea = t.constrainToPlotArea), t.constrainToContainer !== void 0 && (this.options.constrainToContainer = t.constrainToContainer), t.autoFlip !== void 0 && (this.options.autoFlip = t.autoFlip), t.snapMode !== void 0 && (this.snapMode = t.snapMode), t.largeDatasetThreshold !== void 0 && (this.largeDatasetThreshold = Math.max(
1255
+ 1e3,
1256
+ t.largeDatasetThreshold
1257
+ )), this.positioner.configure({
1258
+ offset: this.options.offset,
1259
+ preferredPosition: this.options.preferredPosition,
1260
+ constrainToPlotArea: this.options.constrainToPlotArea,
1261
+ constrainToContainer: this.options.constrainToContainer,
1262
+ autoFlip: this.options.autoFlip
1263
+ });
1127
1264
  }
1128
1265
  /**
1129
- * Render a tooltip with the given template
1266
+ * Get current options
1130
1267
  */
1131
- render(t, i, e) {
1132
- const n = e.measure(this.ctx, t, this.theme), s = n.width + n.padding.left + n.padding.right, o = n.height + n.padding.top + n.padding.bottom;
1133
- this.ctx.save(), this.drawBackground(i.x, i.y, s, o, i.arrowPosition || "none"), i.arrowPosition && i.arrowPosition !== "none" && this.drawArrow(
1134
- i.x,
1135
- i.y,
1136
- s,
1137
- o,
1138
- i.arrowPosition,
1139
- i.arrowOffset || 0
1140
- ), e.render(this.ctx, t, i, n, this.theme), this.ctx.restore();
1268
+ getOptions() {
1269
+ return { ...this.options };
1141
1270
  }
1142
1271
  /**
1143
- * Draw tooltip background with all effects
1272
+ * Enable/disable tooltips
1144
1273
  */
1145
- drawBackground(t, i, e, n, s) {
1146
- const { ctx: o, theme: l } = this, h = l.borderRadius;
1147
- if (o.save(), l.shadow.blur > 0 && (o.shadowColor = l.shadow.color, o.shadowOffsetX = l.shadow.offsetX, o.shadowOffsetY = l.shadow.offsetY, o.shadowBlur = l.shadow.blur), o.beginPath(), this.roundRect(t, i, e, n, h), l.backgroundGradient) {
1148
- const a = o.createLinearGradient(t, i, t, i + n);
1149
- a.addColorStop(0, l.backgroundColor), a.addColorStop(1, l.backgroundGradient), o.fillStyle = a;
1150
- } else
1151
- o.fillStyle = l.backgroundColor;
1152
- o.fill(), o.shadowColor = "transparent", l.borderWidth > 0 && (o.strokeStyle = l.borderColor, o.lineWidth = l.borderWidth, o.stroke()), o.restore();
1274
+ setEnabled(t) {
1275
+ this.options.enabled = t, t || this.hideAll();
1153
1276
  }
1154
1277
  /**
1155
- * Draw arrow pointing to target
1278
+ * Check if tooltips are enabled
1156
1279
  */
1157
- drawArrow(t, i, e, n, s, o) {
1158
- const { ctx: l, theme: h } = this, a = h.arrowSize;
1159
- if (!(a <= 0 || s === "none")) {
1160
- switch (l.save(), l.fillStyle = h.backgroundColor, l.strokeStyle = h.borderColor, l.lineWidth = h.borderWidth, l.beginPath(), s) {
1161
- case "top":
1162
- const r = t + o;
1163
- l.moveTo(r - a, i), l.lineTo(r, i - a), l.lineTo(r + a, i);
1164
- break;
1165
- case "bottom":
1166
- const d = t + o, f = i + n;
1167
- l.moveTo(d - a, f), l.lineTo(d, f + a), l.lineTo(d + a, f);
1168
- break;
1169
- case "left":
1170
- const p = i + o;
1171
- l.moveTo(t, p - a), l.lineTo(t - a, p), l.lineTo(t, p + a);
1172
- break;
1173
- case "right":
1174
- const g = t + e, y = i + o;
1175
- l.moveTo(g, y - a), l.lineTo(g + a, y), l.lineTo(g, y + a);
1176
- break;
1177
- }
1178
- l.closePath(), l.fill(), h.borderWidth > 0 && l.stroke(), l.restore();
1179
- }
1280
+ isEnabled() {
1281
+ return this.options.enabled;
1180
1282
  }
1181
1283
  /**
1182
- * Draw a rounded rectangle path
1284
+ * Suspend tooltip display (used during drag operations)
1285
+ * When suspended, tooltips are immediately hidden and cursor movements are ignored
1183
1286
  */
1184
- roundRect(t, i, e, n, s) {
1185
- const { ctx: o } = this, l = Math.min(s, e / 2, n / 2);
1186
- o.moveTo(t + l, i), o.lineTo(t + e - l, i), o.arcTo(t + e, i, t + e, i + l, l), o.lineTo(t + e, i + n - l), o.arcTo(t + e, i + n, t + e - l, i + n, l), o.lineTo(t + l, i + n), o.arcTo(t, i + n, t, i + n - l, l), o.lineTo(t, i + l), o.arcTo(t, i, t + l, i, l), o.closePath();
1287
+ setSuspended(t) {
1288
+ this.suspended = t, t && this.hideAll();
1187
1289
  }
1188
1290
  /**
1189
- * Draw a horizontal separator line
1291
+ * Check if tooltips are suspended
1190
1292
  */
1191
- drawSeparator(t, i, e) {
1192
- const { ctx: n, theme: s } = this;
1193
- s.showHeaderSeparator && (n.save(), n.strokeStyle = s.separatorColor, n.lineWidth = 1, n.beginPath(), n.moveTo(t, i), n.lineTo(t + e, i), n.stroke(), n.restore());
1293
+ isSuspended() {
1294
+ return this.suspended;
1194
1295
  }
1195
1296
  /**
1196
- * Draw a series color indicator (small circle or square)
1297
+ * Set tooltip theme
1197
1298
  */
1198
- drawSeriesIndicator(t, i, e) {
1199
- const { ctx: n, theme: s } = this;
1200
- if (!s.showSeriesIndicator) return;
1201
- const l = s.seriesIndicatorSize / 2;
1202
- n.save(), n.fillStyle = e, n.beginPath(), n.arc(t + l, i, l, 0, Math.PI * 2), n.fill(), n.restore();
1299
+ setTheme(t) {
1300
+ this.theme = typeof t == "string" ? B(t) : t, this.renderer.setTheme(this.theme), this.positioner.configure({
1301
+ showArrow: this.theme.showArrow,
1302
+ arrowSize: this.theme.arrowSize
1303
+ });
1203
1304
  }
1204
1305
  /**
1205
- * Measure text width
1306
+ * Update theme based on chart theme
1206
1307
  */
1207
- measureText(t, i, e = 400) {
1208
- const { ctx: n, theme: s } = this;
1209
- return n.font = `${e} ${i}px ${s.fontFamily}`, n.measureText(t).width;
1308
+ updateChartTheme(t) {
1309
+ this.options.theme || (this.theme = B(t.name), this.renderer.setTheme(this.theme));
1210
1310
  }
1211
1311
  /**
1212
- * Draw text with theme styling
1312
+ * Get current theme
1213
1313
  */
1214
- drawText(t, i, e, n = {}) {
1215
- const { ctx: s, theme: o } = this, {
1216
- fontSize: l = o.contentFontSize,
1217
- fontWeight: h = 400,
1218
- color: a = o.textColor,
1219
- align: r = "left",
1220
- baseline: d = "top"
1221
- } = n;
1222
- s.save(), s.font = `${h} ${l}px ${o.fontFamily}`, s.fillStyle = a, s.textAlign = r, s.textBaseline = d, s.fillText(t, i, e), s.restore();
1314
+ getTheme() {
1315
+ return this.theme;
1223
1316
  }
1224
1317
  /**
1225
- * Draw a label-value pair
1318
+ * Register a custom template
1226
1319
  */
1227
- drawLabelValue(t, i, e, n, s) {
1228
- const { theme: o } = this;
1229
- this.drawText(e, t, i, {
1230
- color: o.textSecondaryColor,
1231
- fontSize: o.contentFontSize
1232
- }), this.drawText(n, t + s, i, {
1233
- color: o.textColor,
1234
- fontSize: o.contentFontSize,
1235
- align: "right"
1236
- });
1320
+ registerTemplate(t) {
1321
+ this.templates.set(t.id, t);
1237
1322
  }
1238
1323
  /**
1239
- * Get theme for external use
1324
+ * Get a template by ID
1240
1325
  */
1241
- getTheme() {
1242
- return this.theme;
1243
- }
1244
- }
1245
- function ot(c) {
1246
- return c.axisFormat ? O(c.dataX, c.axisFormat) : W(c.dataX);
1247
- }
1248
- function nt(c) {
1249
- return c.axisFormat ? N(c.dataY, c.axisFormat) : W(c.dataY);
1250
- }
1251
- function Ct(c) {
1252
- return c.axisFormat ? O(c.dataX, c.axisFormat) : W(c.dataX);
1253
- }
1254
- function Pt(c, t) {
1255
- return t.axisFormat ? N(c, t.axisFormat) : W(c);
1256
- }
1257
- function W(c) {
1258
- if (c == null || isNaN(c)) return "N/A";
1259
- const t = Math.abs(c);
1260
- return t === 0 ? "0" : t < 1e-4 || t >= 1e4 ? c.toExponential(2) : t < 0.01 ? c.toPrecision(4) : t < 1 ? c.toFixed(4) : t < 100 ? c.toFixed(3) : c.toFixed(1);
1261
- }
1262
- function k(c, t) {
1263
- const i = t === "x" ? c.dataX : c.dataY;
1264
- if (c.axisFormat)
1265
- return t === "x" ? O(i, c.axisFormat) : N(i, c.axisFormat);
1266
- if (i == null || isNaN(i)) return "N/A";
1267
- const e = Math.abs(i);
1268
- return e === 0 ? "0" : e >= 1e9 ? (i / 1e9).toFixed(1) + "G" : e >= 1e6 ? (i / 1e6).toFixed(1) + "M" : e >= 1e3 ? (i / 1e3).toFixed(1) + "k" : e >= 1 ? i.toFixed(2) : e >= 1e-3 ? (i * 1e3).toFixed(1) + "m" : e >= 1e-6 ? (i * 1e6).toFixed(1) + "µ" : e >= 1e-9 ? (i * 1e9).toFixed(1) + "n" : i.toExponential(1);
1269
- }
1270
- function z(c, t, i) {
1271
- if (t && i === "x") return ot(t);
1272
- if (t && i === "y") return nt(t);
1273
- if (c == null || isNaN(c)) return "N/A";
1274
- const e = Math.abs(c);
1275
- return e !== 0 && (e < 1e-4 || e >= 1e4) ? c.toExponential(2) : e < 0.01 ? c.toPrecision(4) : e < 1 ? c.toFixed(4) : e < 100 ? c.toFixed(3) : c.toFixed(1);
1276
- }
1277
- class Ft {
1278
- constructor() {
1279
- u(this, "id", "default");
1280
- u(this, "name", "Default Data Point");
1281
- u(this, "supportedTypes", ["datapoint"]);
1282
- // Cached measurements
1283
- u(this, "cachedKey", "");
1284
- u(this, "cachedMeasurement", null);
1326
+ getTemplate(t) {
1327
+ return this.templates.get(t) || $t(t);
1285
1328
  }
1286
1329
  /**
1287
- * Measure tooltip dimensions
1330
+ * Handle cursor movement - main entry point for hover detection
1331
+ * Optimized with hysteresis to prevent jumping between nearby points
1288
1332
  */
1289
- measure(t, i, e) {
1290
- const n = `${i.seriesId}-${i.dataIndex}-${i.dataX}-${i.dataY}`;
1291
- if (n === this.cachedKey && this.cachedMeasurement)
1292
- return this.cachedMeasurement;
1293
- t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
1294
- const s = t.measureText(i.seriesName).width;
1295
- t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
1296
- const o = 35, l = t.measureText(z(i.dataX, i, "x")).width, h = t.measureText(z(i.dataY, i, "y")).width;
1297
- let a = o + Math.max(l, h), r = e.contentFontSize * e.lineHeight * 2;
1298
- if (i.yError) {
1299
- const g = `± ${z(i.yError[0])} / ${z(i.yError[1])}`;
1300
- a = Math.max(a, t.measureText(g).width), r += e.contentFontSize * e.lineHeight;
1301
- }
1302
- if (i.cycle !== void 0) {
1303
- const g = `Cycle: ${i.cycle}`;
1304
- a = Math.max(a, t.measureText(g).width), r += e.contentFontSize * e.lineHeight;
1333
+ handleCursorMove(t, i) {
1334
+ if (!this.options.enabled || this.suspended) return;
1335
+ this.lastCursorX = t, this.lastCursorY = i;
1336
+ const e = this.getPlotArea();
1337
+ if (!(t >= e.x && t <= e.x + e.width && i >= e.y && i <= e.y + e.height)) {
1338
+ this.cachedNearestResult = null, this.scheduleHide();
1339
+ return;
1305
1340
  }
1306
- const d = e.showSeriesIndicator ? e.seriesIndicatorSize + 6 : 0, f = Math.max(s + d, a), p = e.titleFontSize * e.lineHeight + e.headerGap + r;
1307
- return this.cachedMeasurement = {
1308
- width: f + 2,
1309
- // Tiny buffer
1310
- height: p + 4,
1311
- padding: e.padding,
1312
- arrow: e.showArrow ? { width: e.arrowSize * 2, height: e.arrowSize } : void 0
1313
- }, this.cachedKey = n, this.cachedMeasurement;
1341
+ this.positioner.setContainerSize(
1342
+ this.ctx.canvas.width / (window.devicePixelRatio || 1),
1343
+ this.ctx.canvas.height / (window.devicePixelRatio || 1)
1344
+ ), this.positioner.setPlotArea(e), this.lastKnownDataSize = this.getTotalDataPoints(), this.performTooltipUpdateWithHysteresis(t, i);
1314
1345
  }
1315
1346
  /**
1316
- * Render the tooltip
1347
+ * Handle cursor leave
1317
1348
  */
1318
- render(t, i, e, n, s) {
1319
- const { x: o, y: l } = e, { padding: h } = s;
1320
- let a = o + h.left, r = l + h.top;
1321
- if (t.save(), t.textAlign = "left", t.textBaseline = "top", s.showSeriesIndicator) {
1322
- const f = r + s.titleFontSize / 2;
1323
- t.save(), t.fillStyle = i.seriesColor, t.beginPath(), t.arc(a + s.seriesIndicatorSize / 2, f, s.seriesIndicatorSize / 2, 0, Math.PI * 2), t.fill(), t.restore(), a += s.seriesIndicatorSize + 6;
1324
- }
1325
- if (t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor, t.fillText(i.seriesName, a, r), r += s.titleFontSize * s.lineHeight + 2, s.showHeaderSeparator) {
1326
- const f = n.width;
1327
- t.save(), t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(o + h.left, r + s.headerGap / 2), t.lineTo(o + h.left + f, r + s.headerGap / 2), t.stroke(), t.restore();
1328
- }
1329
- r += s.headerGap + 2, a = o + h.left, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`, t.fillStyle = s.textSecondaryColor, t.fillText("X:", a, r), t.fillStyle = s.textColor;
1330
- const d = 35;
1331
- if (t.fillText(z(i.dataX, i, "x"), a + d, r), r += s.contentFontSize * s.lineHeight + 2, t.fillStyle = s.textSecondaryColor, t.fillText("Y:", a, r), t.fillStyle = s.textColor, t.fillText(z(i.dataY, i, "y"), a + d, r), r += s.contentFontSize * s.lineHeight + 2, i.yError) {
1332
- t.fillStyle = s.textSecondaryColor;
1333
- const f = `± ${z(i.yError[0])} / +${z(i.yError[1])}`;
1334
- t.fillText(f, a, r), r += s.contentFontSize * s.lineHeight;
1349
+ handleCursorLeave() {
1350
+ this.lastCursorX = null, this.lastCursorY = null, this.cachedNearestResult = null, this.scheduleHide();
1351
+ }
1352
+ /**
1353
+ * Get total number of data points across all visible series
1354
+ */
1355
+ getTotalDataPoints() {
1356
+ let t = 0;
1357
+ const i = this.getSeries();
1358
+ for (const e of i) {
1359
+ if (!e.isVisible()) continue;
1360
+ const n = e.getData();
1361
+ n.x && (t += n.x.length);
1335
1362
  }
1336
- i.cycle !== void 0 && (t.fillStyle = s.textSecondaryColor, t.fillText("Cycle:", a, r), t.fillStyle = s.textColor, t.fillText(String(i.cycle), a + 45, r)), t.restore();
1363
+ return t;
1337
1364
  }
1338
- }
1339
- const H = new Ft();
1340
- class It {
1341
- constructor() {
1342
- u(this, "id", "minimal");
1343
- u(this, "name", "Minimal");
1344
- u(this, "supportedTypes", ["datapoint"]);
1345
- // Cached measurements
1346
- u(this, "cachedKey", "");
1347
- u(this, "cachedMeasurement", null);
1365
+ /**
1366
+ * Determine the effective snap mode based on configuration and data size
1367
+ */
1368
+ getEffectiveSnapMode() {
1369
+ return this.snapMode === "x-only" ? "x-only" : this.snapMode === "nearest" ? "nearest" : this.lastKnownDataSize > this.largeDatasetThreshold ? "x-only" : "nearest";
1348
1370
  }
1349
1371
  /**
1350
- * Measure tooltip dimensions
1372
+ * Perform tooltip update with hysteresis to prevent jumping.
1373
+ * The tooltip "sticks" to the current point unless the cursor is
1374
+ * significantly closer to a new point.
1351
1375
  */
1352
- measure(t, i, e) {
1353
- const n = `${i.seriesId}-${i.dataIndex}-${i.dataX}-${i.dataY}-${e.fontFamily}`;
1354
- if (n === this.cachedKey && this.cachedMeasurement)
1355
- return this.cachedMeasurement;
1356
- t.font = `${e.contentFontSize}px ${e.fontFamily}`;
1357
- const s = `${k(i, "x")} ▪ ${k(i, "y")}`, o = t.measureText(s).width;
1358
- return this.cachedMeasurement = {
1359
- width: o,
1360
- height: e.contentFontSize * e.lineHeight,
1361
- padding: {
1362
- top: 6,
1363
- right: 8,
1364
- bottom: 6,
1365
- left: 8
1376
+ performTooltipUpdateWithHysteresis(t, i) {
1377
+ var s;
1378
+ if (!((s = this.options.dataPoint) != null && s.enabled)) {
1379
+ this.performFallbackTooltipUpdate(t, i);
1380
+ return;
1381
+ }
1382
+ const n = this.getEffectiveSnapMode() === "x-only" ? this.findDataPointByXOnly(t) : this.findNearestDataPointOptimized(t, i);
1383
+ if (!n) {
1384
+ this.performFallbackTooltipUpdate(t, i);
1385
+ return;
1386
+ }
1387
+ if (this.cachedNearestResult && this.hoveredSeriesId !== null) {
1388
+ const o = this.cachedNearestResult, l = o.pixelX - t, h = o.pixelY - i, a = l * l + h * h, r = n.pixelX - t, d = n.pixelY - i;
1389
+ if ((r * r + d * d) * this.hysteresisRatio >= a) {
1390
+ this.updateTooltipPosition(this.cachedNearestResult);
1391
+ return;
1366
1392
  }
1367
- }, this.cachedKey = n, this.cachedMeasurement;
1393
+ }
1394
+ this.cachedNearestResult = n, this.scheduleShow(n);
1368
1395
  }
1369
1396
  /**
1370
- * Render the tooltip
1397
+ * Handle heatmap and crosshair tooltip types (fallback)
1371
1398
  */
1372
- render(t, i, e, n, s) {
1373
- const { padding: o } = n, l = e.x + o.left, h = e.y + o.top, a = k(i, "x"), r = k(i, "y");
1374
- t.save(), t.font = `${s.contentFontSize}px ${s.fontFamily}`, t.textAlign = "left", t.textBaseline = "top", t.fillStyle = s.textSecondaryColor, t.fillText(a, l, h);
1375
- const d = t.measureText(a).width;
1376
- t.fillStyle = i.seriesColor, t.fillText(" ▪ ", l + d, h);
1377
- const f = t.measureText(" ▪ ").width;
1378
- t.fillStyle = s.textColor, t.fillText(r, l + d + f, h), t.restore();
1399
+ performFallbackTooltipUpdate(t, i) {
1400
+ var e, n;
1401
+ if ((e = this.options.heatmap) != null && e.enabled) {
1402
+ const s = this.findHeatmapCell(t, i);
1403
+ if (s) {
1404
+ this.scheduleShow(s);
1405
+ return;
1406
+ }
1407
+ }
1408
+ if ((n = this.options.crosshair) != null && n.enabled) {
1409
+ const s = this.buildCrosshairTooltip(t, i);
1410
+ if (s) {
1411
+ this.scheduleShow(s);
1412
+ return;
1413
+ }
1414
+ }
1415
+ this.cachedNearestResult = null, this.scheduleHide();
1379
1416
  }
1380
- }
1381
- const at = new It(), F = {
1382
- INDICATOR_MARGIN: 8,
1383
- MIN_LABEL_WIDTH: 60,
1384
- LINE_SPACING: 2,
1385
- SAFETY_BUFFER: 4,
1386
- HEADER_SPACING: 2,
1387
- GAP_SPACING: 2
1388
- };
1389
- class zt {
1390
- constructor() {
1391
- u(this, "id", "crosshair");
1392
- u(this, "name", "Multi-Series Crosshair");
1393
- u(this, "supportedTypes", ["crosshair"]);
1394
- // Cached measurements
1395
- u(this, "cachedKey", "");
1396
- u(this, "cachedMeasurement", null);
1397
- u(this, "formattedData", null);
1417
+ buildAxisFormat(t) {
1418
+ var o, l;
1419
+ const i = this.getViewBounds(), e = i.xMax - i.xMin, n = (o = this.getXAxisOptions) == null ? void 0 : o.call(this), s = (l = this.getYAxisOptions) == null ? void 0 : l.call(this, t);
1420
+ if (!(!n && !s && !Number.isFinite(e)))
1421
+ return {
1422
+ x: n,
1423
+ y: s,
1424
+ xSpan: Number.isFinite(e) ? e : void 0
1425
+ };
1398
1426
  }
1399
1427
  /**
1400
- * Generate cache key from data and theme
1428
+ * Find data point by X coordinate only (O(log n) - fastest method)
1429
+ * Best for very large datasets where precision is less important than speed
1401
1430
  */
1402
- getCacheKey(t, i) {
1403
- const e = t.interpolatedValues.map((n) => `${n.seriesName}-${n.y}`).join("|");
1404
- return [
1405
- t.dataX,
1406
- e,
1407
- i.fontFamily,
1408
- i.titleFontSize,
1409
- i.contentFontSize,
1410
- i.showSeriesIndicator,
1411
- i.seriesIndicatorSize,
1412
- `${i.padding.top}-${i.padding.right}-${i.padding.bottom}-${i.padding.left}`
1413
- ].join(":");
1431
+ findDataPointByXOnly(t) {
1432
+ var h, a, r;
1433
+ const i = this.getSeries().filter(
1434
+ (d) => d.isVisible() && d.getType() !== "heatmap"
1435
+ );
1436
+ if (i.length === 0) return null;
1437
+ const e = this.getXScale(), n = this.getYScales(), s = this.pixelToDataX(t);
1438
+ let o = null, l = 1 / 0;
1439
+ for (const d of i) {
1440
+ const f = d.getData();
1441
+ if (!f.x || f.x.length === 0) continue;
1442
+ const p = n.get(((h = d.getYAxisId) == null ? void 0 : h.call(d)) || "default") || n.values().next().value;
1443
+ if (!p) continue;
1444
+ const g = this.binarySearchClosest(f.x, s), y = Math.abs(f.x[g] - s);
1445
+ if (y < l) {
1446
+ l = y;
1447
+ const b = d.getStyle(), x = e.transform(f.x[g]), S = p.transform(f.y[g]);
1448
+ o = {
1449
+ type: "datapoint",
1450
+ seriesId: d.getId(),
1451
+ seriesName: d.getId(),
1452
+ seriesColor: b.color || "#ff0055",
1453
+ dataIndex: g,
1454
+ dataX: f.x[g],
1455
+ dataY: f.y[g],
1456
+ pixelX: x,
1457
+ pixelY: S,
1458
+ cycle: (a = d.getCycle) == null ? void 0 : a.call(d),
1459
+ axisFormat: this.buildAxisFormat((r = d.getYAxisId) == null ? void 0 : r.call(d))
1460
+ };
1461
+ const T = d.getYError(g);
1462
+ T && (o.yError = T);
1463
+ }
1464
+ }
1465
+ return o;
1414
1466
  }
1415
1467
  /**
1416
- * Format data for rendering
1468
+ * Optimized nearest data point finder
1417
1469
  */
1418
- formatData(t) {
1419
- return {
1420
- header: `⌖ X = ${Ct(t)}`,
1421
- series: t.interpolatedValues.map((i) => ({
1422
- name: i.seriesName,
1423
- value: Pt(i.y, t),
1424
- color: i.seriesColor,
1425
- isInterpolated: i.isInterpolated
1426
- }))
1427
- };
1470
+ findNearestDataPointOptimized(t, i) {
1471
+ var r, d, f, p;
1472
+ const e = this.getSeries().filter((g) => g.isVisible()), n = ((r = this.options.dataPoint) == null ? void 0 : r.hitRadius) ?? 20, s = this.getXScale(), o = this.getYScales();
1473
+ let l = n * n, h = null;
1474
+ const a = this.pixelToDataX(t);
1475
+ for (const g of e) {
1476
+ if (g.getType() === "heatmap") continue;
1477
+ const y = g.getData(), b = o.get(((d = g.getYAxisId) == null ? void 0 : d.call(g)) || "default") || o.values().next().value;
1478
+ if (!b || !y.x || y.x.length === 0) continue;
1479
+ const x = this.binarySearchClosest(y.x, a), S = y.x.length, T = S > 1e5 ? 2 : S > 1e4 ? 3 : 5, v = Math.max(0, x - T), Y = Math.min(S, x + T + 1);
1480
+ for (let w = v; w < Y; w++) {
1481
+ const C = s.transform(y.x[w]), m = b.transform(y.y[w]), I = C - t, A = m - i, $ = I * I + A * A;
1482
+ if ($ < l) {
1483
+ l = $;
1484
+ const R = g.getStyle();
1485
+ h = {
1486
+ type: "datapoint",
1487
+ seriesId: g.getId(),
1488
+ seriesName: g.getId(),
1489
+ seriesColor: R.color || "#ff0055",
1490
+ dataIndex: w,
1491
+ dataX: y.x[w],
1492
+ dataY: y.y[w],
1493
+ pixelX: C,
1494
+ pixelY: m,
1495
+ cycle: (f = g.getCycle) == null ? void 0 : f.call(g),
1496
+ axisFormat: this.buildAxisFormat((p = g.getYAxisId) == null ? void 0 : p.call(g))
1497
+ };
1498
+ const D = g.getYError(w);
1499
+ D && (h.yError = D);
1500
+ }
1501
+ }
1502
+ }
1503
+ return h;
1428
1504
  }
1429
1505
  /**
1430
- * Measure tooltip dimensions
1506
+ * Find heatmap cell under cursor
1431
1507
  */
1432
- measure(t, i, e) {
1433
- if (!i.interpolatedValues || i.interpolatedValues.length === 0)
1508
+ findHeatmapCell(t, i) {
1509
+ var h, a;
1510
+ const e = this.getSeries().filter(
1511
+ (r) => r.isVisible() && r.getType() === "heatmap"
1512
+ );
1513
+ if (e.length === 0) return null;
1514
+ const n = this.getXScale(), s = this.getYScales(), o = this.pixelToDataX(t), l = this.pixelToDataY(i);
1515
+ for (const r of e) {
1516
+ const d = r.getHeatmapData();
1517
+ if (!d || !d.xValues || !d.yValues || !d.zValues) continue;
1518
+ const f = this.binarySearchClosest(d.xValues, o), p = this.binarySearchClosest(d.yValues, l), g = d.xValues[f], y = d.yValues[p], b = s.get(((h = r.getYAxisId) == null ? void 0 : h.call(r)) || "default") || s.values().next().value;
1519
+ if (!b) continue;
1520
+ const x = n.transform(g), S = b.transform(y), T = d.zValues[p * d.xValues.length + f], v = ((a = r.getHeatmapStyle) == null ? void 0 : a.call(r)) || {};
1434
1521
  return {
1435
- width: 100,
1436
- height: e.titleFontSize * e.lineHeight,
1437
- padding: e.padding
1522
+ type: "heatmap",
1523
+ seriesId: r.getId(),
1524
+ seriesName: r.getId(),
1525
+ xIndex: f,
1526
+ yIndex: p,
1527
+ dataX: g,
1528
+ dataY: y,
1529
+ zValue: T,
1530
+ pixelX: x,
1531
+ pixelY: S,
1532
+ colorScale: v.colorScale
1438
1533
  };
1439
- const n = this.getCacheKey(i, e);
1440
- if (n === this.cachedKey && this.cachedMeasurement)
1441
- return this.cachedMeasurement;
1442
- this.formattedData = this.formatData(i), t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
1443
- let s = t.measureText(this.formattedData.header).width;
1444
- t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
1445
- const o = e.showSeriesIndicator ? e.seriesIndicatorSize + F.INDICATOR_MARGIN : 0;
1446
- for (const r of this.formattedData.series) {
1447
- const d = t.measureText(`${r.name}:`).width, f = t.measureText(r.value).width, p = o + Math.max(d, F.MIN_LABEL_WIDTH) + f;
1448
- s = Math.max(s, p);
1449
1534
  }
1450
- const l = e.titleFontSize * e.lineHeight, a = this.formattedData.series.length * (e.contentFontSize * e.lineHeight + F.LINE_SPACING);
1451
- return this.cachedMeasurement = {
1452
- width: s + F.SAFETY_BUFFER,
1453
- height: l + e.headerGap + a,
1454
- padding: e.padding
1455
- }, this.cachedKey = n, this.cachedMeasurement;
1535
+ return null;
1456
1536
  }
1457
1537
  /**
1458
- * Render the tooltip
1538
+ * Binary search for closest value
1459
1539
  */
1460
- render(t, i, e, n, s) {
1461
- if (!i.interpolatedValues || i.interpolatedValues.length === 0)
1462
- return;
1463
- const o = this.formattedData || this.formatData(i), { x: l, y: h } = e, { padding: a } = s;
1464
- let r = h + a.top;
1465
- const d = l + a.left;
1466
- t.save(), t.textAlign = "left", t.textBaseline = "top", t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor, t.fillText(o.header, d, r), r += s.titleFontSize * s.lineHeight + F.HEADER_SPACING, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(d, r + s.headerGap / 2), t.lineTo(d + n.width, r + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), r += s.headerGap + F.GAP_SPACING, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
1467
- for (const f of o.series) {
1468
- let p = d;
1469
- if (s.showSeriesIndicator && s.seriesIndicatorSize > 0) {
1470
- const b = r + s.contentFontSize / 2, x = s.seriesIndicatorSize / 2;
1471
- t.save(), t.beginPath(), t.arc(p + x, b, x, 0, Math.PI * 2), f.isInterpolated ? (t.strokeStyle = f.color, t.lineWidth = 1.5, t.stroke()) : (t.fillStyle = f.color, t.fill()), t.restore(), p += s.seriesIndicatorSize + F.INDICATOR_MARGIN;
1472
- }
1473
- t.fillStyle = s.textSecondaryColor, t.fillText(`${f.name}:`, p, r);
1474
- const g = t.measureText(`${f.name}: `).width, y = Math.max(g, F.MIN_LABEL_WIDTH);
1475
- t.fillStyle = s.textColor, t.fillText(f.value, p + y, r), r += s.contentFontSize * s.lineHeight + F.LINE_SPACING;
1540
+ binarySearchClosest(t, i) {
1541
+ let e = 0, n = t.length - 1;
1542
+ for (; e < n; ) {
1543
+ const s = Math.floor((e + n) / 2);
1544
+ t[s] < i ? e = s + 1 : n = s;
1476
1545
  }
1477
- t.restore();
1478
- }
1479
- }
1480
- const G = new zt();
1481
- function P(c) {
1482
- if (c == null || isNaN(c))
1483
- return "N/A";
1484
- const t = Math.abs(c);
1485
- return t === 0 ? "0" : t < 1e-4 || t >= 1e4 ? c.toExponential(3) : t < 0.01 ? c.toPrecision(4) : t < 1 ? c.toFixed(4) : t < 100 ? c.toFixed(3) : c.toFixed(2);
1486
- }
1487
- class Yt {
1488
- constructor() {
1489
- u(this, "id", "heatmap");
1490
- u(this, "name", "Heatmap Value");
1491
- u(this, "supportedTypes", ["heatmap"]);
1492
- // Color swatch dimensions
1493
- u(this, "swatchSize", 14);
1494
- u(this, "swatchGap", 8);
1495
- // Cached measurements
1496
- u(this, "cachedKey", "");
1497
- u(this, "cachedMeasurement", null);
1546
+ return e > 0 && Math.abs(t[e - 1] - i) < Math.abs(t[e] - i) ? e - 1 : e;
1498
1547
  }
1499
1548
  /**
1500
- * Measure tooltip dimensions
1549
+ * Build crosshair tooltip data
1501
1550
  */
1502
- measure(t, i, e) {
1503
- const n = `${i.seriesId}-${i.xIndex}-${i.yIndex}-${e.fontFamily}-${e.titleFontSize}`;
1504
- if (n === this.cachedKey && this.cachedMeasurement)
1505
- return this.cachedMeasurement;
1506
- t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
1507
- const s = `Grid [${i.xIndex}, ${i.yIndex}]`;
1508
- let o = t.measureText(s).width;
1509
- t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
1510
- const l = `Value: ${P(i.zValue)}`, h = this.swatchSize + this.swatchGap + t.measureText(l).width;
1511
- if (o = Math.max(o, h), i.colorScale) {
1512
- const y = `Min: ${P(i.colorScale.min)} Max: ${P(i.colorScale.max)}`;
1513
- o = Math.max(o, this.swatchSize + this.swatchGap + t.measureText(y).width);
1551
+ buildCrosshairTooltip(t, i) {
1552
+ var l, h;
1553
+ const e = this.getSeries().filter(
1554
+ (a) => a.isVisible() && a.getType() !== "heatmap"
1555
+ );
1556
+ if (e.length === 0) return null;
1557
+ const n = this.pixelToDataX(t), s = this.getYScales(), o = [];
1558
+ for (const a of e) {
1559
+ const r = a.getData();
1560
+ if (!r.x || r.x.length === 0 || !(s.get(((l = a.getYAxisId) == null ? void 0 : l.call(a)) || "default") || s.values().next().value)) continue;
1561
+ const f = this.binarySearchClosest(r.x, n);
1562
+ let p, g = !1;
1563
+ if (f === 0 || f >= r.x.length - 1 || r.x[f] === n)
1564
+ p = r.y[f];
1565
+ else if ((h = this.options.crosshair) != null && h.interpolate) {
1566
+ const b = r.x[f - 1], x = r.x[f], S = r.y[f - 1], T = r.y[f], v = (n - b) / (x - b);
1567
+ p = S + v * (T - S), g = !0;
1568
+ } else
1569
+ p = r.y[f];
1570
+ const y = a.getStyle();
1571
+ o.push({
1572
+ seriesId: a.getId(),
1573
+ seriesName: a.getId(),
1574
+ seriesColor: y.color || "#ff0055",
1575
+ x: n,
1576
+ y: p,
1577
+ isInterpolated: g
1578
+ });
1514
1579
  }
1515
- const a = 25, r = a + t.measureText(P(i.dataX)).width, d = a + t.measureText(P(i.dataY)).width;
1516
- o = Math.max(o, r, d);
1517
- const f = e.titleFontSize * e.lineHeight, g = (i.colorScale ? 4 : 3) * e.contentFontSize * e.lineHeight;
1518
- return this.cachedMeasurement = {
1519
- width: o + 4,
1520
- // Small buffer
1521
- height: f + e.headerGap + g + e.itemGap,
1522
- padding: e.padding
1523
- }, this.cachedKey = n, this.cachedMeasurement;
1580
+ return o.length === 0 ? null : {
1581
+ type: "crosshair",
1582
+ cursorX: t,
1583
+ cursorY: i,
1584
+ dataX: n,
1585
+ interpolatedValues: o,
1586
+ axisFormat: this.buildAxisFormat()
1587
+ };
1524
1588
  }
1525
1589
  /**
1526
- * Render the tooltip
1590
+ * Schedule showing a tooltip
1527
1591
  */
1528
- render(t, i, e, n, s) {
1529
- const { x: o, y: l } = e, { padding: h } = s;
1530
- let a = l + h.top;
1531
- const r = o + h.left;
1532
- t.save(), t.textAlign = "left", t.textBaseline = "top", t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor;
1533
- const d = `Grid [${i.xIndex}, ${i.yIndex}]`;
1534
- t.fillText(d, r, a), a += s.titleFontSize * s.lineHeight + 2, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(r, a + s.headerGap / 2), t.lineTo(r + n.width, a + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), a += s.headerGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
1535
- const f = a + (s.contentFontSize * s.lineHeight - this.swatchSize) / 2;
1536
- t.fillStyle = i.mappedColor || s.textColor, t.fillRect(r, f, this.swatchSize, this.swatchSize), t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.strokeRect(r, f, this.swatchSize, this.swatchSize), t.fillStyle = s.textSecondaryColor, t.fillText("Value:", r + this.swatchSize + this.swatchGap, a), t.fillStyle = s.textColor;
1537
- const p = t.measureText("Value: ").width;
1538
- if (t.fillText(P(i.zValue), r + this.swatchSize + this.swatchGap + p, a), a += s.contentFontSize * s.lineHeight, i.colorScale) {
1539
- t.fillStyle = s.textSecondaryColor, t.font = `400 ${s.contentFontSize - 1}px ${s.fontFamily}`;
1540
- const g = `Min: ${P(i.colorScale.min)} Max: ${P(i.colorScale.max)}`;
1541
- t.fillText(g, r + this.swatchSize + this.swatchGap, a), a += s.contentFontSize * s.lineHeight;
1592
+ scheduleShow(t) {
1593
+ if (this.clearHideTimeout(), t.type === "datapoint") {
1594
+ const e = t;
1595
+ if (this.hoveredSeriesId === e.seriesId && this.hoveredDataIndex === e.dataIndex) {
1596
+ this.updateTooltipPosition(t);
1597
+ return;
1598
+ }
1599
+ this.hoveredSeriesId = e.seriesId, this.hoveredDataIndex = e.dataIndex;
1542
1600
  }
1543
- a += s.itemGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`, t.fillStyle = s.textSecondaryColor, t.fillText("X:", r, a), t.fillStyle = s.textColor, t.fillText(P(i.dataX), r + 25, a), a += s.contentFontSize * s.lineHeight + 2, t.fillStyle = s.textSecondaryColor, t.fillText("Y:", r, a), t.fillStyle = s.textColor, t.fillText(P(i.dataY), r + 25, a), t.restore();
1544
- }
1545
- }
1546
- const _ = new Yt();
1547
- function Q(c) {
1548
- var t, i;
1549
- return ((i = (t = c.axisFormat) == null ? void 0 : t.x) == null ? void 0 : i.type) === "time" ? ot(c) : E(c.dataX);
1550
- }
1551
- function Z(c) {
1552
- var t, i;
1553
- return ((i = (t = c.axisFormat) == null ? void 0 : t.y) == null ? void 0 : i.scientific) === !1 ? nt(c) : E(c.dataY);
1554
- }
1555
- function E(c, t = 3) {
1556
- if (c == null || isNaN(c))
1557
- return "N/A";
1558
- if (c === 0) return "0";
1559
- const i = Math.floor(Math.log10(Math.abs(c))), e = c / Math.pow(10, i);
1560
- if (Math.abs(i) <= 2)
1561
- return c.toPrecision(t);
1562
- const n = {
1563
- 0: "⁰",
1564
- 1: "¹",
1565
- 2: "²",
1566
- 3: "³",
1567
- 4: "⁴",
1568
- 5: "⁵",
1569
- 6: "⁶",
1570
- 7: "⁷",
1571
- 8: "⁸",
1572
- 9: "⁹",
1573
- "-": "⁻",
1574
- "+": "⁺"
1575
- }, s = i.toString();
1576
- let o = "";
1577
- for (const l of s)
1578
- o += n[l] || l;
1579
- return `${e.toFixed(t - 1)} × 10${o}`;
1580
- }
1581
- class At {
1582
- constructor() {
1583
- u(this, "id", "scientific");
1584
- u(this, "name", "Scientific Notation");
1585
- u(this, "supportedTypes", ["datapoint"]);
1586
- // Cached measurements
1587
- u(this, "cachedKey", "");
1588
- u(this, "cachedMeasurement", null);
1601
+ this.clearShowTimeout();
1602
+ const i = this.activeTooltips.size > 0 ? 0 : this.options.showDelay;
1603
+ i > 0 ? this.showTimeoutId = window.setTimeout(() => {
1604
+ this.showTooltip(t);
1605
+ }, i) : this.showTooltip(t);
1589
1606
  }
1590
1607
  /**
1591
- * Measure tooltip dimensions
1608
+ * Schedule hiding tooltips
1592
1609
  */
1593
- measure(t, i, e) {
1594
- const n = `${i.seriesId}-${i.dataIndex}-${i.dataX}-${i.dataY}-${e.fontFamily}-${e.titleFontSize}`;
1595
- if (n === this.cachedKey && this.cachedMeasurement)
1596
- return this.cachedMeasurement;
1597
- t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
1598
- const s = t.measureText(i.seriesName).width + (e.showSeriesIndicator ? e.seriesIndicatorSize + 8 : 0);
1599
- t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
1600
- const o = 28, l = Q(i);
1601
- let h = Math.max(s, o + t.measureText(l).width);
1602
- const a = Z(i);
1603
- let r = o + t.measureText(a).width;
1604
- i.yError && (r += t.measureText(` ± ${E(i.yError[0])}`).width), h = Math.max(h, r);
1605
- let d = `Index: ${i.dataIndex}`;
1606
- i.cycle !== void 0 && (d += ` │ Cycle: ${i.cycle}`), h = Math.max(h, t.measureText(d).width);
1607
- let f = 3;
1608
- (i.cycle !== void 0 || i.yError) && (f += 1);
1609
- const p = e.titleFontSize * e.lineHeight + e.headerGap, g = (f - 1) * e.contentFontSize * e.lineHeight, y = i.cycle !== void 0 ? e.itemGap : 0;
1610
- return this.cachedMeasurement = {
1611
- width: h,
1612
- height: p + g + y,
1613
- padding: e.padding
1614
- }, this.cachedKey = n, this.cachedMeasurement;
1610
+ scheduleHide() {
1611
+ this.clearShowTimeout(), this.hoveredSeriesId = null, this.hoveredDataIndex = null, this.options.hideDelay > 0 ? this.hideTimeoutId = window.setTimeout(() => {
1612
+ this.hideAll();
1613
+ }, this.options.hideDelay) : this.hideAll();
1614
+ }
1615
+ clearShowTimeout() {
1616
+ this.showTimeoutId !== null && (clearTimeout(this.showTimeoutId), this.showTimeoutId = null);
1617
+ }
1618
+ clearHideTimeout() {
1619
+ this.hideTimeoutId !== null && (clearTimeout(this.hideTimeoutId), this.hideTimeoutId = null);
1615
1620
  }
1616
1621
  /**
1617
- * Render the tooltip
1622
+ * Show a tooltip with the given data
1618
1623
  */
1619
- render(t, i, e, n, s) {
1620
- const { x: o, y: l } = e, { padding: h } = s;
1621
- let a = l + h.top, r = o + h.left;
1622
- if (t.save(), t.textAlign = "left", t.textBaseline = "top", s.showSeriesIndicator) {
1623
- const p = a + s.titleFontSize / 2;
1624
- t.fillStyle = i.seriesColor, t.beginPath(), t.arc(r + s.seriesIndicatorSize / 2, p, s.seriesIndicatorSize / 2, 0, Math.PI * 2), t.fill(), r += s.seriesIndicatorSize + 8;
1625
- }
1626
- t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor, t.fillText(i.seriesName, r, a), a += s.titleFontSize * s.lineHeight + 2, r = o + h.left, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.lineWidth = 1, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(r, a + s.headerGap / 2), t.lineTo(r + n.width, a + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), a += s.headerGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
1627
- const d = 28;
1628
- t.fillStyle = s.textSecondaryColor, t.fillText("X:", r, a), t.fillStyle = s.textColor, t.fillText(Q(i), r + d, a), a += s.contentFontSize * s.lineHeight + 2, t.fillStyle = s.textSecondaryColor, t.fillText("Y:", r, a), t.fillStyle = s.textColor;
1629
- let f = Z(i);
1630
- if (t.fillText(f, r + d, a), i.yError) {
1631
- const p = t.measureText(f).width;
1632
- t.fillStyle = s.textSecondaryColor, t.fillText(` ± ${E(i.yError[0])}`, r + d + p, a);
1633
- }
1634
- if (a += s.contentFontSize * s.lineHeight + 2, i.cycle !== void 0) {
1635
- a += s.itemGap, t.strokeStyle = s.separatorColor, t.globalAlpha = 0.3, t.beginPath(), t.moveTo(r, a - s.itemGap / 2), t.lineTo(r + n.width, a - s.itemGap / 2), t.stroke(), t.globalAlpha = 1, t.fillStyle = s.textSecondaryColor, t.font = `400 ${s.contentFontSize - 1}px ${s.fontFamily}`;
1636
- let p = `Index: ${i.dataIndex}`;
1637
- i.cycle !== void 0 && (p += ` │ Cycle: ${i.cycle}`), t.fillText(p, r, a);
1638
- }
1639
- t.restore();
1640
- }
1641
- }
1642
- const lt = new At();
1643
- function X(c) {
1644
- if (c == null || isNaN(c))
1645
- return "N/A";
1646
- const t = Math.abs(c);
1647
- return t === 0 ? "0" : t < 1e-4 || t >= 1e4 ? c.toExponential(2) : t < 1 ? c.toFixed(4) : c.toFixed(2);
1648
- }
1649
- class Xt {
1650
- constructor() {
1651
- u(this, "id", "annotation");
1652
- u(this, "name", "Annotation Info");
1653
- u(this, "supportedTypes", ["annotation"]);
1654
- // Cache
1655
- u(this, "cachedKey", "");
1656
- u(this, "cachedMeasurement", null);
1624
+ showTooltip(t) {
1625
+ const i = this.getTemplateIdForType(t.type), e = this.templates.get(i) || tt(t.type), n = t.pixelX ?? this.lastCursorX ?? 0, s = t.pixelY ?? this.lastCursorY ?? 0, o = e.measure(this.ctx, t, this.theme), l = this.positioner.calculatePosition(
1626
+ n,
1627
+ s,
1628
+ o
1629
+ ), h = "main";
1630
+ this.activeTooltips.set(h, {
1631
+ id: h,
1632
+ data: t,
1633
+ position: l,
1634
+ template: e
1635
+ });
1657
1636
  }
1658
1637
  /**
1659
- * Measure tooltip dimensions
1638
+ * Update tooltip position
1660
1639
  */
1661
- measure(t, i, e) {
1662
- const n = `${i.annotationId}-${i.label}-${e.fontFamily}-${e.titleFontSize}`;
1663
- if (n === this.cachedKey && this.cachedMeasurement)
1664
- return this.cachedMeasurement;
1665
- t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
1666
- const s = i.label || `Annotation: ${i.annotationType}`;
1667
- let o = t.measureText(s).width;
1668
- t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
1669
- let l = 0;
1670
- const h = 50;
1671
- if (i.value !== void 0) {
1672
- const r = X(i.value);
1673
- o = Math.max(o, h + t.measureText(r).width), l += e.contentFontSize * e.lineHeight;
1674
- }
1675
- if (i.valueX !== void 0 && i.valueY !== void 0) {
1676
- const r = X(i.valueX), d = X(i.valueY);
1677
- o = Math.max(o, h + t.measureText(r).width, h + t.measureText(d).width), l += e.contentFontSize * e.lineHeight * 2;
1678
- }
1679
- const a = e.titleFontSize * e.lineHeight + e.headerGap;
1680
- return this.cachedMeasurement = {
1681
- width: o + 10,
1682
- height: a + l + 4,
1683
- padding: e.padding
1684
- }, this.cachedKey = n, this.cachedMeasurement;
1640
+ updateTooltipPosition(t) {
1641
+ const i = this.activeTooltips.get("main");
1642
+ if (!i) return;
1643
+ const e = t || i.data, n = e.pixelX ?? this.lastCursorX ?? 0, s = e.pixelY ?? this.lastCursorY ?? 0, o = i.template.measure(
1644
+ this.ctx,
1645
+ e,
1646
+ this.theme
1647
+ );
1648
+ i.position = this.positioner.calculatePosition(
1649
+ n,
1650
+ s,
1651
+ o
1652
+ ), i.data = e;
1685
1653
  }
1686
1654
  /**
1687
- * Render the tooltip
1655
+ * Get template ID for a tooltip type
1688
1656
  */
1689
- render(t, i, e, n, s) {
1690
- const { x: o, y: l } = e, { padding: h } = s;
1691
- let a = l + h.top;
1692
- const r = o + h.left;
1693
- t.save(), t.textAlign = "left", t.textBaseline = "top", t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor;
1694
- const d = i.label || `${i.annotationType.toUpperCase()}`;
1695
- t.fillText(d, r, a), a += s.titleFontSize * s.lineHeight + 2, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(r, a + s.headerGap / 2), t.lineTo(r + n.width, a + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), a += s.headerGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
1696
- const f = 50;
1697
- i.value !== void 0 && (t.fillStyle = s.textSecondaryColor, t.fillText("Value:", r, a), t.fillStyle = s.textColor, t.fillText(X(i.value), r + f, a), a += s.contentFontSize * s.lineHeight), i.valueX !== void 0 && i.valueY !== void 0 && (t.fillStyle = s.textSecondaryColor, t.fillText("X:", r, a), t.fillStyle = s.textColor, t.fillText(X(i.valueX), r + f, a), a += s.contentFontSize * s.lineHeight, t.fillStyle = s.textSecondaryColor, t.fillText("Y:", r, a), t.fillStyle = s.textColor, t.fillText(X(i.valueY), r + f, a)), t.restore();
1698
- }
1699
- }
1700
- const K = new Xt();
1701
- function M(c) {
1702
- if (c == null || isNaN(c))
1703
- return "N/A";
1704
- const t = Math.abs(c);
1705
- return t === 0 ? "0" : t < 1e-4 || t >= 1e4 ? c.toExponential(3) : t < 1 ? c.toFixed(4) : c.toFixed(3);
1706
- }
1707
- class $t {
1708
- constructor() {
1709
- u(this, "id", "range");
1710
- u(this, "name", "Range Statistics");
1711
- u(this, "supportedTypes", ["range"]);
1712
- // Cache
1713
- u(this, "cachedKey", "");
1714
- u(this, "cachedMeasurement", null);
1657
+ getTemplateIdForType(t) {
1658
+ var i, e, n;
1659
+ switch (t) {
1660
+ case "datapoint":
1661
+ return ((i = this.options.dataPoint) == null ? void 0 : i.templateId) || "default";
1662
+ case "crosshair":
1663
+ return ((e = this.options.crosshair) == null ? void 0 : e.templateId) || "crosshair";
1664
+ case "heatmap":
1665
+ return ((n = this.options.heatmap) == null ? void 0 : n.templateId) || "heatmap";
1666
+ default:
1667
+ return "default";
1668
+ }
1715
1669
  }
1716
1670
  /**
1717
- * Measure tooltip dimensions
1671
+ * Show a tooltip programmatically
1718
1672
  */
1719
- measure(t, i, e) {
1720
- const n = i.statistics, s = `${i.xMin}-${i.xMax}-${n.count}-${n.mean}-${e.fontFamily}`;
1721
- if (s === this.cachedKey && this.cachedMeasurement)
1722
- return this.cachedMeasurement;
1723
- t.font = `${e.titleFontWeight} ${e.titleFontSize}px ${e.fontFamily}`;
1724
- let l = t.measureText("Range Statistics").width;
1725
- t.font = `400 ${e.contentFontSize}px ${e.fontFamily}`;
1726
- let h = e.contentFontSize * e.lineHeight * 2;
1727
- const a = [
1728
- `Range: ${M(i.xMin)} to ${M(i.xMax)}`,
1729
- `Count: ${n.count}`
1730
- ];
1731
- n.min !== void 0 && a.push(`Min: ${M(n.min)}`), n.max !== void 0 && a.push(`Max: ${M(n.max)}`), n.mean !== void 0 && a.push(`Mean: ${M(n.mean)}`), n.area !== void 0 && a.push(`Area: ${M(n.area)}`);
1732
- for (const d of a)
1733
- l = Math.max(l, t.measureText(d).width + 10);
1734
- h = a.length * e.contentFontSize * e.lineHeight;
1735
- const r = e.titleFontSize * e.lineHeight + e.headerGap;
1736
- return this.cachedMeasurement = {
1737
- width: Math.max(l, 140),
1738
- height: r + h + 10,
1739
- padding: e.padding
1740
- }, this.cachedKey = s, this.cachedMeasurement;
1673
+ show(t, i) {
1674
+ const e = `tooltip-${++this.tooltipIdCounter}`, n = (i == null ? void 0 : i.templateId) || this.getTemplateIdForType(t.type), s = this.templates.get(n) || tt(t.type);
1675
+ let o;
1676
+ if (i != null && i.position) {
1677
+ const l = s.measure(this.ctx, t, this.theme);
1678
+ o = this.positioner.calculatePosition(
1679
+ i.position.x,
1680
+ i.position.y,
1681
+ l
1682
+ );
1683
+ } else {
1684
+ const l = t.pixelX ?? 0, h = t.pixelY ?? 0, a = s.measure(this.ctx, t, this.theme);
1685
+ o = this.positioner.calculatePosition(
1686
+ l,
1687
+ h,
1688
+ a
1689
+ );
1690
+ }
1691
+ return this.activeTooltips.set(e, {
1692
+ id: e,
1693
+ data: t,
1694
+ position: o,
1695
+ template: s
1696
+ }), i != null && i.duration && i.duration > 0 && setTimeout(() => this.hide(e), i.duration), e;
1741
1697
  }
1742
1698
  /**
1743
- * Render the tooltip
1699
+ * Hide a specific tooltip
1744
1700
  */
1745
- render(t, i, e, n, s) {
1746
- const { x: o, y: l } = e, { padding: h } = s, a = i.statistics;
1747
- let r = l + h.top;
1748
- const d = o + h.left;
1749
- t.save(), t.textAlign = "left", t.textBaseline = "top", t.font = `${s.titleFontWeight} ${s.titleFontSize}px ${s.fontFamily}`, t.fillStyle = s.textColor, t.fillText("Range Statistics", d, r), r += s.titleFontSize * s.lineHeight + 2, s.showHeaderSeparator && (t.strokeStyle = s.separatorColor, t.globalAlpha = 0.4, t.beginPath(), t.moveTo(d, r + s.headerGap / 2), t.lineTo(d + n.width, r + s.headerGap / 2), t.stroke(), t.globalAlpha = 1), r += s.headerGap + 2, t.font = `400 ${s.contentFontSize}px ${s.fontFamily}`;
1750
- const f = 60, p = (g, y) => {
1751
- t.fillStyle = s.textSecondaryColor, t.fillText(g, d, r), t.fillStyle = s.textColor, t.fillText(String(y), d + f, r), r += s.contentFontSize * s.lineHeight;
1752
- };
1753
- p("Points:", a.count), a.min !== void 0 && p("Min:", M(a.min)), a.max !== void 0 && p("Max:", M(a.max)), a.mean !== void 0 && p("Mean:", M(a.mean)), a.stdDev !== void 0 && p("StdDev:", M(a.stdDev)), a.area !== void 0 && p("Area:", M(a.area)), a.peakX !== void 0 && (r += 4, t.fillStyle = s.textSecondaryColor, t.font = `italic ${s.contentFontSize - 1}px ${s.fontFamily}`, t.fillText(`Peak: ${M(a.peakY ?? 0)} at X=${M(a.peakX)}`, d, r)), t.restore();
1701
+ hide(t) {
1702
+ this.activeTooltips.delete(t);
1754
1703
  }
1755
- }
1756
- const U = new $t(), Dt = [
1757
- H,
1758
- at,
1759
- G,
1760
- _,
1761
- lt,
1762
- K,
1763
- U
1764
- ];
1765
- function Bt(c) {
1766
- return Dt.find((t) => t.id === c);
1767
- }
1768
- function tt(c) {
1769
- switch (c) {
1770
- case "datapoint":
1771
- return H;
1772
- case "crosshair":
1773
- return G;
1774
- case "heatmap":
1775
- return _;
1776
- case "annotation":
1777
- return K;
1778
- case "range":
1779
- return U;
1780
- default:
1781
- return H;
1704
+ /**
1705
+ * Hide all tooltips immediately
1706
+ */
1707
+ hideAll() {
1708
+ this.activeTooltips.clear(), this.hoveredSeriesId = null, this.hoveredDataIndex = null;
1782
1709
  }
1783
- }
1784
- class kt {
1785
- constructor(t) {
1786
- u(this, "ctx");
1787
- u(this, "getPlotArea");
1788
- u(this, "getSeries");
1789
- u(this, "pixelToDataX");
1790
- u(this, "pixelToDataY");
1791
- u(this, "getXScale");
1792
- u(this, "getYScales");
1793
- u(this, "getViewBounds");
1794
- u(this, "getXAxisOptions");
1795
- u(this, "getYAxisOptions");
1796
- u(this, "options");
1797
- u(this, "theme");
1798
- u(this, "positioner");
1799
- u(this, "renderer");
1800
- u(this, "templates", /* @__PURE__ */ new Map());
1801
- u(this, "events", new ut());
1802
- u(this, "activeTooltips", /* @__PURE__ */ new Map());
1803
- u(this, "tooltipIdCounter", 0);
1804
- u(this, "showTimeoutId", null);
1805
- u(this, "hideTimeoutId", null);
1806
- u(this, "lastCursorX", null);
1807
- u(this, "lastCursorY", null);
1808
- u(this, "hoveredSeriesId", null);
1809
- u(this, "hoveredDataIndex", null);
1810
- u(this, "cachedNearestResult", null);
1811
- // Large dataset optimizations
1812
- u(this, "snapMode", "auto");
1813
- u(this, "largeDatasetThreshold", 5e4);
1814
- u(this, "lastKnownDataSize", 0);
1815
- // Hysteresis factor: new point must be this much closer to switch (2 = 2x closer)
1816
- u(this, "hysteresisRatio", 2);
1817
- // Suspended state - used during drag operations to hide tooltip
1818
- u(this, "suspended", !1);
1819
- var i;
1820
- this.ctx = t.overlayCtx, this.getPlotArea = t.getPlotArea, this.getSeries = t.getSeries, this.pixelToDataX = t.pixelToDataX, this.pixelToDataY = t.pixelToDataY, this.getXScale = t.getXScale, this.getYScales = t.getYScales, this.getViewBounds = t.getViewBounds, this.getXAxisOptions = t.getXAxisOptions, this.getYAxisOptions = t.getYAxisOptions, this.options = {
1821
- enabled: !0,
1822
- showDelay: 50,
1823
- hideDelay: 100,
1824
- followCursor: !1,
1825
- offset: { x: 12, y: 12 },
1826
- dataPoint: {
1827
- enabled: !0,
1828
- templateId: "default",
1829
- snapToPoint: !0,
1830
- hitRadius: 20
1831
- },
1832
- crosshair: {
1833
- enabled: !1,
1834
- templateId: "crosshair",
1835
- interpolate: !0,
1836
- visibleSeriesOnly: !0
1837
- },
1838
- range: {
1839
- enabled: !1,
1840
- templateId: "default",
1841
- calculateStats: !0
1842
- },
1843
- annotation: {
1844
- enabled: !0,
1845
- templateId: "default"
1846
- },
1847
- heatmap: {
1848
- enabled: !0,
1849
- templateId: "heatmap",
1850
- showColorInfo: !0
1851
- },
1852
- positioning: "auto",
1853
- preferredPosition: "auto",
1854
- constrainToPlotArea: !0,
1855
- constrainToContainer: !0,
1856
- autoFlip: !0
1857
- }, this.theme = (i = t.options) != null && i.theme ? typeof t.options.theme == "string" ? B(t.options.theme) : mt(Tt, t.options.theme) : B(t.chartTheme.name), this.positioner = new Mt({
1858
- offset: this.options.offset,
1859
- preferredPosition: this.options.preferredPosition,
1860
- constrainToPlotArea: this.options.constrainToPlotArea,
1861
- constrainToContainer: this.options.constrainToContainer,
1862
- autoFlip: this.options.autoFlip,
1863
- showArrow: this.theme.showArrow,
1864
- arrowSize: this.theme.arrowSize
1865
- }), this.renderer = new vt(this.ctx, this.theme), this.registerBuiltinTemplates(), t.options && this.configure(t.options);
1710
+ /**
1711
+ * Render all active tooltips
1712
+ */
1713
+ render() {
1714
+ if (!(!this.options.enabled || this.activeTooltips.size === 0))
1715
+ for (const t of this.activeTooltips.values())
1716
+ this.renderer.render(t.data, t.position, t.template);
1866
1717
  }
1867
1718
  /**
1868
- * Register built-in templates
1719
+ * Check if any tooltip is currently visible
1869
1720
  */
1870
- registerBuiltinTemplates() {
1871
- this.templates.set(
1872
- "default",
1873
- H
1874
- ), this.templates.set(
1875
- "minimal",
1876
- at
1877
- ), this.templates.set(
1878
- "crosshair",
1879
- G
1880
- ), this.templates.set(
1881
- "heatmap",
1882
- _
1883
- ), this.templates.set(
1884
- "scientific",
1885
- lt
1886
- ), this.templates.set(
1887
- "annotation",
1888
- K
1889
- ), this.templates.set(
1890
- "range",
1891
- U
1892
- );
1721
+ hasActiveTooltip() {
1722
+ return this.activeTooltips.size > 0;
1893
1723
  }
1894
1724
  /**
1895
- * Configure tooltip options
1725
+ * Subscribe to tooltip events
1896
1726
  */
1897
- configure(t) {
1898
- t.enabled !== void 0 && (this.options.enabled = t.enabled), t.showDelay !== void 0 && (this.options.showDelay = t.showDelay), t.hideDelay !== void 0 && (this.options.hideDelay = t.hideDelay), t.followCursor !== void 0 && (this.options.followCursor = t.followCursor), t.offset && (this.options.offset = { ...this.options.offset, ...t.offset }), t.dataPoint && (this.options.dataPoint = {
1899
- ...this.options.dataPoint,
1900
- ...t.dataPoint
1901
- }), t.crosshair && (this.options.crosshair = {
1902
- ...this.options.crosshair,
1903
- ...t.crosshair
1904
- }), t.heatmap && (this.options.heatmap = { ...this.options.heatmap, ...t.heatmap }), t.preferredPosition && (this.options.preferredPosition = t.preferredPosition), t.constrainToPlotArea !== void 0 && (this.options.constrainToPlotArea = t.constrainToPlotArea), t.constrainToContainer !== void 0 && (this.options.constrainToContainer = t.constrainToContainer), t.autoFlip !== void 0 && (this.options.autoFlip = t.autoFlip), t.snapMode !== void 0 && (this.snapMode = t.snapMode), t.largeDatasetThreshold !== void 0 && (this.largeDatasetThreshold = Math.max(
1905
- 1e3,
1906
- t.largeDatasetThreshold
1907
- )), this.positioner.configure({
1908
- offset: this.options.offset,
1909
- preferredPosition: this.options.preferredPosition,
1910
- constrainToPlotArea: this.options.constrainToPlotArea,
1911
- constrainToContainer: this.options.constrainToContainer,
1912
- autoFlip: this.options.autoFlip
1913
- });
1727
+ on(t, i) {
1728
+ this.events.on(t, i);
1914
1729
  }
1915
1730
  /**
1916
- * Get current options
1731
+ * Unsubscribe from tooltip events
1917
1732
  */
1918
- getOptions() {
1919
- return { ...this.options };
1733
+ off(t, i) {
1734
+ this.events.off(t, i);
1920
1735
  }
1921
1736
  /**
1922
- * Enable/disable tooltips
1737
+ * Destroy the tooltip manager
1923
1738
  */
1924
- setEnabled(t) {
1925
- this.options.enabled = t, t || this.hideAll();
1739
+ destroy() {
1740
+ this.clearShowTimeout(), this.clearHideTimeout(), this.activeTooltips.clear(), this.templates.clear(), this.events.clear();
1741
+ }
1742
+ }
1743
+ class Bt {
1744
+ constructor(t, i) {
1745
+ u(this, "ctx");
1746
+ u(this, "options");
1747
+ u(this, "state", {
1748
+ enabled: !1,
1749
+ selectionState: "idle",
1750
+ point1: null,
1751
+ point2: null,
1752
+ hoveredPoint: null,
1753
+ lastMeasurement: null
1754
+ });
1755
+ u(this, "overlayCanvas", null);
1756
+ u(this, "overlayCtx", null);
1757
+ // Label dragging
1758
+ u(this, "labelOffset", { x: 15, y: 0 });
1759
+ u(this, "isDraggingLabel", !1);
1760
+ u(this, "labelBounds", null);
1761
+ u(this, "dragStart", { x: 0, y: 0 });
1762
+ u(this, "crosshairPosition", null);
1763
+ // Bound handlers
1764
+ u(this, "boundMouseMove");
1765
+ u(this, "boundClick");
1766
+ u(this, "boundKeyDown");
1767
+ u(this, "boundMouseDown");
1768
+ u(this, "boundMouseUp");
1769
+ u(this, "boundResize");
1770
+ this.ctx = t, this.options = {
1771
+ lineColor: "#ff6b6b",
1772
+ lineWidth: 2,
1773
+ showLabels: !0,
1774
+ labelFontSize: 12,
1775
+ labelBackground: "rgba(0, 0, 0, 0.85)",
1776
+ labelColor: "#ffffff",
1777
+ showDelta: !0,
1778
+ precision: 4,
1779
+ showSlope: !0,
1780
+ showDistance: !0,
1781
+ className: "velo-plot-delta-tool",
1782
+ highlightSize: 12,
1783
+ highlightColor: "#00f2ff",
1784
+ snapRadius: 30,
1785
+ ...i
1786
+ }, this.boundMouseMove = this.handleMouseMove.bind(this), this.boundClick = this.handleClick.bind(this), this.boundKeyDown = this.handleKeyDown.bind(this), this.boundMouseDown = this.handleLabelMouseDown.bind(this), this.boundMouseUp = this.handleLabelMouseUp.bind(this), this.boundResize = this.resizeOverlay.bind(this);
1926
1787
  }
1927
1788
  /**
1928
- * Check if tooltips are enabled
1789
+ * Enable the delta measurement tool
1929
1790
  */
1930
- isEnabled() {
1931
- return this.options.enabled;
1791
+ enable() {
1792
+ this.state.enabled || (this.state.enabled = !0, this.state.selectionState = "idle", this.createOverlay(), this.attachListeners(), this.ctx.container.style.cursor = "crosshair");
1932
1793
  }
1933
1794
  /**
1934
- * Suspend tooltip display (used during drag operations)
1935
- * When suspended, tooltips are immediately hidden and cursor movements are ignored
1795
+ * Disable the delta measurement tool
1936
1796
  */
1937
- setSuspended(t) {
1938
- this.suspended = t, t && this.hideAll();
1797
+ disable() {
1798
+ this.state.enabled && (this.state.enabled = !1, this.state.selectionState = "idle", this.state.point1 = null, this.state.point2 = null, this.state.hoveredPoint = null, this.detachListeners(), this.destroyOverlay(), this.ctx.container.style.cursor = "");
1939
1799
  }
1940
1800
  /**
1941
- * Check if tooltips are suspended
1801
+ * Toggle the tool on/off
1942
1802
  */
1943
- isSuspended() {
1944
- return this.suspended;
1803
+ toggle() {
1804
+ this.state.enabled ? this.disable() : this.enable();
1945
1805
  }
1946
1806
  /**
1947
- * Set tooltip theme
1807
+ * Check if tool is enabled
1948
1808
  */
1949
- setTheme(t) {
1950
- this.theme = typeof t == "string" ? B(t) : t, this.renderer.setTheme(this.theme), this.positioner.configure({
1951
- showArrow: this.theme.showArrow,
1952
- arrowSize: this.theme.arrowSize
1953
- });
1809
+ isEnabled() {
1810
+ return this.state.enabled;
1954
1811
  }
1955
1812
  /**
1956
- * Update theme based on chart theme
1813
+ * Get the current state
1957
1814
  */
1958
- updateChartTheme(t) {
1959
- this.options.theme || (this.theme = B(t.name), this.renderer.setTheme(this.theme));
1815
+ getState() {
1816
+ return { ...this.state };
1960
1817
  }
1961
1818
  /**
1962
- * Get current theme
1819
+ * Get the last completed measurement
1963
1820
  */
1964
- getTheme() {
1965
- return this.theme;
1821
+ getMeasurement() {
1822
+ return this.state.lastMeasurement;
1966
1823
  }
1967
1824
  /**
1968
- * Register a custom template
1825
+ * Clear the current measurement
1969
1826
  */
1970
- registerTemplate(t) {
1971
- this.templates.set(t.id, t);
1827
+ clear() {
1828
+ this.state.point1 = null, this.state.point2 = null, this.state.selectionState = "idle", this.state.lastMeasurement = null, this.labelBounds = null, this.labelOffset = { x: 15, y: 0 }, this.renderOverlay();
1972
1829
  }
1973
1830
  /**
1974
- * Get a template by ID
1831
+ * Destroy the tool and cleanup
1975
1832
  */
1976
- getTemplate(t) {
1977
- return this.templates.get(t) || Bt(t);
1833
+ destroy() {
1834
+ this.disable(), this.state.lastMeasurement = null;
1978
1835
  }
1979
1836
  /**
1980
- * Handle cursor movement - main entry point for hover detection
1981
- * Optimized with hysteresis to prevent jumping between nearby points
1837
+ * Recalculate measurements (useful if data changes)
1982
1838
  */
1983
- handleCursorMove(t, i) {
1984
- if (!this.options.enabled || this.suspended) return;
1985
- this.lastCursorX = t, this.lastCursorY = i;
1986
- const e = this.getPlotArea();
1987
- if (!(t >= e.x && t <= e.x + e.width && i >= e.y && i <= e.y + e.height)) {
1988
- this.cachedNearestResult = null, this.scheduleHide();
1839
+ recalculate() {
1840
+ if (!this.state.point1 || !this.state.point2) return;
1841
+ const t = this.ctx.getSeries ? this.ctx.getSeries() : [], i = (s) => {
1842
+ const o = t.find((l) => l.id === s.seriesId);
1843
+ if (o && s.index < o.x.length) {
1844
+ s.x = o.x[s.index], s.y = o.y[s.index], s.yAxisId = o.yAxisId;
1845
+ const l = this.ctx.getPlotArea(), h = this.ctx.getViewBounds(), a = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: h.yMin, yMax: h.yMax };
1846
+ return s.pixelX = l.x + (s.x - h.xMin) / (h.xMax - h.xMin) * l.width, s.pixelY = l.y + (1 - (s.y - a.yMin) / (a.yMax - a.yMin)) * l.height, !0;
1847
+ }
1848
+ return !1;
1849
+ }, e = i(this.state.point1), n = i(this.state.point2);
1850
+ e && n ? (this.state.lastMeasurement = this.calculateMeasurement(), this.ctx.onMeasure && this.ctx.onMeasure(this.state.lastMeasurement)) : this.clear(), this.renderOverlay();
1851
+ }
1852
+ // ============================================
1853
+ // Private Methods
1854
+ // ============================================
1855
+ createOverlay() {
1856
+ this.overlayCanvas || (this.overlayCanvas = document.createElement("canvas"), this.overlayCanvas.className = this.options.className, this.overlayCanvas.style.cssText = `
1857
+ position: absolute;
1858
+ top: 0;
1859
+ left: 0;
1860
+ width: 100%;
1861
+ height: 100%;
1862
+ pointer-events: none;
1863
+ z-index: 1000;
1864
+ `, this.resizeOverlay(), this.overlayCtx = this.overlayCanvas.getContext("2d"), this.ctx.container.appendChild(this.overlayCanvas));
1865
+ }
1866
+ resizeOverlay() {
1867
+ if (!this.overlayCanvas) return;
1868
+ const t = this.ctx.container.getBoundingClientRect(), i = window.devicePixelRatio || 1;
1869
+ this.overlayCanvas.width = t.width * i, this.overlayCanvas.height = t.height * i, this.overlayCtx && (this.overlayCtx.setTransform(1, 0, 0, 1, 0, 0), this.overlayCtx.scale(i, i));
1870
+ }
1871
+ destroyOverlay() {
1872
+ this.overlayCanvas && this.overlayCanvas.parentNode && this.overlayCanvas.parentNode.removeChild(this.overlayCanvas), this.overlayCanvas = null, this.overlayCtx = null;
1873
+ }
1874
+ attachListeners() {
1875
+ this.ctx.container.addEventListener("mousemove", this.boundMouseMove, !0), this.ctx.container.addEventListener("mousedown", this.boundClick, !0), this.ctx.container.addEventListener("mousedown", this.boundMouseDown, !0), this.ctx.container.addEventListener("mouseup", this.boundMouseUp, !0), window.addEventListener("resize", this.boundResize), document.addEventListener("keydown", this.boundKeyDown);
1876
+ }
1877
+ detachListeners() {
1878
+ this.ctx.container.removeEventListener("mousemove", this.boundMouseMove, !0), this.ctx.container.removeEventListener("mousedown", this.boundClick, !0), this.ctx.container.removeEventListener("mousedown", this.boundMouseDown, !0), this.ctx.container.removeEventListener("mouseup", this.boundMouseUp, !0), window.removeEventListener("resize", this.boundResize), document.removeEventListener("keydown", this.boundKeyDown);
1879
+ }
1880
+ handleMouseMove(t) {
1881
+ if (!this.state.enabled) return;
1882
+ const i = this.ctx.container.getBoundingClientRect(), e = this.ctx.getPlotArea(), n = this.ctx.getViewBounds(), s = t.clientX - i.left, o = t.clientY - i.top;
1883
+ if (this.isDraggingLabel) {
1884
+ this.labelOffset.x += s - this.dragStart.x, this.labelOffset.y += o - this.dragStart.y, this.dragStart = { x: s, y: o }, this.renderOverlay();
1989
1885
  return;
1990
1886
  }
1991
- this.positioner.setContainerSize(
1992
- this.ctx.canvas.width / (window.devicePixelRatio || 1),
1993
- this.ctx.canvas.height / (window.devicePixelRatio || 1)
1994
- ), this.positioner.setPlotArea(e), this.lastKnownDataSize = this.getTotalDataPoints(), this.performTooltipUpdateWithHysteresis(t, i);
1887
+ if (this.labelBounds && s >= this.labelBounds.x && s <= this.labelBounds.x + this.labelBounds.width && o >= this.labelBounds.y && o <= this.labelBounds.y + this.labelBounds.height ? this.ctx.container.style.cursor = "move" : this.ctx.container.style.cursor = "crosshair", s < e.x || s > e.x + e.width || o < e.y || o > e.y + e.height) {
1888
+ this.state.hoveredPoint = null, this.crosshairPosition = null, this.renderOverlay();
1889
+ return;
1890
+ }
1891
+ this.crosshairPosition = { x: s, y: o };
1892
+ const l = this.findNearestPoint(s, o, e, n);
1893
+ this.state.hoveredPoint = l, this.renderOverlay();
1995
1894
  }
1996
- /**
1997
- * Handle cursor leave
1998
- */
1999
- handleCursorLeave() {
2000
- this.lastCursorX = null, this.lastCursorY = null, this.cachedNearestResult = null, this.scheduleHide();
1895
+ handleLabelMouseDown(t) {
1896
+ if (!this.state.enabled || !this.labelBounds) return;
1897
+ const i = this.ctx.container.getBoundingClientRect(), e = t.clientX - i.left, n = t.clientY - i.top;
1898
+ e >= this.labelBounds.x && e <= this.labelBounds.x + this.labelBounds.width && n >= this.labelBounds.y && n <= this.labelBounds.y + this.labelBounds.height && (this.isDraggingLabel = !0, this.dragStart = { x: e, y: n }, t.stopPropagation(), t.preventDefault());
1899
+ }
1900
+ handleLabelMouseUp() {
1901
+ this.isDraggingLabel = !1;
1902
+ }
1903
+ async handleClick(t) {
1904
+ var h;
1905
+ if (!this.state.enabled) return;
1906
+ (h = this.ctx.animationEngine) != null && h.isAnimating() && await this.ctx.animationEngine.waitForIdle(), t.stopPropagation(), t.preventDefault();
1907
+ const i = this.ctx.container.getBoundingClientRect(), e = this.ctx.getPlotArea(), n = this.ctx.getViewBounds(), s = t.clientX - i.left, o = t.clientY - i.top;
1908
+ if (s < e.x || s > e.x + e.width || o < e.y || o > e.y + e.height)
1909
+ return;
1910
+ const l = this.findNearestPoint(s, o, e, n);
1911
+ if (l) {
1912
+ if (t.preventDefault(), t.stopPropagation(), this.state.selectionState === "idle" || this.state.selectionState === "complete")
1913
+ this.state.point1 = l, this.state.point2 = null, this.state.selectionState = "waitingSecond", this.state.lastMeasurement = null;
1914
+ else if (this.state.selectionState === "waitingSecond") {
1915
+ this.state.point2 = l, this.state.selectionState = "complete";
1916
+ const a = this.calculateMeasurement();
1917
+ this.state.lastMeasurement = a, this.ctx.onMeasure && this.ctx.onMeasure(a);
1918
+ }
1919
+ this.renderOverlay();
1920
+ }
2001
1921
  }
2002
- /**
2003
- * Get total number of data points across all visible series
2004
- */
2005
- getTotalDataPoints() {
2006
- let t = 0;
2007
- const i = this.getSeries();
2008
- for (const e of i) {
2009
- if (!e.isVisible()) continue;
2010
- const n = e.getData();
2011
- n.x && (t += n.x.length);
1922
+ handleKeyDown(t) {
1923
+ t.key === "Escape" && this.state.enabled && this.clear();
1924
+ }
1925
+ findNearestPoint(t, i, e, n) {
1926
+ if (!this.ctx.getSeries) return null;
1927
+ const s = this.ctx.getSeries();
1928
+ let o = null, l = this.options.snapRadius;
1929
+ for (const h of s) {
1930
+ const a = h.x, r = h.y, d = Math.min(a.length, r.length), f = this.ctx.getYBounds ? this.ctx.getYBounds(h.yAxisId) : { yMin: n.yMin, yMax: n.yMax };
1931
+ for (let p = 0; p < d; p++) {
1932
+ const g = a[p], y = r[p], b = e.x + (g - n.xMin) / (n.xMax - n.xMin) * e.width, x = e.y + (1 - (y - f.yMin) / (f.yMax - f.yMin)) * e.height;
1933
+ if (b < e.x || b > e.x + e.width || x < e.y || x > e.y + e.height) continue;
1934
+ const S = Math.sqrt((t - b) ** 2 + (i - x) ** 2);
1935
+ S < l && (l = S, o = {
1936
+ x: g,
1937
+ y,
1938
+ seriesId: h.id,
1939
+ yAxisId: h.yAxisId,
1940
+ index: p,
1941
+ pixelX: b,
1942
+ pixelY: x
1943
+ });
1944
+ }
2012
1945
  }
2013
- return t;
1946
+ return o;
2014
1947
  }
2015
- /**
2016
- * Determine the effective snap mode based on configuration and data size
2017
- */
2018
- getEffectiveSnapMode() {
2019
- return this.snapMode === "x-only" ? "x-only" : this.snapMode === "nearest" ? "nearest" : this.lastKnownDataSize > this.largeDatasetThreshold ? "x-only" : "nearest";
1948
+ calculateMeasurement() {
1949
+ const t = this.state.point1, i = this.state.point2, e = i.x - t.x, n = i.y - t.y, s = e !== 0 ? n / e : 1 / 0, o = Math.sqrt(e * e + n * n);
1950
+ return {
1951
+ x1: t.x,
1952
+ y1: t.y,
1953
+ x2: i.x,
1954
+ y2: i.y,
1955
+ deltaX: e,
1956
+ deltaY: n,
1957
+ slope: s,
1958
+ distance: o,
1959
+ pixelX1: t.pixelX,
1960
+ pixelY1: t.pixelY,
1961
+ pixelX2: i.pixelX,
1962
+ pixelY2: i.pixelY
1963
+ };
2020
1964
  }
2021
- /**
2022
- * Perform tooltip update with hysteresis to prevent jumping.
2023
- * The tooltip "sticks" to the current point unless the cursor is
2024
- * significantly closer to a new point.
2025
- */
2026
- performTooltipUpdateWithHysteresis(t, i) {
2027
- var s;
2028
- if (!((s = this.options.dataPoint) != null && s.enabled)) {
2029
- this.performFallbackTooltipUpdate(t, i);
2030
- return;
1965
+ renderOverlay() {
1966
+ if (!this.overlayCtx || !this.overlayCanvas) return;
1967
+ this.resizeOverlay();
1968
+ const t = this.ctx.container.getBoundingClientRect();
1969
+ this.overlayCtx.clearRect(0, 0, t.width, t.height);
1970
+ const i = this.options, e = this.overlayCtx, n = this.ctx.getPlotArea(), s = this.ctx.getViewBounds();
1971
+ if (this.state.point1) {
1972
+ const o = this.state.point1, l = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: s.yMin, yMax: s.yMax };
1973
+ o.pixelX = n.x + (o.x - s.xMin) / (s.xMax - s.xMin) * n.width, o.pixelY = n.y + (1 - (o.y - l.yMin) / (l.yMax - l.yMin)) * n.height;
2031
1974
  }
2032
- const n = this.getEffectiveSnapMode() === "x-only" ? this.findDataPointByXOnly(t) : this.findNearestDataPointOptimized(t, i);
2033
- if (!n) {
2034
- this.performFallbackTooltipUpdate(t, i);
2035
- return;
1975
+ if (this.state.point2) {
1976
+ const o = this.state.point2, l = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: s.yMin, yMax: s.yMax };
1977
+ o.pixelX = n.x + (o.x - s.xMin) / (s.xMax - s.xMin) * n.width, o.pixelY = n.y + (1 - (o.y - l.yMin) / (l.yMax - l.yMin)) * n.height;
2036
1978
  }
2037
- if (this.cachedNearestResult && this.hoveredSeriesId !== null) {
2038
- const o = this.cachedNearestResult, l = o.pixelX - t, h = o.pixelY - i, a = l * l + h * h, r = n.pixelX - t, d = n.pixelY - i;
2039
- if ((r * r + d * d) * this.hysteresisRatio >= a) {
2040
- this.updateTooltipPosition(this.cachedNearestResult);
2041
- return;
2042
- }
1979
+ if (this.crosshairPosition && this.state.selectionState !== "complete") {
1980
+ const o = this.crosshairPosition.x, l = this.crosshairPosition.y;
1981
+ e.save(), e.setLineDash([4, 4]), e.strokeStyle = "rgba(150, 150, 150, 0.7)", e.lineWidth = 1, e.beginPath(), e.moveTo(o, n.y), e.lineTo(o, n.y + n.height), e.stroke(), e.beginPath(), e.moveTo(n.x, l), e.lineTo(n.x + n.width, l), e.stroke(), e.restore();
2043
1982
  }
2044
- this.cachedNearestResult = n, this.scheduleShow(n);
2045
- }
2046
- /**
2047
- * Handle heatmap and crosshair tooltip types (fallback)
2048
- */
2049
- performFallbackTooltipUpdate(t, i) {
2050
- var e, n;
2051
- if ((e = this.options.heatmap) != null && e.enabled) {
2052
- const s = this.findHeatmapCell(t, i);
2053
- if (s) {
2054
- this.scheduleShow(s);
2055
- return;
2056
- }
1983
+ if (this.state.hoveredPoint && this.state.selectionState !== "complete") {
1984
+ const o = this.state.hoveredPoint;
1985
+ e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize + 4, 0, Math.PI * 2), e.fillStyle = i.highlightColor + "30", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.strokeStyle = i.highlightColor, e.lineWidth = 2, e.stroke(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 4, 0, Math.PI * 2), e.fillStyle = i.highlightColor, e.fill(), this.drawPointTooltip(e, o, "Hover");
2057
1986
  }
2058
- if ((n = this.options.crosshair) != null && n.enabled) {
2059
- const s = this.buildCrosshairTooltip(t, i);
2060
- if (s) {
2061
- this.scheduleShow(s);
2062
- return;
2063
- }
1987
+ if (this.state.point1) {
1988
+ const o = this.state.point1;
1989
+ e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.fillStyle = i.lineColor, e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize - 3, 0, Math.PI * 2), e.fillStyle = "#ffffff", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 3, 0, Math.PI * 2), e.fillStyle = i.lineColor, e.fill(), this.drawPointLabel(e, o.pixelX, o.pixelY - i.highlightSize - 8, "P1", i.lineColor);
2064
1990
  }
2065
- this.cachedNearestResult = null, this.scheduleHide();
1991
+ if (this.state.point2) {
1992
+ const o = this.state.point2;
1993
+ e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.fillStyle = "#4ade80", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize - 3, 0, Math.PI * 2), e.fillStyle = "#ffffff", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 3, 0, Math.PI * 2), e.fillStyle = "#4ade80", e.fill(), this.drawPointLabel(e, o.pixelX, o.pixelY - i.highlightSize - 8, "P2", "#4ade80");
1994
+ }
1995
+ if (this.state.point1 && this.state.point2) {
1996
+ const o = this.state.point1, l = this.state.point2;
1997
+ e.beginPath(), e.moveTo(o.pixelX, o.pixelY), e.lineTo(l.pixelX, l.pixelY), e.strokeStyle = i.lineColor, e.lineWidth = i.lineWidth, e.setLineDash([6, 4]), e.stroke(), e.setLineDash([]), e.beginPath(), e.moveTo(o.pixelX, o.pixelY), e.lineTo(l.pixelX, o.pixelY), e.lineTo(l.pixelX, l.pixelY), e.strokeStyle = "rgba(255, 107, 107, 0.4)", e.lineWidth = 1, e.setLineDash([3, 3]), e.stroke(), e.setLineDash([]), i.showLabels && this.state.lastMeasurement && this.drawMeasurementLabel(e, o, l, this.state.lastMeasurement);
1998
+ }
1999
+ this.drawStatusIndicator(e);
2066
2000
  }
2067
- buildAxisFormat(t) {
2068
- var o, l;
2069
- const i = this.getViewBounds(), e = i.xMax - i.xMin, n = (o = this.getXAxisOptions) == null ? void 0 : o.call(this), s = (l = this.getYAxisOptions) == null ? void 0 : l.call(this, t);
2070
- if (!(!n && !s && !Number.isFinite(e)))
2071
- return {
2072
- x: n,
2073
- y: s,
2074
- xSpan: Number.isFinite(e) ? e : void 0
2075
- };
2001
+ drawPointTooltip(t, i, e) {
2002
+ const n = this.options, s = `${e}: (${this.formatValue(i.x)}, ${this.formatValue(i.y)})`;
2003
+ t.font = `${n.labelFontSize}px system-ui, sans-serif`;
2004
+ const o = t.measureText(s), l = 6, h = o.width + l * 2, a = n.labelFontSize + l * 2;
2005
+ let r = i.pixelX + 15, d = i.pixelY - a / 2;
2006
+ const f = this.ctx.container.getBoundingClientRect();
2007
+ r + h > f.width && (r = i.pixelX - h - 15), d < 0 && (d = 5), d + a > f.height && (d = f.height - a - 5), t.fillStyle = n.labelBackground, this.roundRect(t, r, d, h, a, 4), t.fill(), t.fillStyle = n.labelColor, t.textAlign = "left", t.textBaseline = "middle", t.fillText(s, r + l, d + a / 2);
2076
2008
  }
2077
- /**
2078
- * Find data point by X coordinate only (O(log n) - fastest method)
2079
- * Best for very large datasets where precision is less important than speed
2080
- */
2081
- findDataPointByXOnly(t) {
2082
- var h, a, r;
2083
- const i = this.getSeries().filter(
2084
- (d) => d.isVisible() && d.getType() !== "heatmap"
2085
- );
2086
- if (i.length === 0) return null;
2087
- const e = this.getXScale(), n = this.getYScales(), s = this.pixelToDataX(t);
2088
- let o = null, l = 1 / 0;
2089
- for (const d of i) {
2090
- const f = d.getData();
2091
- if (!f.x || f.x.length === 0) continue;
2092
- const p = n.get(((h = d.getYAxisId) == null ? void 0 : h.call(d)) || "default") || n.values().next().value;
2093
- if (!p) continue;
2094
- const g = this.binarySearchClosest(f.x, s), y = Math.abs(f.x[g] - s);
2095
- if (y < l) {
2096
- l = y;
2097
- const b = d.getStyle(), x = e.transform(f.x[g]), S = p.transform(f.y[g]);
2098
- o = {
2099
- type: "datapoint",
2100
- seriesId: d.getId(),
2101
- seriesName: d.getId(),
2102
- seriesColor: b.color || "#ff0055",
2103
- dataIndex: g,
2104
- dataX: f.x[g],
2105
- dataY: f.y[g],
2106
- pixelX: x,
2107
- pixelY: S,
2108
- cycle: (a = d.getCycle) == null ? void 0 : a.call(d),
2109
- axisFormat: this.buildAxisFormat((r = d.getYAxisId) == null ? void 0 : r.call(d))
2110
- };
2111
- const T = d.getYError(g);
2112
- T && (o.yError = T);
2113
- }
2114
- }
2115
- return o;
2009
+ drawPointLabel(t, i, e, n, s) {
2010
+ const o = this.options;
2011
+ t.font = `bold ${o.labelFontSize}px system-ui, sans-serif`, t.textAlign = "center", t.textBaseline = "bottom";
2012
+ const l = t.measureText(n), h = 4, a = l.width + h * 2, r = o.labelFontSize + h;
2013
+ t.fillStyle = s, this.roundRect(t, i - a / 2, e - r, a, r, 3), t.fill(), t.fillStyle = "#ffffff", t.fillText(n, i, e - 2);
2014
+ }
2015
+ drawMeasurementLabel(t, i, e, n) {
2016
+ const s = this.options, o = (i.pixelX + e.pixelX) / 2, l = (i.pixelY + e.pixelY) / 2, h = [];
2017
+ s.showDelta && (h.push(`ΔX: ${this.formatValue(n.deltaX)}`), h.push(`ΔY: ${this.formatValue(n.deltaY)}`)), s.showSlope && isFinite(n.slope) && h.push(`Slope: ${this.formatValue(n.slope)}`), s.showDistance && h.push(`Distance: ${this.formatValue(n.distance)}`);
2018
+ const a = s.labelFontSize + 4, r = 10, d = 170, f = h.length * a + r * 2;
2019
+ let p = o + this.labelOffset.x, g = l + this.labelOffset.y;
2020
+ const y = this.ctx.container.getBoundingClientRect();
2021
+ p < 0 && (p = 0), p + d > y.width && (p = y.width - d), g < 0 && (g = 0), g + f > y.height && (g = y.height - f), this.labelBounds = { x: p, y: g, width: d, height: f }, t.fillStyle = s.labelBackground, t.strokeStyle = s.lineColor, t.lineWidth = 2, this.roundRect(t, p, g, d, f, 8), t.fill(), t.stroke(), t.fillStyle = s.labelColor, t.font = `${s.labelFontSize}px system-ui, sans-serif`, t.textAlign = "left", t.textBaseline = "top", h.forEach((b, x) => {
2022
+ t.fillText(b, p + r, g + r + x * a);
2023
+ });
2024
+ }
2025
+ drawStatusIndicator(t) {
2026
+ const i = this.options, e = this.ctx.getPlotArea();
2027
+ let n = "", s = "#666";
2028
+ if (this.state.selectionState === "idle" ? (n = "📏 Click a point to start measuring", s = i.highlightColor) : this.state.selectionState === "waitingSecond" ? (n = "📏 Click second point to complete", s = "#f59e0b") : this.state.selectionState === "complete" && (n = "📏 Measurement complete (ESC to clear)", s = "#4ade80"), !n) return;
2029
+ t.font = "12px system-ui, sans-serif";
2030
+ const h = t.measureText(n).width + 8 * 2, a = 24, r = e.x + 10, d = e.y + 10;
2031
+ t.fillStyle = "rgba(0, 0, 0, 0.75)", this.roundRect(t, r, d, h, a, 4), t.fill(), t.beginPath(), t.arc(r + 16, d + a / 2, 4, 0, Math.PI * 2), t.fillStyle = s, t.fill(), t.fillStyle = "#fff", t.textAlign = "left", t.textBaseline = "middle", t.fillText(n, r + 26, d + a / 2);
2032
+ }
2033
+ roundRect(t, i, e, n, s, o) {
2034
+ t.beginPath(), t.moveTo(i + o, e), t.lineTo(i + n - o, e), t.quadraticCurveTo(i + n, e, i + n, e + o), t.lineTo(i + n, e + s - o), t.quadraticCurveTo(i + n, e + s, i + n - o, e + s), t.lineTo(i + o, e + s), t.quadraticCurveTo(i, e + s, i, e + s - o), t.lineTo(i, e + o), t.quadraticCurveTo(i, e, i + o, e), t.closePath();
2035
+ }
2036
+ formatValue(t) {
2037
+ const i = Math.abs(t);
2038
+ return i === 0 ? "0" : i >= 1e6 || i < 1e-3 ? t.toExponential(this.options.precision - 1) : t.toPrecision(this.options.precision);
2039
+ }
2040
+ }
2041
+ class kt {
2042
+ constructor(t, i) {
2043
+ u(this, "ctx");
2044
+ u(this, "options");
2045
+ u(this, "state", {
2046
+ enabled: !1,
2047
+ selectionState: "idle",
2048
+ point1: null,
2049
+ point2: null,
2050
+ hoveredPoint: null,
2051
+ lastMeasurement: null
2052
+ });
2053
+ u(this, "overlayCanvas", null);
2054
+ u(this, "overlayCtx", null);
2055
+ // Label dragging
2056
+ u(this, "labelOffset", { x: 15, y: 0 });
2057
+ u(this, "isDraggingLabel", !1);
2058
+ u(this, "labelBounds", null);
2059
+ u(this, "dragStart", { x: 0, y: 0 });
2060
+ u(this, "crosshairPosition", null);
2061
+ // Bound handlers
2062
+ u(this, "boundMouseMove");
2063
+ u(this, "boundClick");
2064
+ u(this, "boundKeyDown");
2065
+ u(this, "boundMouseDown");
2066
+ u(this, "boundMouseUp");
2067
+ u(this, "boundResize");
2068
+ this.ctx = t, this.options = {
2069
+ lineColor: "#ff6b6b",
2070
+ lineWidth: 2,
2071
+ showLabels: !0,
2072
+ labelFontSize: 12,
2073
+ labelBackground: "rgba(0, 0, 0, 0.85)",
2074
+ labelColor: "#ffffff",
2075
+ showPeak: !0,
2076
+ precision: 4,
2077
+ className: "velo-plot-peak-tool",
2078
+ highlightSize: 12,
2079
+ highlightColor: "#00f2ff",
2080
+ snapRadius: 30,
2081
+ baselineWindow: 10,
2082
+ areaFill: "rgba(255, 107, 107, 0.3)",
2083
+ ...i
2084
+ }, this.boundMouseMove = this.handleMouseMove.bind(this), this.boundClick = this.handleClick.bind(this), this.boundKeyDown = this.handleKeyDown.bind(this), this.boundMouseDown = this.handleLabelMouseDown.bind(this), this.boundMouseUp = this.handleLabelMouseUp.bind(this), this.boundResize = this.resizeOverlay.bind(this);
2116
2085
  }
2117
2086
  /**
2118
- * Optimized nearest data point finder
2087
+ * Enable the peak measurement tool
2119
2088
  */
2120
- findNearestDataPointOptimized(t, i) {
2121
- var r, d, f, p;
2122
- const e = this.getSeries().filter((g) => g.isVisible()), n = ((r = this.options.dataPoint) == null ? void 0 : r.hitRadius) ?? 20, s = this.getXScale(), o = this.getYScales();
2123
- let l = n * n, h = null;
2124
- const a = this.pixelToDataX(t);
2125
- for (const g of e) {
2126
- if (g.getType() === "heatmap") continue;
2127
- const y = g.getData(), b = o.get(((d = g.getYAxisId) == null ? void 0 : d.call(g)) || "default") || o.values().next().value;
2128
- if (!b || !y.x || y.x.length === 0) continue;
2129
- const x = this.binarySearchClosest(y.x, a), S = y.x.length, T = S > 1e5 ? 2 : S > 1e4 ? 3 : 5, v = Math.max(0, x - T), Y = Math.min(S, x + T + 1);
2130
- for (let w = v; w < Y; w++) {
2131
- const C = s.transform(y.x[w]), m = b.transform(y.y[w]), I = C - t, A = m - i, $ = I * I + A * A;
2132
- if ($ < l) {
2133
- l = $;
2134
- const R = g.getStyle();
2135
- h = {
2136
- type: "datapoint",
2137
- seriesId: g.getId(),
2138
- seriesName: g.getId(),
2139
- seriesColor: R.color || "#ff0055",
2140
- dataIndex: w,
2141
- dataX: y.x[w],
2142
- dataY: y.y[w],
2143
- pixelX: C,
2144
- pixelY: m,
2145
- cycle: (f = g.getCycle) == null ? void 0 : f.call(g),
2146
- axisFormat: this.buildAxisFormat((p = g.getYAxisId) == null ? void 0 : p.call(g))
2147
- };
2148
- const D = g.getYError(w);
2149
- D && (h.yError = D);
2150
- }
2151
- }
2152
- }
2153
- return h;
2089
+ enable() {
2090
+ this.state.enabled || (this.state.enabled = !0, this.state.selectionState = "idle", this.createOverlay(), this.attachListeners(), this.ctx.container.style.cursor = "crosshair");
2154
2091
  }
2155
2092
  /**
2156
- * Find heatmap cell under cursor
2093
+ * Disable the peak measurement tool
2157
2094
  */
2158
- findHeatmapCell(t, i) {
2159
- var h, a;
2160
- const e = this.getSeries().filter(
2161
- (r) => r.isVisible() && r.getType() === "heatmap"
2162
- );
2163
- if (e.length === 0) return null;
2164
- const n = this.getXScale(), s = this.getYScales(), o = this.pixelToDataX(t), l = this.pixelToDataY(i);
2165
- for (const r of e) {
2166
- const d = r.getHeatmapData();
2167
- if (!d || !d.xValues || !d.yValues || !d.zValues) continue;
2168
- const f = this.binarySearchClosest(d.xValues, o), p = this.binarySearchClosest(d.yValues, l), g = d.xValues[f], y = d.yValues[p], b = s.get(((h = r.getYAxisId) == null ? void 0 : h.call(r)) || "default") || s.values().next().value;
2169
- if (!b) continue;
2170
- const x = n.transform(g), S = b.transform(y), T = d.zValues[p * d.xValues.length + f], v = ((a = r.getHeatmapStyle) == null ? void 0 : a.call(r)) || {};
2171
- return {
2172
- type: "heatmap",
2173
- seriesId: r.getId(),
2174
- seriesName: r.getId(),
2175
- xIndex: f,
2176
- yIndex: p,
2177
- dataX: g,
2178
- dataY: y,
2179
- zValue: T,
2180
- pixelX: x,
2181
- pixelY: S,
2182
- colorScale: v.colorScale
2183
- };
2184
- }
2185
- return null;
2095
+ disable() {
2096
+ this.state.enabled && (this.state.enabled = !1, this.state.selectionState = "idle", this.state.point1 = null, this.state.point2 = null, this.state.hoveredPoint = null, this.detachListeners(), this.destroyOverlay(), this.ctx.container.style.cursor = "");
2186
2097
  }
2187
2098
  /**
2188
- * Binary search for closest value
2099
+ * Toggle the tool on/off
2189
2100
  */
2190
- binarySearchClosest(t, i) {
2191
- let e = 0, n = t.length - 1;
2192
- for (; e < n; ) {
2193
- const s = Math.floor((e + n) / 2);
2194
- t[s] < i ? e = s + 1 : n = s;
2195
- }
2196
- return e > 0 && Math.abs(t[e - 1] - i) < Math.abs(t[e] - i) ? e - 1 : e;
2101
+ toggle() {
2102
+ this.state.enabled ? this.disable() : this.enable();
2197
2103
  }
2198
2104
  /**
2199
- * Build crosshair tooltip data
2105
+ * Check if tool is enabled
2200
2106
  */
2201
- buildCrosshairTooltip(t, i) {
2202
- var l, h;
2203
- const e = this.getSeries().filter(
2204
- (a) => a.isVisible() && a.getType() !== "heatmap"
2205
- );
2206
- if (e.length === 0) return null;
2207
- const n = this.pixelToDataX(t), s = this.getYScales(), o = [];
2208
- for (const a of e) {
2209
- const r = a.getData();
2210
- if (!r.x || r.x.length === 0 || !(s.get(((l = a.getYAxisId) == null ? void 0 : l.call(a)) || "default") || s.values().next().value)) continue;
2211
- const f = this.binarySearchClosest(r.x, n);
2212
- let p, g = !1;
2213
- if (f === 0 || f >= r.x.length - 1 || r.x[f] === n)
2214
- p = r.y[f];
2215
- else if ((h = this.options.crosshair) != null && h.interpolate) {
2216
- const b = r.x[f - 1], x = r.x[f], S = r.y[f - 1], T = r.y[f], v = (n - b) / (x - b);
2217
- p = S + v * (T - S), g = !0;
2218
- } else
2219
- p = r.y[f];
2220
- const y = a.getStyle();
2221
- o.push({
2222
- seriesId: a.getId(),
2223
- seriesName: a.getId(),
2224
- seriesColor: y.color || "#ff0055",
2225
- x: n,
2226
- y: p,
2227
- isInterpolated: g
2228
- });
2229
- }
2230
- return o.length === 0 ? null : {
2231
- type: "crosshair",
2232
- cursorX: t,
2233
- cursorY: i,
2234
- dataX: n,
2235
- interpolatedValues: o,
2236
- axisFormat: this.buildAxisFormat()
2237
- };
2107
+ isEnabled() {
2108
+ return this.state.enabled;
2238
2109
  }
2239
2110
  /**
2240
- * Schedule showing a tooltip
2111
+ * Get the current state
2241
2112
  */
2242
- scheduleShow(t) {
2243
- if (this.clearHideTimeout(), t.type === "datapoint") {
2244
- const e = t;
2245
- if (this.hoveredSeriesId === e.seriesId && this.hoveredDataIndex === e.dataIndex) {
2246
- this.updateTooltipPosition(t);
2247
- return;
2248
- }
2249
- this.hoveredSeriesId = e.seriesId, this.hoveredDataIndex = e.dataIndex;
2250
- }
2251
- this.clearShowTimeout();
2252
- const i = this.activeTooltips.size > 0 ? 0 : this.options.showDelay;
2253
- i > 0 ? this.showTimeoutId = window.setTimeout(() => {
2254
- this.showTooltip(t);
2255
- }, i) : this.showTooltip(t);
2113
+ getState() {
2114
+ return { ...this.state };
2256
2115
  }
2257
2116
  /**
2258
- * Schedule hiding tooltips
2117
+ * Get the last completed measurement
2259
2118
  */
2260
- scheduleHide() {
2261
- this.clearShowTimeout(), this.hoveredSeriesId = null, this.hoveredDataIndex = null, this.options.hideDelay > 0 ? this.hideTimeoutId = window.setTimeout(() => {
2262
- this.hideAll();
2263
- }, this.options.hideDelay) : this.hideAll();
2119
+ getMeasurement() {
2120
+ return this.state.lastMeasurement;
2264
2121
  }
2265
- clearShowTimeout() {
2266
- this.showTimeoutId !== null && (clearTimeout(this.showTimeoutId), this.showTimeoutId = null);
2122
+ /**
2123
+ * Clear the current measurement
2124
+ */
2125
+ clear() {
2126
+ this.state.point1 = null, this.state.point2 = null, this.state.selectionState = "idle", this.state.lastMeasurement = null, this.labelBounds = null, this.labelOffset = { x: 15, y: 0 }, this.renderOverlay();
2267
2127
  }
2268
- clearHideTimeout() {
2269
- this.hideTimeoutId !== null && (clearTimeout(this.hideTimeoutId), this.hideTimeoutId = null);
2128
+ /**
2129
+ * Destroy the tool and cleanup
2130
+ */
2131
+ destroy() {
2132
+ this.disable(), this.state.lastMeasurement = null;
2270
2133
  }
2271
2134
  /**
2272
- * Show a tooltip with the given data
2135
+ * Recalculate measurements (useful if data changes)
2273
2136
  */
2274
- showTooltip(t) {
2275
- const i = this.getTemplateIdForType(t.type), e = this.templates.get(i) || tt(t.type), n = t.pixelX ?? this.lastCursorX ?? 0, s = t.pixelY ?? this.lastCursorY ?? 0, o = e.measure(this.ctx, t, this.theme), l = this.positioner.calculatePosition(
2276
- n,
2277
- s,
2278
- o
2279
- ), h = "main";
2280
- this.activeTooltips.set(h, {
2281
- id: h,
2282
- data: t,
2283
- position: l,
2284
- template: e
2285
- });
2137
+ recalculate() {
2138
+ if (!this.state.point1 || !this.state.point2) return;
2139
+ const t = this.ctx.getSeries ? this.ctx.getSeries() : [], i = (s) => {
2140
+ const o = t.find((l) => l.id === s.seriesId);
2141
+ if (o && s.index < o.x.length) {
2142
+ s.x = o.x[s.index], s.y = o.y[s.index], s.yAxisId = o.yAxisId;
2143
+ const l = this.ctx.getPlotArea(), h = this.ctx.getViewBounds(), a = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: h.yMin, yMax: h.yMax };
2144
+ return s.pixelX = l.x + (s.x - h.xMin) / (h.xMax - h.xMin) * l.width, s.pixelY = l.y + (1 - (s.y - a.yMin) / (a.yMax - a.yMin)) * l.height, !0;
2145
+ }
2146
+ return !1;
2147
+ }, e = i(this.state.point1), n = i(this.state.point2);
2148
+ e && n ? (this.state.lastMeasurement = this.calculateMeasurement(), this.ctx.onMeasure && this.ctx.onMeasure(this.state.lastMeasurement)) : this.clear(), this.renderOverlay();
2149
+ }
2150
+ // ============================================
2151
+ // Private Methods
2152
+ // ============================================
2153
+ createOverlay() {
2154
+ this.overlayCanvas || (this.overlayCanvas = document.createElement("canvas"), this.overlayCanvas.className = this.options.className, this.overlayCanvas.style.cssText = `
2155
+ position: absolute;
2156
+ top: 0;
2157
+ left: 0;
2158
+ width: 100%;
2159
+ height: 100%;
2160
+ pointer-events: none;
2161
+ z-index: 1000;
2162
+ `, this.resizeOverlay(), this.overlayCtx = this.overlayCanvas.getContext("2d"), this.ctx.container.appendChild(this.overlayCanvas));
2163
+ }
2164
+ resizeOverlay() {
2165
+ if (!this.overlayCanvas) return;
2166
+ const t = this.ctx.container.getBoundingClientRect(), i = window.devicePixelRatio || 1;
2167
+ this.overlayCanvas.width = t.width * i, this.overlayCanvas.height = t.height * i, this.overlayCtx && (this.overlayCtx.setTransform(1, 0, 0, 1, 0, 0), this.overlayCtx.scale(i, i));
2168
+ }
2169
+ destroyOverlay() {
2170
+ this.overlayCanvas && this.overlayCanvas.parentNode && this.overlayCanvas.parentNode.removeChild(this.overlayCanvas), this.overlayCanvas = null, this.overlayCtx = null;
2171
+ }
2172
+ attachListeners() {
2173
+ this.ctx.container.addEventListener("mousemove", this.boundMouseMove, !0), this.ctx.container.addEventListener("mousedown", this.boundClick, !0), this.ctx.container.addEventListener("mousedown", this.boundMouseDown, !0), this.ctx.container.addEventListener("mouseup", this.boundMouseUp, !0), window.addEventListener("resize", this.boundResize), document.addEventListener("keydown", this.boundKeyDown);
2174
+ }
2175
+ detachListeners() {
2176
+ this.ctx.container.removeEventListener("mousemove", this.boundMouseMove, !0), this.ctx.container.removeEventListener("mousedown", this.boundClick, !0), this.ctx.container.removeEventListener("mousedown", this.boundMouseDown, !0), this.ctx.container.removeEventListener("mouseup", this.boundMouseUp, !0), window.removeEventListener("resize", this.boundResize), document.removeEventListener("keydown", this.boundKeyDown);
2177
+ }
2178
+ handleMouseMove(t) {
2179
+ if (!this.state.enabled) return;
2180
+ const i = this.ctx.container.getBoundingClientRect(), e = this.ctx.getPlotArea(), n = this.ctx.getViewBounds(), s = t.clientX - i.left, o = t.clientY - i.top;
2181
+ if (this.isDraggingLabel) {
2182
+ this.labelOffset.x += s - this.dragStart.x, this.labelOffset.y += o - this.dragStart.y, this.dragStart = { x: s, y: o }, this.renderOverlay();
2183
+ return;
2184
+ }
2185
+ if (this.labelBounds && s >= this.labelBounds.x && s <= this.labelBounds.x + this.labelBounds.width && o >= this.labelBounds.y && o <= this.labelBounds.y + this.labelBounds.height ? this.ctx.container.style.cursor = "move" : this.ctx.container.style.cursor = "crosshair", s < e.x || s > e.x + e.width || o < e.y || o > e.y + e.height) {
2186
+ this.state.hoveredPoint = null, this.crosshairPosition = null, this.renderOverlay();
2187
+ return;
2188
+ }
2189
+ this.crosshairPosition = { x: s, y: o };
2190
+ const l = this.state.selectionState === "waitingSecond" && this.state.point1 ? this.state.point1.seriesId : void 0, h = this.findNearestPoint(s, o, e, n, l);
2191
+ this.state.hoveredPoint = h, this.renderOverlay();
2192
+ }
2193
+ handleLabelMouseDown(t) {
2194
+ if (!this.state.enabled || !this.labelBounds) return;
2195
+ const i = this.ctx.container.getBoundingClientRect(), e = t.clientX - i.left, n = t.clientY - i.top;
2196
+ e >= this.labelBounds.x && e <= this.labelBounds.x + this.labelBounds.width && n >= this.labelBounds.y && n <= this.labelBounds.y + this.labelBounds.height && (this.isDraggingLabel = !0, this.dragStart = { x: e, y: n }, t.stopPropagation(), t.preventDefault());
2197
+ }
2198
+ handleLabelMouseUp() {
2199
+ this.isDraggingLabel = !1;
2200
+ }
2201
+ async handleClick(t) {
2202
+ var a;
2203
+ if (!this.state.enabled) return;
2204
+ (a = this.ctx.animationEngine) != null && a.isAnimating() && await this.ctx.animationEngine.waitForIdle(), t.stopPropagation(), t.preventDefault();
2205
+ const i = this.ctx.container.getBoundingClientRect(), e = this.ctx.getPlotArea(), n = this.ctx.getViewBounds(), s = t.clientX - i.left, o = t.clientY - i.top;
2206
+ if (s < e.x || s > e.x + e.width || o < e.y || o > e.y + e.height)
2207
+ return;
2208
+ const l = this.state.selectionState === "waitingSecond" && this.state.point1 ? this.state.point1.seriesId : void 0, h = this.findNearestPoint(s, o, e, n, l);
2209
+ if (h) {
2210
+ if (t.preventDefault(), t.stopPropagation(), this.state.selectionState === "idle" || this.state.selectionState === "complete")
2211
+ this.state.point1 = h, this.state.point2 = null, this.state.selectionState = "waitingSecond", this.state.lastMeasurement = null;
2212
+ else if (this.state.selectionState === "waitingSecond") {
2213
+ if (this.state.point1 && h.seriesId !== this.state.point1.seriesId)
2214
+ return;
2215
+ this.state.point2 = h, this.state.selectionState = "complete";
2216
+ const r = this.calculateMeasurement();
2217
+ this.state.lastMeasurement = r, this.ctx.onMeasure && this.ctx.onMeasure(r);
2218
+ }
2219
+ this.renderOverlay();
2220
+ }
2286
2221
  }
2287
- /**
2288
- * Update tooltip position
2289
- */
2290
- updateTooltipPosition(t) {
2291
- const i = this.activeTooltips.get("main");
2292
- if (!i) return;
2293
- const e = t || i.data, n = e.pixelX ?? this.lastCursorX ?? 0, s = e.pixelY ?? this.lastCursorY ?? 0, o = i.template.measure(
2294
- this.ctx,
2295
- e,
2296
- this.theme
2297
- );
2298
- i.position = this.positioner.calculatePosition(
2299
- n,
2300
- s,
2301
- o
2302
- ), i.data = e;
2222
+ handleKeyDown(t) {
2223
+ t.key === "Escape" && this.state.enabled && this.clear();
2303
2224
  }
2304
- /**
2305
- * Get template ID for a tooltip type
2306
- */
2307
- getTemplateIdForType(t) {
2308
- var i, e, n;
2309
- switch (t) {
2310
- case "datapoint":
2311
- return ((i = this.options.dataPoint) == null ? void 0 : i.templateId) || "default";
2312
- case "crosshair":
2313
- return ((e = this.options.crosshair) == null ? void 0 : e.templateId) || "crosshair";
2314
- case "heatmap":
2315
- return ((n = this.options.heatmap) == null ? void 0 : n.templateId) || "heatmap";
2316
- default:
2317
- return "default";
2225
+ findNearestPoint(t, i, e, n, s) {
2226
+ if (!this.ctx.getSeries) return null;
2227
+ const o = this.ctx.getSeries();
2228
+ let l = null, h = this.options.snapRadius;
2229
+ for (const a of o) {
2230
+ if (s && a.id !== s) continue;
2231
+ const r = a.x, d = a.y, f = Math.min(r.length, d.length), p = this.ctx.getYBounds ? this.ctx.getYBounds(a.yAxisId) : { yMin: n.yMin, yMax: n.yMax };
2232
+ for (let g = 0; g < f; g++) {
2233
+ const y = r[g], b = d[g], x = e.x + (y - n.xMin) / (n.xMax - n.xMin) * e.width, S = e.y + (1 - (b - p.yMin) / (p.yMax - p.yMin)) * e.height;
2234
+ if (x < e.x || x > e.x + e.width || S < e.y || S > e.y + e.height) continue;
2235
+ const T = Math.sqrt((t - x) ** 2 + (i - S) ** 2);
2236
+ T < h && (h = T, l = {
2237
+ x: y,
2238
+ y: b,
2239
+ seriesId: a.id,
2240
+ yAxisId: a.yAxisId,
2241
+ index: g,
2242
+ pixelX: x,
2243
+ pixelY: S
2244
+ });
2245
+ }
2318
2246
  }
2247
+ return l;
2319
2248
  }
2320
- /**
2321
- * Show a tooltip programmatically
2322
- */
2323
- show(t, i) {
2324
- const e = `tooltip-${++this.tooltipIdCounter}`, n = (i == null ? void 0 : i.templateId) || this.getTemplateIdForType(t.type), s = this.templates.get(n) || tt(t.type);
2325
- let o;
2326
- if (i != null && i.position) {
2327
- const l = s.measure(this.ctx, t, this.theme);
2328
- o = this.positioner.calculatePosition(
2329
- i.position.x,
2330
- i.position.y,
2331
- l
2332
- );
2333
- } else {
2334
- const l = t.pixelX ?? 0, h = t.pixelY ?? 0, a = s.measure(this.ctx, t, this.theme);
2335
- o = this.positioner.calculatePosition(
2336
- l,
2337
- h,
2338
- a
2339
- );
2249
+ calculateMeasurement() {
2250
+ const t = this.state.point1, i = this.state.point2, n = (this.ctx.getSeries ? this.ctx.getSeries() : []).find((m) => m.id === t.seriesId);
2251
+ if (!n)
2252
+ return {
2253
+ x1: t.x,
2254
+ y1: t.y,
2255
+ x2: i.x,
2256
+ y2: i.y,
2257
+ area: 0,
2258
+ integral: 0,
2259
+ baselineSlope: 0,
2260
+ baselineIntercept: 0,
2261
+ peakHeight: 0,
2262
+ peakX: 0,
2263
+ peakY: 0,
2264
+ pixelX1: t.pixelX,
2265
+ pixelY1: t.pixelY,
2266
+ pixelX2: i.pixelX,
2267
+ pixelY2: i.pixelY
2268
+ };
2269
+ const s = n.x, o = n.y, l = Math.min(t.index, i.index), h = Math.max(t.index, i.index), a = this.options.baselineWindow, r = [], d = [];
2270
+ for (let m = Math.max(0, l - Math.floor(a / 2)); m <= Math.min(s.length - 1, l + Math.floor(a / 2)); m++)
2271
+ r.push(s[m]), d.push(o[m]);
2272
+ for (let m = Math.max(0, h - Math.floor(a / 2)); m <= Math.min(s.length - 1, h + Math.floor(a / 2)); m++)
2273
+ r.push(s[m]), d.push(o[m]);
2274
+ const f = r.length;
2275
+ let p = 0, g = 0, y = 0, b = 0;
2276
+ for (let m = 0; m < f; m++)
2277
+ p += r[m], g += d[m], y += r[m] * d[m], b += r[m] * r[m];
2278
+ const x = (f * y - p * g) / (f * b - p * p), S = (g - x * p) / f;
2279
+ let T = 0, v = 0, Y = -1 / 0, w = 0, C = 0;
2280
+ for (let m = l; m < h; m++) {
2281
+ const I = s[m], A = o[m], $ = s[m + 1], R = o[m + 1], D = $ - I, q = (A + R) / 2;
2282
+ v += q * D;
2283
+ const j = x * I + S, rt = x * $ + S, ht = (j + rt) / 2;
2284
+ T += (q - ht) * D;
2285
+ const J = A - j;
2286
+ J > Y && (Y = J, w = I, C = A);
2340
2287
  }
2341
- return this.activeTooltips.set(e, {
2342
- id: e,
2343
- data: t,
2344
- position: o,
2345
- template: s
2346
- }), i != null && i.duration && i.duration > 0 && setTimeout(() => this.hide(e), i.duration), e;
2288
+ return {
2289
+ x1: t.x,
2290
+ y1: t.y,
2291
+ x2: i.x,
2292
+ y2: i.y,
2293
+ area: T,
2294
+ integral: v,
2295
+ baselineSlope: x,
2296
+ baselineIntercept: S,
2297
+ peakHeight: Y,
2298
+ peakX: w,
2299
+ peakY: C,
2300
+ pixelX1: t.pixelX,
2301
+ pixelY1: t.pixelY,
2302
+ pixelX2: i.pixelX,
2303
+ pixelY2: i.pixelY
2304
+ };
2347
2305
  }
2348
- /**
2349
- * Hide a specific tooltip
2350
- */
2351
- hide(t) {
2352
- this.activeTooltips.delete(t);
2306
+ renderOverlay() {
2307
+ if (!this.overlayCtx || !this.overlayCanvas) return;
2308
+ this.resizeOverlay();
2309
+ const t = this.ctx.container.getBoundingClientRect();
2310
+ this.overlayCtx.clearRect(0, 0, t.width, t.height);
2311
+ const i = this.options, e = this.overlayCtx, n = this.ctx.getPlotArea(), s = this.ctx.getViewBounds();
2312
+ if (this.state.point1) {
2313
+ const o = this.state.point1, l = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: s.yMin, yMax: s.yMax };
2314
+ o.pixelX = n.x + (o.x - s.xMin) / (s.xMax - s.xMin) * n.width, o.pixelY = n.y + (1 - (o.y - l.yMin) / (l.yMax - l.yMin)) * n.height;
2315
+ }
2316
+ if (this.state.point2) {
2317
+ const o = this.state.point2, l = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: s.yMin, yMax: s.yMax };
2318
+ o.pixelX = n.x + (o.x - s.xMin) / (s.xMax - s.xMin) * n.width, o.pixelY = n.y + (1 - (o.y - l.yMin) / (l.yMax - l.yMin)) * n.height;
2319
+ }
2320
+ if (this.crosshairPosition && this.state.selectionState !== "complete") {
2321
+ const o = this.crosshairPosition.x, l = this.crosshairPosition.y;
2322
+ e.save(), e.setLineDash([4, 4]), e.strokeStyle = "rgba(150, 150, 150, 0.7)", e.lineWidth = 1, e.beginPath(), e.moveTo(o, n.y), e.lineTo(o, n.y + n.height), e.stroke(), e.beginPath(), e.moveTo(n.x, l), e.lineTo(n.x + n.width, l), e.stroke(), e.restore();
2323
+ }
2324
+ if (this.state.hoveredPoint && this.state.selectionState !== "complete") {
2325
+ const o = this.state.hoveredPoint;
2326
+ e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize + 4, 0, Math.PI * 2), e.fillStyle = i.highlightColor + "30", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.strokeStyle = i.highlightColor, e.lineWidth = 2, e.stroke(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 4, 0, Math.PI * 2), e.fillStyle = i.highlightColor, e.fill(), this.drawPointTooltip(e, o, "Hover");
2327
+ }
2328
+ if (this.state.point1) {
2329
+ const o = this.state.point1;
2330
+ e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.fillStyle = i.lineColor, e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize - 3, 0, Math.PI * 2), e.fillStyle = "#ffffff", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 3, 0, Math.PI * 2), e.fillStyle = i.lineColor, e.fill(), this.drawPointLabel(e, o.pixelX, o.pixelY - i.highlightSize - 8, "P1", i.lineColor);
2331
+ }
2332
+ if (this.state.point2) {
2333
+ const o = this.state.point2;
2334
+ e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize, 0, Math.PI * 2), e.fillStyle = "#4ade80", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, i.highlightSize - 3, 0, Math.PI * 2), e.fillStyle = "#ffffff", e.fill(), e.beginPath(), e.arc(o.pixelX, o.pixelY, 3, 0, Math.PI * 2), e.fillStyle = "#4ade80", e.fill(), this.drawPointLabel(e, o.pixelX, o.pixelY - i.highlightSize - 8, "P2", "#4ade80");
2335
+ }
2336
+ if (this.state.point1 && this.state.point2 && this.state.lastMeasurement) {
2337
+ const o = this.state.point1, l = this.state.point2, h = this.state.lastMeasurement, a = this.ctx.getPlotArea(), r = this.ctx.getViewBounds(), f = (this.ctx.getSeries ? this.ctx.getSeries() : []).find((S) => S.id === o.seriesId);
2338
+ if (f) {
2339
+ const S = f.x, T = f.y, v = Math.min(o.index, l.index), Y = Math.max(o.index, l.index), w = this.ctx.getYBounds ? this.ctx.getYBounds(f.yAxisId) : { yMin: r.yMin, yMax: r.yMax };
2340
+ e.beginPath(), e.moveTo(o.pixelX, o.pixelY);
2341
+ for (let C = v; C <= Y; C++) {
2342
+ const m = a.x + (S[C] - r.xMin) / (r.xMax - r.xMin) * a.width, I = a.y + (1 - (T[C] - w.yMin) / (w.yMax - w.yMin)) * a.height;
2343
+ e.lineTo(m, I);
2344
+ }
2345
+ e.lineTo(l.pixelX, l.pixelY), e.closePath(), e.fillStyle = i.areaFill, e.fill();
2346
+ }
2347
+ e.beginPath(), e.moveTo(o.pixelX, o.pixelY), e.lineTo(l.pixelX, l.pixelY), e.strokeStyle = i.lineColor, e.lineWidth = 1, e.stroke();
2348
+ const p = this.ctx.getYBounds ? this.ctx.getYBounds(o.yAxisId) : { yMin: r.yMin, yMax: r.yMax }, g = a.x + (h.peakX - r.xMin) / (r.xMax - r.xMin) * a.width, y = a.y + (1 - (h.peakY - p.yMin) / (p.yMax - p.yMin)) * a.height, b = h.baselineSlope * h.peakX + h.baselineIntercept, x = a.y + (1 - (b - p.yMin) / (p.yMax - p.yMin)) * a.height;
2349
+ e.beginPath(), e.moveTo(g, y), e.lineTo(g, x), e.strokeStyle = i.lineColor, e.setLineDash([2, 2]), e.stroke(), e.setLineDash([]), i.showLabels && this.drawMeasurementLabel(e, o, l, h);
2350
+ }
2351
+ this.drawStatusIndicator(e);
2353
2352
  }
2354
- /**
2355
- * Hide all tooltips immediately
2356
- */
2357
- hideAll() {
2358
- this.activeTooltips.clear(), this.hoveredSeriesId = null, this.hoveredDataIndex = null;
2353
+ drawPointTooltip(t, i, e) {
2354
+ const n = this.options, s = `${e}: (${this.formatValue(i.x)}, ${this.formatValue(i.y)})`;
2355
+ t.font = `${n.labelFontSize}px system-ui, sans-serif`;
2356
+ const o = t.measureText(s), l = 6, h = o.width + l * 2, a = n.labelFontSize + l * 2;
2357
+ let r = i.pixelX + 15, d = i.pixelY - a / 2;
2358
+ const f = this.ctx.container.getBoundingClientRect();
2359
+ r + h > f.width && (r = i.pixelX - h - 15), d < 0 && (d = 5), d + a > f.height && (d = f.height - a - 5), t.fillStyle = n.labelBackground, this.roundRect(t, r, d, h, a, 4), t.fill(), t.fillStyle = n.labelColor, t.textAlign = "left", t.textBaseline = "middle", t.fillText(s, r + l, d + a / 2);
2359
2360
  }
2360
- /**
2361
- * Render all active tooltips
2362
- */
2363
- render() {
2364
- if (!(!this.options.enabled || this.activeTooltips.size === 0))
2365
- for (const t of this.activeTooltips.values())
2366
- this.renderer.render(t.data, t.position, t.template);
2361
+ drawPointLabel(t, i, e, n, s) {
2362
+ const o = this.options;
2363
+ t.font = `bold ${o.labelFontSize}px system-ui, sans-serif`, t.textAlign = "center", t.textBaseline = "bottom";
2364
+ const l = t.measureText(n), h = 4, a = l.width + h * 2, r = o.labelFontSize + h;
2365
+ t.fillStyle = s, this.roundRect(t, i - a / 2, e - r, a, r, 3), t.fill(), t.fillStyle = "#ffffff", t.fillText(n, i, e - 2);
2367
2366
  }
2368
- /**
2369
- * Check if any tooltip is currently visible
2370
- */
2371
- hasActiveTooltip() {
2372
- return this.activeTooltips.size > 0;
2367
+ drawMeasurementLabel(t, i, e, n) {
2368
+ const s = this.options, o = (i.pixelX + e.pixelX) / 2, l = (i.pixelY + e.pixelY) / 2, h = [];
2369
+ s.showPeak && (h.push(`Peak Area: ${this.formatValue(n.area)}`), h.push(`Peak Height: ${this.formatValue(n.peakHeight)}`), h.push(`Integral: ${this.formatValue(n.integral)}`));
2370
+ const a = s.labelFontSize + 4, r = 10, d = 170, f = h.length * a + r * 2;
2371
+ let p = o + this.labelOffset.x, g = l + this.labelOffset.y;
2372
+ const y = this.ctx.container.getBoundingClientRect();
2373
+ p < 0 && (p = 0), p + d > y.width && (p = y.width - d), g < 0 && (g = 0), g + f > y.height && (g = y.height - f), this.labelBounds = { x: p, y: g, width: d, height: f }, t.fillStyle = s.labelBackground, t.strokeStyle = s.lineColor, t.lineWidth = 2, this.roundRect(t, p, g, d, f, 8), t.fill(), t.stroke(), t.fillStyle = s.labelColor, t.font = `${s.labelFontSize}px system-ui, sans-serif`, t.textAlign = "left", t.textBaseline = "top", h.forEach((b, x) => {
2374
+ t.fillText(b, p + r, g + r + x * a);
2375
+ });
2373
2376
  }
2374
- /**
2375
- * Subscribe to tooltip events
2376
- */
2377
- on(t, i) {
2378
- this.events.on(t, i);
2377
+ drawStatusIndicator(t) {
2378
+ const i = this.options, e = this.ctx.getPlotArea();
2379
+ let n = "", s = "#666";
2380
+ if (this.state.selectionState === "idle" ? (n = "📏 Click a point to start measuring", s = i.highlightColor) : this.state.selectionState === "waitingSecond" ? (n = "📏 Click second point to complete", s = "#f59e0b") : this.state.selectionState === "complete" && (n = "📏 Measurement complete (ESC to clear)", s = "#4ade80"), !n) return;
2381
+ t.font = "12px system-ui, sans-serif";
2382
+ const h = t.measureText(n).width + 8 * 2, a = 24, r = e.x + 10, d = e.y + 10;
2383
+ t.fillStyle = "rgba(0, 0, 0, 0.75)", this.roundRect(t, r, d, h, a, 4), t.fill(), t.beginPath(), t.arc(r + 16, d + a / 2, 4, 0, Math.PI * 2), t.fillStyle = s, t.fill(), t.fillStyle = "#fff", t.textAlign = "left", t.textBaseline = "middle", t.fillText(n, r + 26, d + a / 2);
2379
2384
  }
2380
- /**
2381
- * Unsubscribe from tooltip events
2382
- */
2383
- off(t, i) {
2384
- this.events.off(t, i);
2385
+ roundRect(t, i, e, n, s, o) {
2386
+ t.beginPath(), t.moveTo(i + o, e), t.lineTo(i + n - o, e), t.quadraticCurveTo(i + n, e, i + n, e + o), t.lineTo(i + n, e + s - o), t.quadraticCurveTo(i + n, e + s, i + n - o, e + s), t.lineTo(i + o, e + s), t.quadraticCurveTo(i, e + s, i, e + s - o), t.lineTo(i, e + o), t.quadraticCurveTo(i, e, i + o, e), t.closePath();
2385
2387
  }
2386
- /**
2387
- * Destroy the tooltip manager
2388
- */
2389
- destroy() {
2390
- this.clearShowTimeout(), this.clearHideTimeout(), this.activeTooltips.clear(), this.templates.clear(), this.events.clear();
2388
+ formatValue(t) {
2389
+ const i = Math.abs(t);
2390
+ return i === 0 ? "0" : i >= 1e6 || i < 1e-3 ? t.toExponential(this.options.precision - 1) : t.toPrecision(this.options.precision);
2391
2391
  }
2392
2392
  }
2393
2393
  const Ht = {
@@ -2421,14 +2421,14 @@ function Wt(c = {}) {
2421
2421
  }),
2422
2422
  onMeasure: (l) => n.events.emit("measure", l)
2423
2423
  };
2424
- if ((c.enableDeltaTool ?? !0) && (t = new ft(s)), (c.enablePeakTool ?? !0) && (i = new pt(s)), c.useEnhancedTooltips ?? !0) {
2424
+ if ((c.enableDeltaTool ?? !0) && (t = new Bt(s)), (c.enablePeakTool ?? !0) && (i = new kt(s)), c.useEnhancedTooltips ?? !0) {
2425
2425
  const l = n.chart;
2426
2426
  n.log.debug("TooltipManager init - checking deps:", {
2427
2427
  hasCtx2d: !!n.render.ctx2d,
2428
2428
  hasXScale: !!l.xScale,
2429
2429
  hasYScales: !!l.yScales,
2430
2430
  seriesCount: n.data.getAllSeries().length
2431
- }), n.render.ctx2d ? e = new kt({
2431
+ }), n.render.ctx2d ? e = new Dt({
2432
2432
  overlayCtx: n.render.ctx2d,
2433
2433
  chartTheme: n.ui.theme,
2434
2434
  getPlotArea: () => n.render.plotArea,
@@ -2461,11 +2461,11 @@ function Wt(c = {}) {
2461
2461
  };
2462
2462
  }
2463
2463
  export {
2464
- ft as D,
2464
+ Bt as D,
2465
2465
  Wt as P,
2466
- kt as T,
2467
- bt as a,
2468
- pt as b,
2466
+ Dt as T,
2467
+ yt as a,
2468
+ kt as b,
2469
2469
  B as g
2470
2470
  };
2471
- //# sourceMappingURL=index-qunX30Xu.js.map
2471
+ //# sourceMappingURL=index-B7PTX94B.js.map