matterviz 0.3.4 → 0.3.6

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 (852) hide show
  1. package/.vscode/launch.json +13 -0
  2. package/.vscodeignore +7 -0
  3. package/dist/assets/STLExporter-BpTH3YHE.js +8 -0
  4. package/dist/assets/browser-DdDecX_W.js +1 -0
  5. package/dist/assets/export-qgn-H9y6.js +2 -0
  6. package/dist/assets/main-DiKYzti2.css +1 -0
  7. package/dist/assets/moyo_wasm_bg-0ocwg7xY.wasm +0 -0
  8. package/dist/extension.js +31293 -0
  9. package/dist/src/lib/FilePicker.svelte +360 -0
  10. package/dist/src/lib/MillerIndexInput.svelte +66 -0
  11. package/dist/src/lib/api/mp.ts +26 -0
  12. package/dist/src/lib/api/optimade.ts +204 -0
  13. package/dist/src/lib/app.css +247 -0
  14. package/dist/src/lib/brillouin/BrillouinZone.svelte +549 -0
  15. package/dist/src/lib/brillouin/BrillouinZoneControls.svelte +144 -0
  16. package/dist/src/lib/brillouin/BrillouinZoneExportPane.svelte +146 -0
  17. package/dist/src/lib/brillouin/BrillouinZoneInfoPane.svelte +146 -0
  18. package/dist/src/lib/brillouin/BrillouinZoneScene.svelte +476 -0
  19. package/dist/src/lib/brillouin/BrillouinZoneTooltip.svelte +92 -0
  20. package/dist/src/lib/brillouin/compute.ts +529 -0
  21. package/dist/src/lib/brillouin/index.ts +8 -0
  22. package/dist/src/lib/brillouin/types.ts +51 -0
  23. package/dist/src/lib/chempot-diagram/ChemPotDiagram.svelte +327 -0
  24. package/dist/src/lib/chempot-diagram/ChemPotDiagram2D.svelte +846 -0
  25. package/dist/src/lib/chempot-diagram/ChemPotDiagram3D.svelte +3193 -0
  26. package/dist/src/lib/chempot-diagram/async-compute.svelte.ts +94 -0
  27. package/dist/src/lib/chempot-diagram/chempot-worker.ts +11 -0
  28. package/dist/src/lib/chempot-diagram/color.ts +42 -0
  29. package/dist/src/lib/chempot-diagram/compute.ts +1014 -0
  30. package/dist/src/lib/chempot-diagram/index.ts +6 -0
  31. package/dist/src/lib/chempot-diagram/pointer.ts +56 -0
  32. package/dist/src/lib/chempot-diagram/temperature.ts +77 -0
  33. package/dist/src/lib/chempot-diagram/types.ts +130 -0
  34. package/dist/src/lib/colors/index.ts +249 -0
  35. package/dist/src/lib/composition/BarChart.svelte +297 -0
  36. package/dist/src/lib/composition/BubbleChart.svelte +218 -0
  37. package/dist/src/lib/composition/Composition.svelte +165 -0
  38. package/dist/src/lib/composition/Formula.svelte +268 -0
  39. package/dist/src/lib/composition/FormulaFilter.svelte +1257 -0
  40. package/dist/src/lib/composition/PieChart.svelte +323 -0
  41. package/dist/src/lib/composition/format.ts +155 -0
  42. package/dist/src/lib/composition/index.ts +37 -0
  43. package/dist/src/lib/composition/parse.ts +605 -0
  44. package/dist/src/lib/constants.ts +134 -0
  45. package/dist/src/lib/controls.ts +42 -0
  46. package/dist/src/lib/convex-hull/ConvexHull.svelte +157 -0
  47. package/dist/src/lib/convex-hull/ConvexHull2D.svelte +825 -0
  48. package/dist/src/lib/convex-hull/ConvexHull3D.svelte +1801 -0
  49. package/dist/src/lib/convex-hull/ConvexHull4D.svelte +1398 -0
  50. package/dist/src/lib/convex-hull/ConvexHullControls.svelte +535 -0
  51. package/dist/src/lib/convex-hull/ConvexHullInfoPane.svelte +125 -0
  52. package/dist/src/lib/convex-hull/ConvexHullStats.svelte +929 -0
  53. package/dist/src/lib/convex-hull/ConvexHullTooltip.svelte +131 -0
  54. package/dist/src/lib/convex-hull/GasPressureControls.svelte +247 -0
  55. package/dist/src/lib/convex-hull/StructurePopup.svelte +151 -0
  56. package/dist/src/lib/convex-hull/TemperatureSlider.svelte +140 -0
  57. package/dist/src/lib/convex-hull/barycentric-coords.ts +246 -0
  58. package/dist/src/lib/convex-hull/demo-temperature.ts +63 -0
  59. package/dist/src/lib/convex-hull/gas-thermodynamics.ts +405 -0
  60. package/dist/src/lib/convex-hull/helpers.ts +932 -0
  61. package/dist/src/lib/convex-hull/index.ts +202 -0
  62. package/dist/src/lib/convex-hull/thermodynamics.ts +2192 -0
  63. package/dist/src/lib/convex-hull/types.ts +267 -0
  64. package/dist/src/lib/coordination/CoordinationBarPlot.svelte +311 -0
  65. package/dist/src/lib/coordination/calc-coordination.ts +93 -0
  66. package/dist/src/lib/coordination/index.ts +9 -0
  67. package/dist/src/lib/effects.svelte.ts +48 -0
  68. package/dist/src/lib/element/BohrAtom.svelte +147 -0
  69. package/dist/src/lib/element/ElementHeading.svelte +26 -0
  70. package/dist/src/lib/element/ElementPhoto.svelte +57 -0
  71. package/dist/src/lib/element/ElementStats.svelte +80 -0
  72. package/dist/src/lib/element/ElementTile.svelte +484 -0
  73. package/dist/src/lib/element/data.json.gz.d.ts +4 -0
  74. package/dist/src/lib/element/data.ts +14 -0
  75. package/dist/src/lib/element/index.ts +8 -0
  76. package/dist/src/lib/element/types.ts +62 -0
  77. package/dist/src/lib/feedback/ClickFeedback.svelte +58 -0
  78. package/dist/src/lib/feedback/DragOverlay.svelte +42 -0
  79. package/dist/src/lib/feedback/index.ts +4 -0
  80. package/dist/src/lib/fermi-surface/FermiSlice.svelte +189 -0
  81. package/dist/src/lib/fermi-surface/FermiSurface.svelte +600 -0
  82. package/dist/src/lib/fermi-surface/FermiSurfaceControls.svelte +448 -0
  83. package/dist/src/lib/fermi-surface/FermiSurfaceScene.svelte +794 -0
  84. package/dist/src/lib/fermi-surface/FermiSurfaceTooltip.svelte +111 -0
  85. package/dist/src/lib/fermi-surface/compute.ts +728 -0
  86. package/dist/src/lib/fermi-surface/constants.ts +32 -0
  87. package/dist/src/lib/fermi-surface/export.ts +64 -0
  88. package/dist/src/lib/fermi-surface/index.ts +14 -0
  89. package/dist/src/lib/fermi-surface/marching-cubes.ts +3 -0
  90. package/dist/src/lib/fermi-surface/parse.ts +574 -0
  91. package/dist/src/lib/fermi-surface/symmetry.ts +56 -0
  92. package/dist/src/lib/fermi-surface/types.ts +159 -0
  93. package/dist/src/lib/heatmap-matrix/HeatmapMatrix.svelte +1545 -0
  94. package/dist/src/lib/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
  95. package/dist/src/lib/heatmap-matrix/index.ts +167 -0
  96. package/dist/src/lib/heatmap-matrix/shared.ts +7 -0
  97. package/dist/src/lib/icons.ts +650 -0
  98. package/dist/src/lib/index.ts +61 -0
  99. package/dist/src/lib/io/decompress.ts +92 -0
  100. package/dist/src/lib/io/export.ts +385 -0
  101. package/dist/src/lib/io/fetch.ts +46 -0
  102. package/dist/src/lib/io/file-drop.ts +51 -0
  103. package/dist/src/lib/io/index.ts +7 -0
  104. package/dist/src/lib/io/is-binary.ts +24 -0
  105. package/dist/src/lib/io/types.ts +8 -0
  106. package/dist/src/lib/io/url-drop.ts +141 -0
  107. package/dist/src/lib/isosurface/Isosurface.svelte +285 -0
  108. package/dist/src/lib/isosurface/IsosurfaceControls.svelte +277 -0
  109. package/dist/src/lib/isosurface/index.ts +7 -0
  110. package/dist/src/lib/isosurface/parse.ts +656 -0
  111. package/dist/src/lib/isosurface/slice.ts +175 -0
  112. package/dist/src/lib/isosurface/types.ts +309 -0
  113. package/dist/src/lib/labels.ts +320 -0
  114. package/dist/src/lib/layout/FullscreenToggle.svelte +50 -0
  115. package/dist/src/lib/layout/InfoCard.svelte +120 -0
  116. package/dist/src/lib/layout/InfoTag.svelte +185 -0
  117. package/dist/src/lib/layout/PropertyFilter.svelte +246 -0
  118. package/dist/src/lib/layout/SettingsSection.svelte +148 -0
  119. package/dist/src/lib/layout/SubpageGrid.svelte +82 -0
  120. package/dist/src/lib/layout/fullscreen.ts +65 -0
  121. package/dist/src/lib/layout/index.ts +11 -0
  122. package/dist/src/lib/layout/json-tree/JsonNode.svelte +548 -0
  123. package/dist/src/lib/layout/json-tree/JsonTree.svelte +1230 -0
  124. package/dist/src/lib/layout/json-tree/JsonValue.svelte +334 -0
  125. package/dist/src/lib/layout/json-tree/index.ts +3 -0
  126. package/dist/src/lib/layout/json-tree/types.ts +126 -0
  127. package/dist/src/lib/layout/json-tree/utils.ts +682 -0
  128. package/dist/src/lib/marching-cubes.ts +614 -0
  129. package/dist/src/lib/math.ts +1081 -0
  130. package/dist/src/lib/overlays/ContextMenu.svelte +162 -0
  131. package/dist/src/lib/overlays/CopyButton.svelte +45 -0
  132. package/dist/src/lib/overlays/DragControlTab.svelte +98 -0
  133. package/dist/src/lib/overlays/DraggablePane.svelte +487 -0
  134. package/dist/src/lib/overlays/InfoPaneCards.svelte +149 -0
  135. package/dist/src/lib/overlays/index.ts +3 -0
  136. package/dist/src/lib/periodic-table/PeriodicTable.svelte +469 -0
  137. package/dist/src/lib/periodic-table/PeriodicTableControls.svelte +557 -0
  138. package/dist/src/lib/periodic-table/PropertySelect.svelte +37 -0
  139. package/dist/src/lib/periodic-table/index.ts +12 -0
  140. package/dist/src/lib/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +1086 -0
  141. package/dist/src/lib/phase-diagram/PhaseDiagramControls.svelte +444 -0
  142. package/dist/src/lib/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
  143. package/dist/src/lib/phase-diagram/PhaseDiagramExportPane.svelte +184 -0
  144. package/dist/src/lib/phase-diagram/PhaseDiagramTooltip.svelte +391 -0
  145. package/dist/src/lib/phase-diagram/TdbInfoPanel.svelte +203 -0
  146. package/dist/src/lib/phase-diagram/build-diagram.ts +186 -0
  147. package/dist/src/lib/phase-diagram/colors.ts +58 -0
  148. package/dist/src/lib/phase-diagram/diagram-input.ts +40 -0
  149. package/dist/src/lib/phase-diagram/index.ts +13 -0
  150. package/dist/src/lib/phase-diagram/parse.ts +348 -0
  151. package/dist/src/lib/phase-diagram/svg-to-diagram.ts +1023 -0
  152. package/dist/src/lib/phase-diagram/types.ts +144 -0
  153. package/dist/src/lib/phase-diagram/utils.ts +775 -0
  154. package/dist/src/lib/plot/AxisLabel.svelte +51 -0
  155. package/dist/src/lib/plot/BarPlot.svelte +2113 -0
  156. package/dist/src/lib/plot/BarPlotControls.svelte +66 -0
  157. package/dist/src/lib/plot/BinnedScatterPlot.svelte +1114 -0
  158. package/dist/src/lib/plot/ColorBar.svelte +721 -0
  159. package/dist/src/lib/plot/ColorScaleSelect.svelte +54 -0
  160. package/dist/src/lib/plot/ElementScatter.svelte +63 -0
  161. package/dist/src/lib/plot/FillArea.svelte +223 -0
  162. package/dist/src/lib/plot/Histogram.svelte +1558 -0
  163. package/dist/src/lib/plot/HistogramControls.svelte +212 -0
  164. package/dist/src/lib/plot/InteractiveAxisLabel.svelte +96 -0
  165. package/dist/src/lib/plot/Line.svelte +84 -0
  166. package/dist/src/lib/plot/PlotAxis.svelte +169 -0
  167. package/dist/src/lib/plot/PlotControls.svelte +537 -0
  168. package/dist/src/lib/plot/PlotLegend.svelte +569 -0
  169. package/dist/src/lib/plot/PlotTooltip.svelte +67 -0
  170. package/dist/src/lib/plot/PortalSelect.svelte +253 -0
  171. package/dist/src/lib/plot/ReferenceLine3D.svelte +156 -0
  172. package/dist/src/lib/plot/ReferencePlane.svelte +175 -0
  173. package/dist/src/lib/plot/ScatterPlot.svelte +2778 -0
  174. package/dist/src/lib/plot/ScatterPlot3D.svelte +529 -0
  175. package/dist/src/lib/plot/ScatterPlot3DControls.svelte +437 -0
  176. package/dist/src/lib/plot/ScatterPlot3DScene.svelte +912 -0
  177. package/dist/src/lib/plot/ScatterPlotControls.svelte +306 -0
  178. package/dist/src/lib/plot/ScatterPoint.svelte +182 -0
  179. package/dist/src/lib/plot/SpacegroupBarPlot.svelte +293 -0
  180. package/dist/src/lib/plot/Surface3D.svelte +197 -0
  181. package/dist/src/lib/plot/ZeroLines.svelte +97 -0
  182. package/dist/src/lib/plot/ZoomRect.svelte +23 -0
  183. package/dist/src/lib/plot/adaptive-density.ts +316 -0
  184. package/dist/src/lib/plot/auto-place.ts +184 -0
  185. package/dist/src/lib/plot/axis-utils.ts +122 -0
  186. package/dist/src/lib/plot/binned-scatter-types.ts +83 -0
  187. package/dist/src/lib/plot/data-cleaning.ts +1069 -0
  188. package/dist/src/lib/plot/data-transform.ts +69 -0
  189. package/dist/src/lib/plot/defaults.ts +9 -0
  190. package/dist/src/lib/plot/fill-utils.ts +494 -0
  191. package/dist/src/lib/plot/hover-lock.svelte.ts +60 -0
  192. package/dist/src/lib/plot/index.ts +53 -0
  193. package/dist/src/lib/plot/interactions.ts +119 -0
  194. package/dist/src/lib/plot/layout.ts +425 -0
  195. package/dist/src/lib/plot/reference-line.ts +426 -0
  196. package/dist/src/lib/plot/scales.ts +654 -0
  197. package/dist/src/lib/plot/svg.ts +23 -0
  198. package/dist/src/lib/plot/types.ts +1144 -0
  199. package/dist/src/lib/plot/utils/label-placement.ts +541 -0
  200. package/dist/src/lib/plot/utils/series-visibility.ts +140 -0
  201. package/dist/src/lib/plot/utils.ts +11 -0
  202. package/dist/src/lib/rdf/RdfPlot.svelte +247 -0
  203. package/dist/src/lib/rdf/calc-rdf.ts +167 -0
  204. package/dist/src/lib/rdf/index.ts +27 -0
  205. package/dist/src/lib/sanitize.ts +126 -0
  206. package/dist/src/lib/settings.ts +1479 -0
  207. package/dist/src/lib/spectral/Bands.svelte +1040 -0
  208. package/dist/src/lib/spectral/BandsAndDos.svelte +134 -0
  209. package/dist/src/lib/spectral/BrillouinBandsDos.svelte +252 -0
  210. package/dist/src/lib/spectral/Dos.svelte +697 -0
  211. package/dist/src/lib/spectral/helpers.ts +1381 -0
  212. package/dist/src/lib/spectral/index.ts +8 -0
  213. package/dist/src/lib/spectral/types.ts +112 -0
  214. package/dist/src/lib/state.svelte.ts +64 -0
  215. package/dist/src/lib/structure/Arrow.svelte +72 -0
  216. package/dist/src/lib/structure/AtomLegend.svelte +815 -0
  217. package/dist/src/lib/structure/Bond.svelte +140 -0
  218. package/dist/src/lib/structure/CanvasTooltip.svelte +33 -0
  219. package/dist/src/lib/structure/CellSelect.svelte +349 -0
  220. package/dist/src/lib/structure/Cylinder.svelte +45 -0
  221. package/dist/src/lib/structure/Lattice.svelte +196 -0
  222. package/dist/src/lib/structure/Structure.svelte +2248 -0
  223. package/dist/src/lib/structure/StructureControls.svelte +1273 -0
  224. package/dist/src/lib/structure/StructureExportPane.svelte +252 -0
  225. package/dist/src/lib/structure/StructureInfoPane.svelte +737 -0
  226. package/dist/src/lib/structure/StructureScene.svelte +2255 -0
  227. package/dist/src/lib/structure/atom-properties.ts +316 -0
  228. package/dist/src/lib/structure/bond-order-perception.ts +447 -0
  229. package/dist/src/lib/structure/bonding.ts +944 -0
  230. package/dist/src/lib/structure/export.ts +861 -0
  231. package/dist/src/lib/structure/index.ts +291 -0
  232. package/dist/src/lib/structure/label-placement.ts +130 -0
  233. package/dist/src/lib/structure/measure.ts +45 -0
  234. package/dist/src/lib/structure/parse.ts +1705 -0
  235. package/dist/src/lib/structure/partial-occupancy.ts +183 -0
  236. package/dist/src/lib/structure/pbc.ts +164 -0
  237. package/dist/src/lib/structure/supercell.ts +226 -0
  238. package/dist/src/lib/structure/validation.ts +11 -0
  239. package/dist/src/lib/symmetry/SymmetryStats.svelte +226 -0
  240. package/dist/src/lib/symmetry/WyckoffTable.svelte +120 -0
  241. package/dist/src/lib/symmetry/cell-transform.ts +118 -0
  242. package/dist/src/lib/symmetry/index.ts +348 -0
  243. package/dist/src/lib/symmetry/spacegroups.ts +404 -0
  244. package/dist/src/lib/table/HeatmapTable.svelte +1833 -0
  245. package/dist/src/lib/table/ToggleMenu.svelte +385 -0
  246. package/dist/src/lib/table/index.ts +139 -0
  247. package/dist/src/lib/theme/ThemeControl.svelte +53 -0
  248. package/dist/src/lib/theme/index.ts +107 -0
  249. package/dist/src/lib/theme/themes.mjs +297 -0
  250. package/dist/src/lib/time.ts +71 -0
  251. package/dist/src/lib/tooltip/TooltipContent.svelte +58 -0
  252. package/dist/src/lib/tooltip/index.ts +2 -0
  253. package/dist/src/lib/tooltip/types.ts +13 -0
  254. package/dist/src/lib/trajectory/Trajectory.svelte +1545 -0
  255. package/dist/src/lib/trajectory/TrajectoryError.svelte +128 -0
  256. package/dist/src/lib/trajectory/TrajectoryExportPane.svelte +357 -0
  257. package/dist/src/lib/trajectory/TrajectoryInfoPane.svelte +313 -0
  258. package/dist/src/lib/trajectory/constants.ts +7 -0
  259. package/dist/src/lib/trajectory/extract.ts +196 -0
  260. package/dist/src/lib/trajectory/format-detect.ts +96 -0
  261. package/dist/src/lib/trajectory/frame-reader.ts +456 -0
  262. package/dist/src/lib/trajectory/helpers.ts +217 -0
  263. package/dist/src/lib/trajectory/index.ts +218 -0
  264. package/dist/src/lib/trajectory/parse/ase.ts +109 -0
  265. package/dist/src/lib/trajectory/parse/hdf5.ts +173 -0
  266. package/dist/src/lib/trajectory/parse/index.ts +411 -0
  267. package/dist/src/lib/trajectory/parse/lammps.ts +215 -0
  268. package/dist/src/lib/trajectory/parse/vasp.ts +102 -0
  269. package/dist/src/lib/trajectory/parse/xyz.ts +143 -0
  270. package/dist/src/lib/trajectory/plotting.ts +599 -0
  271. package/dist/src/lib/trajectory/types.ts +13 -0
  272. package/dist/src/lib/utils.ts +56 -0
  273. package/dist/src/lib/xrd/XrdPlot.svelte +615 -0
  274. package/dist/src/lib/xrd/broadening.ts +130 -0
  275. package/dist/src/lib/xrd/calc-xrd.ts +397 -0
  276. package/dist/src/lib/xrd/index.ts +38 -0
  277. package/dist/src/lib/xrd/parse.ts +858 -0
  278. package/dist/webview.js +29421 -0
  279. package/icon.png +0 -0
  280. package/license +1 -1
  281. package/matterviz-0.3.2.vsix +0 -0
  282. package/matterviz-0.3.4.vsix +0 -0
  283. package/matterviz-0.3.5.vsix +0 -0
  284. package/package.json +1461 -231
  285. package/readme.md +171 -98
  286. package/scripts/sync-config.ts +101 -0
  287. package/src/declarations.d.ts +2 -0
  288. package/src/extension.ts +972 -0
  289. package/src/node-io.ts +65 -0
  290. package/src/types.ts +17 -0
  291. package/src/webview/JsonBrowser.svelte +1079 -0
  292. package/src/webview/PlotPanel.svelte +346 -0
  293. package/src/webview/detect.ts +444 -0
  294. package/src/webview/main.ts +764 -0
  295. package/src/webview/plot-utils.ts +250 -0
  296. package/test-fixtures/all-viz-types.json.gz +0 -0
  297. package/test-fixtures/plot-demo-data.json.gz +0 -0
  298. package/tests/detect.test.ts +604 -0
  299. package/tests/extension.test.ts +2041 -0
  300. package/tests/node-io.test.ts +39 -0
  301. package/tests/plot-utils.test.ts +302 -0
  302. package/tests/vite-plugin-json-gz.test.ts +114 -0
  303. package/tests/vscode-mock.ts +18 -0
  304. package/tests/webview.test.ts +231 -0
  305. package/tsconfig.json +20 -0
  306. package/vite-plugin-json-gz.ts +29 -0
  307. package/vite.config.ts +34 -0
  308. package/vite.extension.config.ts +34 -0
  309. package/dist/EmptyState.svelte.d.ts +0 -9
  310. package/dist/FilePicker.svelte +0 -360
  311. package/dist/FilePicker.svelte.d.ts +0 -17
  312. package/dist/Icon.svelte.d.ts +0 -13
  313. package/dist/MillerIndexInput.svelte +0 -66
  314. package/dist/MillerIndexInput.svelte.d.ts +0 -7
  315. package/dist/api/mp.d.ts +0 -6
  316. package/dist/api/mp.js +0 -22
  317. package/dist/api/optimade.d.ts +0 -45
  318. package/dist/api/optimade.js +0 -135
  319. package/dist/app.css +0 -240
  320. package/dist/brillouin/BrillouinZone.svelte +0 -543
  321. package/dist/brillouin/BrillouinZone.svelte.d.ts +0 -83
  322. package/dist/brillouin/BrillouinZoneControls.svelte +0 -144
  323. package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +0 -17
  324. package/dist/brillouin/BrillouinZoneExportPane.svelte +0 -148
  325. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +0 -15
  326. package/dist/brillouin/BrillouinZoneInfoPane.svelte +0 -146
  327. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +0 -13
  328. package/dist/brillouin/BrillouinZoneScene.svelte +0 -476
  329. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +0 -48
  330. package/dist/brillouin/BrillouinZoneTooltip.svelte +0 -92
  331. package/dist/brillouin/BrillouinZoneTooltip.svelte.d.ts +0 -8
  332. package/dist/brillouin/compute.d.ts +0 -17
  333. package/dist/brillouin/compute.js +0 -422
  334. package/dist/brillouin/index.d.ts +0 -8
  335. package/dist/brillouin/index.js +0 -8
  336. package/dist/brillouin/types.d.ts +0 -48
  337. package/dist/brillouin/types.js +0 -1
  338. package/dist/chempot-diagram/ChemPotDiagram.svelte +0 -327
  339. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +0 -13
  340. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +0 -847
  341. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +0 -16
  342. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +0 -3194
  343. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +0 -16
  344. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +0 -7
  345. package/dist/chempot-diagram/async-compute.svelte.d.ts +0 -3
  346. package/dist/chempot-diagram/async-compute.svelte.js +0 -77
  347. package/dist/chempot-diagram/chempot-worker.d.ts +0 -1
  348. package/dist/chempot-diagram/chempot-worker.js +0 -11
  349. package/dist/chempot-diagram/color.d.ts +0 -10
  350. package/dist/chempot-diagram/color.js +0 -32
  351. package/dist/chempot-diagram/compute.d.ts +0 -48
  352. package/dist/chempot-diagram/compute.js +0 -812
  353. package/dist/chempot-diagram/index.d.ts +0 -6
  354. package/dist/chempot-diagram/index.js +0 -6
  355. package/dist/chempot-diagram/pointer.d.ts +0 -16
  356. package/dist/chempot-diagram/pointer.js +0 -40
  357. package/dist/chempot-diagram/temperature.d.ts +0 -15
  358. package/dist/chempot-diagram/temperature.js +0 -36
  359. package/dist/chempot-diagram/types.d.ts +0 -86
  360. package/dist/chempot-diagram/types.js +0 -28
  361. package/dist/colors/index.d.ts +0 -47
  362. package/dist/colors/index.js +0 -203
  363. package/dist/composition/BarChart.svelte +0 -297
  364. package/dist/composition/BarChart.svelte.d.ts +0 -39
  365. package/dist/composition/BubbleChart.svelte +0 -218
  366. package/dist/composition/BubbleChart.svelte.d.ts +0 -28
  367. package/dist/composition/Composition.svelte +0 -164
  368. package/dist/composition/Composition.svelte.d.ts +0 -15
  369. package/dist/composition/Formula.svelte +0 -265
  370. package/dist/composition/Formula.svelte.d.ts +0 -19
  371. package/dist/composition/FormulaFilter.svelte +0 -1259
  372. package/dist/composition/FormulaFilter.svelte.d.ts +0 -51
  373. package/dist/composition/PieChart.svelte +0 -323
  374. package/dist/composition/PieChart.svelte.d.ts +0 -37
  375. package/dist/composition/format.d.ts +0 -15
  376. package/dist/composition/format.js +0 -109
  377. package/dist/composition/index.d.ts +0 -20
  378. package/dist/composition/index.js +0 -14
  379. package/dist/composition/parse.d.ts +0 -55
  380. package/dist/composition/parse.js +0 -459
  381. package/dist/constants.d.ts +0 -29
  382. package/dist/constants.js +0 -105
  383. package/dist/controls.d.ts +0 -14
  384. package/dist/controls.js +0 -30
  385. package/dist/convex-hull/ConvexHull.svelte +0 -157
  386. package/dist/convex-hull/ConvexHull.svelte.d.ts +0 -13
  387. package/dist/convex-hull/ConvexHull2D.svelte +0 -813
  388. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +0 -11
  389. package/dist/convex-hull/ConvexHull3D.svelte +0 -1788
  390. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +0 -8
  391. package/dist/convex-hull/ConvexHull4D.svelte +0 -1374
  392. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +0 -8
  393. package/dist/convex-hull/ConvexHullControls.svelte +0 -546
  394. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +0 -48
  395. package/dist/convex-hull/ConvexHullInfoPane.svelte +0 -115
  396. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +0 -18
  397. package/dist/convex-hull/ConvexHullStats.svelte +0 -905
  398. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +0 -15
  399. package/dist/convex-hull/ConvexHullTooltip.svelte +0 -131
  400. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +0 -33
  401. package/dist/convex-hull/GasPressureControls.svelte +0 -247
  402. package/dist/convex-hull/GasPressureControls.svelte.d.ts +0 -11
  403. package/dist/convex-hull/StructurePopup.svelte +0 -116
  404. package/dist/convex-hull/StructurePopup.svelte.d.ts +0 -18
  405. package/dist/convex-hull/TemperatureSlider.svelte +0 -137
  406. package/dist/convex-hull/TemperatureSlider.svelte.d.ts +0 -8
  407. package/dist/convex-hull/barycentric-coords.d.ts +0 -18
  408. package/dist/convex-hull/barycentric-coords.js +0 -182
  409. package/dist/convex-hull/demo-temperature.d.ts +0 -6
  410. package/dist/convex-hull/demo-temperature.js +0 -40
  411. package/dist/convex-hull/gas-thermodynamics.d.ts +0 -16
  412. package/dist/convex-hull/gas-thermodynamics.js +0 -316
  413. package/dist/convex-hull/helpers.d.ts +0 -103
  414. package/dist/convex-hull/helpers.js +0 -671
  415. package/dist/convex-hull/index.d.ts +0 -118
  416. package/dist/convex-hull/index.js +0 -57
  417. package/dist/convex-hull/thermodynamics.d.ts +0 -66
  418. package/dist/convex-hull/thermodynamics.js +0 -1752
  419. package/dist/convex-hull/types.d.ts +0 -162
  420. package/dist/convex-hull/types.js +0 -36
  421. package/dist/coordination/CoordinationBarPlot.svelte +0 -311
  422. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +0 -30
  423. package/dist/coordination/calc-coordination.d.ts +0 -15
  424. package/dist/coordination/calc-coordination.js +0 -63
  425. package/dist/coordination/index.d.ts +0 -8
  426. package/dist/coordination/index.js +0 -7
  427. package/dist/element/BohrAtom.svelte +0 -149
  428. package/dist/element/BohrAtom.svelte.d.ts +0 -20
  429. package/dist/element/ElementHeading.svelte +0 -26
  430. package/dist/element/ElementHeading.svelte.d.ts +0 -8
  431. package/dist/element/ElementPhoto.svelte +0 -57
  432. package/dist/element/ElementPhoto.svelte.d.ts +0 -9
  433. package/dist/element/ElementStats.svelte +0 -80
  434. package/dist/element/ElementStats.svelte.d.ts +0 -8
  435. package/dist/element/ElementTile.svelte +0 -484
  436. package/dist/element/ElementTile.svelte.d.ts +0 -29
  437. package/dist/element/Nucleus.svelte.d.ts +0 -17
  438. package/dist/element/data.d.ts +0 -3
  439. package/dist/element/data.js +0 -2
  440. package/dist/element/data.json.gz.d.ts +0 -2
  441. package/dist/element/index.d.ts +0 -8
  442. package/dist/element/index.js +0 -8
  443. package/dist/element/types.d.ts +0 -57
  444. package/dist/element/types.js +0 -1
  445. package/dist/feedback/ClickFeedback.svelte +0 -58
  446. package/dist/feedback/ClickFeedback.svelte.d.ts +0 -12
  447. package/dist/feedback/DragOverlay.svelte +0 -42
  448. package/dist/feedback/DragOverlay.svelte.d.ts +0 -7
  449. package/dist/feedback/Spinner.svelte.d.ts +0 -7
  450. package/dist/feedback/StatusMessage.svelte.d.ts +0 -9
  451. package/dist/feedback/index.d.ts +0 -4
  452. package/dist/feedback/index.js +0 -4
  453. package/dist/fermi-surface/FermiSlice.svelte +0 -189
  454. package/dist/fermi-surface/FermiSlice.svelte.d.ts +0 -24
  455. package/dist/fermi-surface/FermiSurface.svelte +0 -597
  456. package/dist/fermi-surface/FermiSurface.svelte.d.ts +0 -83
  457. package/dist/fermi-surface/FermiSurfaceControls.svelte +0 -452
  458. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +0 -35
  459. package/dist/fermi-surface/FermiSurfaceScene.svelte +0 -792
  460. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +0 -50
  461. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +0 -111
  462. package/dist/fermi-surface/FermiSurfaceTooltip.svelte.d.ts +0 -8
  463. package/dist/fermi-surface/compute.d.ts +0 -5
  464. package/dist/fermi-surface/compute.js +0 -538
  465. package/dist/fermi-surface/constants.d.ts +0 -9
  466. package/dist/fermi-surface/constants.js +0 -27
  467. package/dist/fermi-surface/export.d.ts +0 -5
  468. package/dist/fermi-surface/export.js +0 -63
  469. package/dist/fermi-surface/index.d.ts +0 -12
  470. package/dist/fermi-surface/index.js +0 -13
  471. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  472. package/dist/fermi-surface/marching-cubes.js +0 -2
  473. package/dist/fermi-surface/parse.d.ts +0 -2
  474. package/dist/fermi-surface/parse.js +0 -495
  475. package/dist/fermi-surface/symmetry.d.ts +0 -3
  476. package/dist/fermi-surface/symmetry.js +0 -46
  477. package/dist/fermi-surface/types.d.ts +0 -113
  478. package/dist/fermi-surface/types.js +0 -4
  479. package/dist/heatmap-matrix/HeatmapMatrix.svelte +0 -1527
  480. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +0 -110
  481. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +0 -225
  482. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +0 -30
  483. package/dist/heatmap-matrix/index.d.ts +0 -53
  484. package/dist/heatmap-matrix/index.js +0 -100
  485. package/dist/heatmap-matrix/shared.d.ts +0 -2
  486. package/dist/heatmap-matrix/shared.js +0 -4
  487. package/dist/icons.d.ts +0 -569
  488. package/dist/icons.js +0 -648
  489. package/dist/index.d.ts +0 -39
  490. package/dist/index.js +0 -39
  491. package/dist/io/decompress.d.ts +0 -10
  492. package/dist/io/decompress.js +0 -69
  493. package/dist/io/export.d.ts +0 -16
  494. package/dist/io/export.js +0 -312
  495. package/dist/io/fetch.d.ts +0 -5
  496. package/dist/io/fetch.js +0 -39
  497. package/dist/io/file-drop.d.ts +0 -7
  498. package/dist/io/file-drop.js +0 -43
  499. package/dist/io/index.d.ts +0 -7
  500. package/dist/io/index.js +0 -7
  501. package/dist/io/is-binary.d.ts +0 -1
  502. package/dist/io/is-binary.js +0 -5
  503. package/dist/io/types.d.ts +0 -8
  504. package/dist/io/types.js +0 -1
  505. package/dist/io/url-drop.d.ts +0 -2
  506. package/dist/io/url-drop.js +0 -117
  507. package/dist/isosurface/Isosurface.svelte +0 -285
  508. package/dist/isosurface/Isosurface.svelte.d.ts +0 -8
  509. package/dist/isosurface/IsosurfaceControls.svelte +0 -291
  510. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +0 -9
  511. package/dist/isosurface/index.d.ts +0 -5
  512. package/dist/isosurface/index.js +0 -6
  513. package/dist/isosurface/parse.d.ts +0 -6
  514. package/dist/isosurface/parse.js +0 -553
  515. package/dist/isosurface/slice.d.ts +0 -11
  516. package/dist/isosurface/slice.js +0 -140
  517. package/dist/isosurface/types.d.ts +0 -56
  518. package/dist/isosurface/types.js +0 -227
  519. package/dist/labels.d.ts +0 -53
  520. package/dist/labels.js +0 -274
  521. package/dist/layout/FullscreenToggle.svelte +0 -50
  522. package/dist/layout/FullscreenToggle.svelte.d.ts +0 -7
  523. package/dist/layout/InfoCard.svelte +0 -120
  524. package/dist/layout/InfoCard.svelte.d.ts +0 -21
  525. package/dist/layout/InfoTag.svelte +0 -183
  526. package/dist/layout/InfoTag.svelte.d.ts +0 -19
  527. package/dist/layout/PropertyFilter.svelte +0 -244
  528. package/dist/layout/PropertyFilter.svelte.d.ts +0 -24
  529. package/dist/layout/SettingsSection.svelte +0 -148
  530. package/dist/layout/SettingsSection.svelte.d.ts +0 -17
  531. package/dist/layout/SubpageGrid.svelte +0 -82
  532. package/dist/layout/SubpageGrid.svelte.d.ts +0 -14
  533. package/dist/layout/fullscreen.d.ts +0 -9
  534. package/dist/layout/fullscreen.js +0 -53
  535. package/dist/layout/index.d.ts +0 -10
  536. package/dist/layout/index.js +0 -8
  537. package/dist/layout/json-tree/JsonNode.svelte +0 -547
  538. package/dist/layout/json-tree/JsonNode.svelte.d.ts +0 -11
  539. package/dist/layout/json-tree/JsonTree.svelte +0 -1222
  540. package/dist/layout/json-tree/JsonTree.svelte.d.ts +0 -6
  541. package/dist/layout/json-tree/JsonValue.svelte +0 -334
  542. package/dist/layout/json-tree/JsonValue.svelte.d.ts +0 -9
  543. package/dist/layout/json-tree/index.d.ts +0 -3
  544. package/dist/layout/json-tree/index.js +0 -3
  545. package/dist/layout/json-tree/types.d.ts +0 -73
  546. package/dist/layout/json-tree/types.js +0 -3
  547. package/dist/layout/json-tree/utils.d.ts +0 -29
  548. package/dist/layout/json-tree/utils.js +0 -648
  549. package/dist/marching-cubes.d.ts +0 -14
  550. package/dist/marching-cubes.js +0 -542
  551. package/dist/math.d.ts +0 -91
  552. package/dist/math.js +0 -896
  553. package/dist/overlays/ContextMenu.svelte +0 -162
  554. package/dist/overlays/ContextMenu.svelte.d.ts +0 -25
  555. package/dist/overlays/DraggablePane.svelte +0 -564
  556. package/dist/overlays/DraggablePane.svelte.d.ts +0 -36
  557. package/dist/overlays/index.d.ts +0 -2
  558. package/dist/overlays/index.js +0 -2
  559. package/dist/periodic-table/PeriodicTable.svelte +0 -469
  560. package/dist/periodic-table/PeriodicTable.svelte.d.ts +0 -55
  561. package/dist/periodic-table/PeriodicTableControls.svelte +0 -557
  562. package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +0 -24
  563. package/dist/periodic-table/PropertySelect.svelte +0 -37
  564. package/dist/periodic-table/PropertySelect.svelte.d.ts +0 -13
  565. package/dist/periodic-table/TableInset.svelte.d.ts +0 -9
  566. package/dist/periodic-table/index.d.ts +0 -10
  567. package/dist/periodic-table/index.js +0 -4
  568. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +0 -1086
  569. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +0 -44
  570. package/dist/phase-diagram/PhaseDiagramControls.svelte +0 -451
  571. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +0 -30
  572. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +0 -126
  573. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +0 -15
  574. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +0 -192
  575. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +0 -19
  576. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +0 -392
  577. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +0 -16
  578. package/dist/phase-diagram/TdbInfoPanel.svelte +0 -203
  579. package/dist/phase-diagram/TdbInfoPanel.svelte.d.ts +0 -12
  580. package/dist/phase-diagram/build-diagram.d.ts +0 -11
  581. package/dist/phase-diagram/build-diagram.js +0 -167
  582. package/dist/phase-diagram/colors.d.ts +0 -35
  583. package/dist/phase-diagram/colors.js +0 -51
  584. package/dist/phase-diagram/diagram-input.d.ts +0 -33
  585. package/dist/phase-diagram/diagram-input.js +0 -3
  586. package/dist/phase-diagram/index.d.ts +0 -13
  587. package/dist/phase-diagram/index.js +0 -13
  588. package/dist/phase-diagram/parse.d.ts +0 -55
  589. package/dist/phase-diagram/parse.js +0 -276
  590. package/dist/phase-diagram/svg-to-diagram.d.ts +0 -2
  591. package/dist/phase-diagram/svg-to-diagram.js +0 -869
  592. package/dist/phase-diagram/types.d.ts +0 -99
  593. package/dist/phase-diagram/types.js +0 -1
  594. package/dist/phase-diagram/utils.d.ts +0 -118
  595. package/dist/phase-diagram/utils.js +0 -606
  596. package/dist/plot/AxisLabel.svelte +0 -51
  597. package/dist/plot/AxisLabel.svelte.d.ts +0 -16
  598. package/dist/plot/BarPlot.svelte +0 -2256
  599. package/dist/plot/BarPlot.svelte.d.ts +0 -82
  600. package/dist/plot/BarPlotControls.svelte +0 -66
  601. package/dist/plot/BarPlotControls.svelte.d.ts +0 -18
  602. package/dist/plot/ColorBar.svelte +0 -719
  603. package/dist/plot/ColorBar.svelte.d.ts +0 -31
  604. package/dist/plot/ColorScaleSelect.svelte +0 -54
  605. package/dist/plot/ColorScaleSelect.svelte.d.ts +0 -15
  606. package/dist/plot/ElementScatter.svelte +0 -63
  607. package/dist/plot/ElementScatter.svelte.d.ts +0 -14
  608. package/dist/plot/FillArea.svelte +0 -226
  609. package/dist/plot/FillArea.svelte.d.ts +0 -20
  610. package/dist/plot/Histogram.svelte +0 -1654
  611. package/dist/plot/Histogram.svelte.d.ts +0 -50
  612. package/dist/plot/HistogramControls.svelte +0 -212
  613. package/dist/plot/HistogramControls.svelte.d.ts +0 -22
  614. package/dist/plot/InteractiveAxisLabel.svelte +0 -94
  615. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +0 -14
  616. package/dist/plot/Line.svelte +0 -85
  617. package/dist/plot/Line.svelte.d.ts +0 -15
  618. package/dist/plot/PlotControls.svelte +0 -537
  619. package/dist/plot/PlotControls.svelte.d.ts +0 -4
  620. package/dist/plot/PlotLegend.svelte +0 -498
  621. package/dist/plot/PlotLegend.svelte.d.ts +0 -25
  622. package/dist/plot/PlotTooltip.svelte +0 -67
  623. package/dist/plot/PlotTooltip.svelte.d.ts +0 -17
  624. package/dist/plot/PortalSelect.svelte +0 -253
  625. package/dist/plot/PortalSelect.svelte.d.ts +0 -16
  626. package/dist/plot/ReferenceLine.svelte.d.ts +0 -20
  627. package/dist/plot/ReferenceLine3D.svelte +0 -154
  628. package/dist/plot/ReferenceLine3D.svelte.d.ts +0 -14
  629. package/dist/plot/ReferencePlane.svelte +0 -178
  630. package/dist/plot/ReferencePlane.svelte.d.ts +0 -14
  631. package/dist/plot/ScatterPlot.svelte +0 -2831
  632. package/dist/plot/ScatterPlot.svelte.d.ts +0 -92
  633. package/dist/plot/ScatterPlot3D.svelte +0 -499
  634. package/dist/plot/ScatterPlot3D.svelte.d.ts +0 -94
  635. package/dist/plot/ScatterPlot3DControls.svelte +0 -437
  636. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +0 -20
  637. package/dist/plot/ScatterPlot3DScene.svelte +0 -912
  638. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +0 -74
  639. package/dist/plot/ScatterPlotControls.svelte +0 -307
  640. package/dist/plot/ScatterPlotControls.svelte.d.ts +0 -17
  641. package/dist/plot/ScatterPoint.svelte +0 -185
  642. package/dist/plot/ScatterPoint.svelte.d.ts +0 -19
  643. package/dist/plot/SpacegroupBarPlot.svelte +0 -292
  644. package/dist/plot/SpacegroupBarPlot.svelte.d.ts +0 -9
  645. package/dist/plot/Surface3D.svelte +0 -200
  646. package/dist/plot/Surface3D.svelte.d.ts +0 -13
  647. package/dist/plot/ZeroLines.svelte +0 -96
  648. package/dist/plot/ZeroLines.svelte.d.ts +0 -32
  649. package/dist/plot/ZoomRect.svelte +0 -23
  650. package/dist/plot/ZoomRect.svelte.d.ts +0 -8
  651. package/dist/plot/axis-utils.d.ts +0 -19
  652. package/dist/plot/axis-utils.js +0 -80
  653. package/dist/plot/data-cleaning.d.ts +0 -37
  654. package/dist/plot/data-cleaning.js +0 -855
  655. package/dist/plot/data-transform.d.ts +0 -16
  656. package/dist/plot/data-transform.js +0 -45
  657. package/dist/plot/defaults.d.ts +0 -19
  658. package/dist/plot/defaults.js +0 -9
  659. package/dist/plot/fill-utils.d.ts +0 -51
  660. package/dist/plot/fill-utils.js +0 -337
  661. package/dist/plot/hover-lock.svelte.d.ts +0 -14
  662. package/dist/plot/hover-lock.svelte.js +0 -46
  663. package/dist/plot/index.d.ts +0 -43
  664. package/dist/plot/index.js +0 -37
  665. package/dist/plot/interactions.d.ts +0 -12
  666. package/dist/plot/interactions.js +0 -100
  667. package/dist/plot/layout.d.ts +0 -60
  668. package/dist/plot/layout.js +0 -230
  669. package/dist/plot/reference-line.d.ts +0 -60
  670. package/dist/plot/reference-line.js +0 -316
  671. package/dist/plot/scales.d.ts +0 -48
  672. package/dist/plot/scales.js +0 -484
  673. package/dist/plot/svg.d.ts +0 -1
  674. package/dist/plot/svg.js +0 -11
  675. package/dist/plot/types.d.ts +0 -863
  676. package/dist/plot/types.js +0 -103
  677. package/dist/plot/utils/label-placement.d.ts +0 -47
  678. package/dist/plot/utils/label-placement.js +0 -256
  679. package/dist/plot/utils/series-visibility.d.ts +0 -9
  680. package/dist/plot/utils/series-visibility.js +0 -67
  681. package/dist/plot/utils.d.ts +0 -1
  682. package/dist/plot/utils.js +0 -14
  683. package/dist/rdf/RdfPlot.svelte +0 -247
  684. package/dist/rdf/RdfPlot.svelte.d.ts +0 -27
  685. package/dist/rdf/calc-rdf.d.ts +0 -4
  686. package/dist/rdf/calc-rdf.js +0 -111
  687. package/dist/rdf/index.d.ts +0 -23
  688. package/dist/rdf/index.js +0 -2
  689. package/dist/sanitize.d.ts +0 -4
  690. package/dist/sanitize.js +0 -107
  691. package/dist/settings.d.ts +0 -253
  692. package/dist/settings.js +0 -1123
  693. package/dist/spectral/Bands.svelte +0 -1040
  694. package/dist/spectral/Bands.svelte.d.ts +0 -40
  695. package/dist/spectral/BandsAndDos.svelte +0 -128
  696. package/dist/spectral/BandsAndDos.svelte.d.ts +0 -18
  697. package/dist/spectral/BrillouinBandsDos.svelte +0 -248
  698. package/dist/spectral/BrillouinBandsDos.svelte.d.ts +0 -20
  699. package/dist/spectral/Dos.svelte +0 -697
  700. package/dist/spectral/Dos.svelte.d.ts +0 -29
  701. package/dist/spectral/helpers.d.ts +0 -117
  702. package/dist/spectral/helpers.js +0 -1023
  703. package/dist/spectral/index.d.ts +0 -6
  704. package/dist/spectral/index.js +0 -7
  705. package/dist/spectral/types.d.ts +0 -84
  706. package/dist/spectral/types.js +0 -2
  707. package/dist/state.svelte.d.ts +0 -25
  708. package/dist/state.svelte.js +0 -45
  709. package/dist/structure/Arrow.svelte +0 -72
  710. package/dist/structure/Arrow.svelte.d.ts +0 -15
  711. package/dist/structure/AtomLegend.svelte +0 -798
  712. package/dist/structure/AtomLegend.svelte.d.ts +0 -34
  713. package/dist/structure/Bond.svelte +0 -140
  714. package/dist/structure/Bond.svelte.d.ts +0 -9
  715. package/dist/structure/CanvasTooltip.svelte +0 -33
  716. package/dist/structure/CanvasTooltip.svelte.d.ts +0 -12
  717. package/dist/structure/CellSelect.svelte +0 -351
  718. package/dist/structure/CellSelect.svelte.d.ts +0 -13
  719. package/dist/structure/Cylinder.svelte +0 -45
  720. package/dist/structure/Cylinder.svelte.d.ts +0 -10
  721. package/dist/structure/Lattice.svelte +0 -196
  722. package/dist/structure/Lattice.svelte.d.ts +0 -17
  723. package/dist/structure/Structure.svelte +0 -1857
  724. package/dist/structure/Structure.svelte.d.ts +0 -83
  725. package/dist/structure/StructureControls.svelte +0 -1184
  726. package/dist/structure/StructureControls.svelte.d.ts +0 -31
  727. package/dist/structure/StructureExportPane.svelte +0 -251
  728. package/dist/structure/StructureExportPane.svelte.d.ts +0 -17
  729. package/dist/structure/StructureInfoPane.svelte +0 -434
  730. package/dist/structure/StructureInfoPane.svelte.d.ts +0 -18
  731. package/dist/structure/StructureScene.svelte +0 -1574
  732. package/dist/structure/StructureScene.svelte.d.ts +0 -104
  733. package/dist/structure/atom-properties.d.ts +0 -37
  734. package/dist/structure/atom-properties.js +0 -198
  735. package/dist/structure/bonding.d.ts +0 -33
  736. package/dist/structure/bonding.js +0 -304
  737. package/dist/structure/export.d.ts +0 -20
  738. package/dist/structure/export.js +0 -725
  739. package/dist/structure/ferrox-wasm-types.d.ts +0 -46
  740. package/dist/structure/ferrox-wasm-types.js +0 -18
  741. package/dist/structure/ferrox-wasm.d.ts +0 -94
  742. package/dist/structure/ferrox-wasm.js +0 -249
  743. package/dist/structure/index.d.ts +0 -110
  744. package/dist/structure/index.js +0 -168
  745. package/dist/structure/measure.d.ts +0 -6
  746. package/dist/structure/measure.js +0 -29
  747. package/dist/structure/parse.d.ts +0 -65
  748. package/dist/structure/parse.js +0 -1374
  749. package/dist/structure/partial-occupancy.d.ts +0 -25
  750. package/dist/structure/partial-occupancy.js +0 -99
  751. package/dist/structure/pbc.d.ts +0 -9
  752. package/dist/structure/pbc.js +0 -123
  753. package/dist/structure/supercell.d.ts +0 -8
  754. package/dist/structure/supercell.js +0 -137
  755. package/dist/structure/validation.d.ts +0 -2
  756. package/dist/structure/validation.js +0 -10
  757. package/dist/symmetry/SymmetryStats.svelte +0 -226
  758. package/dist/symmetry/SymmetryStats.svelte.d.ts +0 -21
  759. package/dist/symmetry/WyckoffTable.svelte +0 -113
  760. package/dist/symmetry/WyckoffTable.svelte.d.ts +0 -11
  761. package/dist/symmetry/cell-transform.d.ts +0 -12
  762. package/dist/symmetry/cell-transform.js +0 -77
  763. package/dist/symmetry/index.d.ts +0 -43
  764. package/dist/symmetry/index.js +0 -229
  765. package/dist/symmetry/spacegroups.d.ts +0 -9
  766. package/dist/symmetry/spacegroups.js +0 -394
  767. package/dist/table/HeatmapTable.svelte +0 -1854
  768. package/dist/table/HeatmapTable.svelte.d.ts +0 -49
  769. package/dist/table/ToggleMenu.svelte +0 -376
  770. package/dist/table/ToggleMenu.svelte.d.ts +0 -11
  771. package/dist/table/index.d.ts +0 -74
  772. package/dist/table/index.js +0 -38
  773. package/dist/theme/ThemeControl.svelte +0 -53
  774. package/dist/theme/ThemeControl.svelte.d.ts +0 -9
  775. package/dist/theme/index.d.ts +0 -29
  776. package/dist/theme/index.js +0 -79
  777. package/dist/theme/themes.mjs +0 -285
  778. package/dist/time.d.ts +0 -4
  779. package/dist/time.js +0 -70
  780. package/dist/tooltip/TooltipContent.svelte +0 -58
  781. package/dist/tooltip/TooltipContent.svelte.d.ts +0 -31
  782. package/dist/tooltip/index.d.ts +0 -2
  783. package/dist/tooltip/index.js +0 -2
  784. package/dist/tooltip/types.d.ts +0 -8
  785. package/dist/tooltip/types.js +0 -1
  786. package/dist/trajectory/Trajectory.svelte +0 -1517
  787. package/dist/trajectory/Trajectory.svelte.d.ts +0 -77
  788. package/dist/trajectory/TrajectoryError.svelte +0 -128
  789. package/dist/trajectory/TrajectoryError.svelte.d.ts +0 -13
  790. package/dist/trajectory/TrajectoryExportPane.svelte +0 -357
  791. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +0 -17
  792. package/dist/trajectory/TrajectoryInfoPane.svelte +0 -387
  793. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +0 -17
  794. package/dist/trajectory/constants.d.ts +0 -6
  795. package/dist/trajectory/constants.js +0 -7
  796. package/dist/trajectory/extract.d.ts +0 -5
  797. package/dist/trajectory/extract.js +0 -162
  798. package/dist/trajectory/format-detect.d.ts +0 -9
  799. package/dist/trajectory/format-detect.js +0 -76
  800. package/dist/trajectory/frame-reader.d.ts +0 -17
  801. package/dist/trajectory/frame-reader.js +0 -332
  802. package/dist/trajectory/helpers.d.ts +0 -14
  803. package/dist/trajectory/helpers.js +0 -172
  804. package/dist/trajectory/index.d.ts +0 -63
  805. package/dist/trajectory/index.js +0 -126
  806. package/dist/trajectory/parse/ase.d.ts +0 -2
  807. package/dist/trajectory/parse/ase.js +0 -77
  808. package/dist/trajectory/parse/hdf5.d.ts +0 -2
  809. package/dist/trajectory/parse/hdf5.js +0 -129
  810. package/dist/trajectory/parse/index.d.ts +0 -12
  811. package/dist/trajectory/parse/index.js +0 -299
  812. package/dist/trajectory/parse/lammps.d.ts +0 -5
  813. package/dist/trajectory/parse/lammps.js +0 -179
  814. package/dist/trajectory/parse/vasp.d.ts +0 -2
  815. package/dist/trajectory/parse/vasp.js +0 -68
  816. package/dist/trajectory/parse/xyz.d.ts +0 -2
  817. package/dist/trajectory/parse/xyz.js +0 -110
  818. package/dist/trajectory/plotting.d.ts +0 -28
  819. package/dist/trajectory/plotting.js +0 -423
  820. package/dist/trajectory/types.d.ts +0 -11
  821. package/dist/trajectory/types.js +0 -1
  822. package/dist/utils.d.ts +0 -5
  823. package/dist/utils.js +0 -36
  824. package/dist/xrd/XrdPlot.svelte +0 -615
  825. package/dist/xrd/XrdPlot.svelte.d.ts +0 -28
  826. package/dist/xrd/broadening.d.ts +0 -20
  827. package/dist/xrd/broadening.js +0 -97
  828. package/dist/xrd/calc-xrd.d.ts +0 -37
  829. package/dist/xrd/calc-xrd.js +0 -337
  830. package/dist/xrd/index.d.ts +0 -37
  831. package/dist/xrd/index.js +0 -4
  832. package/dist/xrd/parse.d.ts +0 -13
  833. package/dist/xrd/parse.js +0 -749
  834. /package/dist/{EmptyState.svelte → src/lib/EmptyState.svelte} +0 -0
  835. /package/dist/{Icon.svelte → src/lib/Icon.svelte} +0 -0
  836. /package/dist/{chempot-diagram → src/lib/chempot-diagram}/ChemPotScene3D.svelte +0 -0
  837. /package/dist/{colors → src/lib/colors}/alloy-colors.json +0 -0
  838. /package/dist/{colors → src/lib/colors}/dark-mode-colors.json +0 -0
  839. /package/dist/{colors → src/lib/colors}/jmol-colors.json +0 -0
  840. /package/dist/{colors → src/lib/colors}/muted-colors.json +0 -0
  841. /package/dist/{colors → src/lib/colors}/pastel-colors.json +0 -0
  842. /package/dist/{colors → src/lib/colors}/vesta-colors.json +0 -0
  843. /package/dist/{element → src/lib/element}/Nucleus.svelte +0 -0
  844. /package/dist/{element → src/lib/element}/data.json +0 -0
  845. /package/dist/{element → src/lib/element}/data.json.gz +0 -0
  846. /package/dist/{element → src/lib/element}/data.schema.json +0 -0
  847. /package/dist/{element-image-urls.json → src/lib/element-image-urls.json} +0 -0
  848. /package/dist/{feedback → src/lib/feedback}/Spinner.svelte +0 -0
  849. /package/dist/{feedback → src/lib/feedback}/StatusMessage.svelte +0 -0
  850. /package/dist/{periodic-table → src/lib/periodic-table}/TableInset.svelte +0 -0
  851. /package/dist/{plot → src/lib/plot}/ReferenceLine.svelte +0 -0
  852. /package/dist/{xrd → src/lib/xrd}/atomic_scattering_params.json +0 -0
@@ -1,2831 +0,0 @@
1
- <script
2
- lang="ts"
3
- generics="Metadata extends Record<string, unknown> = Record<string, unknown>"
4
- >
5
- import type { D3ColorSchemeName, D3InterpolateName } from '../colors'
6
- import type { D3SymbolName } from '../labels'
7
- import { format_value, symbol_names } from '../labels'
8
- import { sanitize_html } from '../sanitize'
9
- import { FullscreenToggle, set_fullscreen_bg } from '../layout'
10
- import type { Vec2 } from '../math'
11
- import type {
12
- AxisLoadError,
13
- BasePlotProps,
14
- ControlsConfig,
15
- DataLoaderFn,
16
- DataSeries,
17
- ErrorBand,
18
- FillHandlerEvent,
19
- FillRegion,
20
- HoverConfig,
21
- InitialRanges,
22
- InternalPoint,
23
- LabelPlacementConfig,
24
- LegendConfig,
25
- PanConfig,
26
- PlotConfig,
27
- Point,
28
- RefLine,
29
- RefLineEvent,
30
- ScaleType,
31
- ScatterHandlerEvent,
32
- ScatterHandlerProps,
33
- Sides,
34
- StyleOverrides,
35
- TweenedOptions,
36
- UserContentProps,
37
- XyObj,
38
- } from './'
39
- import {
40
- AxisLabel,
41
- ColorBar,
42
- compute_element_placement,
43
- FillArea,
44
- get_tick_label,
45
- Line,
46
- PlotLegend,
47
- PlotTooltip,
48
- ReferenceLine,
49
- ScatterPlotControls,
50
- ScatterPoint,
51
- ZeroLines,
52
- ZoomRect,
53
- } from './'
54
- import type { AxisChangeState } from './axis-utils'
55
- import { create_axis_change_handler } from './axis-utils'
56
- import {
57
- get_series_color,
58
- get_series_symbol,
59
- process_prop,
60
- } from './data-transform'
61
- import { AXIS_DEFAULTS } from './defaults'
62
- import {
63
- create_dimension_tracker,
64
- create_hover_lock,
65
- } from './hover-lock.svelte'
66
- import {
67
- DEFAULT_GRID_STYLE,
68
- DEFAULT_MARKERS,
69
- get_scale_type_name,
70
- is_time_scale,
71
- } from './types'
72
- import { compute_label_positions } from './utils/label-placement'
73
- import {
74
- handle_legend_double_click,
75
- toggle_group_visibility,
76
- toggle_series_visibility,
77
- } from './utils/series-visibility'
78
- import { DEFAULTS } from '../settings'
79
- import { extent } from 'd3-array'
80
- import { scaleTime } from 'd3-scale'
81
- import type { ComponentProps, Snippet } from 'svelte'
82
- import { untrack } from 'svelte'
83
- import type { HTMLAttributes } from 'svelte/elements'
84
- import { Tween } from 'svelte/motion'
85
- import { SvelteSet } from 'svelte/reactivity'
86
- import type { FillPathPoint } from './fill-utils'
87
- import {
88
- apply_range_constraints,
89
- apply_where_condition,
90
- clamp_for_log_scale,
91
- convert_error_band_to_fill_region,
92
- generate_fill_path,
93
- is_fill_gradient,
94
- resolve_boundary,
95
- } from './fill-utils'
96
- import {
97
- expand_range_if_needed,
98
- get_relative_coords,
99
- normalize_y2_sync,
100
- pan_range,
101
- PINCH_ZOOM_THRESHOLD,
102
- pixels_to_data_delta,
103
- sync_y2_range,
104
- } from './interactions'
105
- import type { Rect } from './layout'
106
- import {
107
- calc_auto_padding,
108
- constrain_tooltip_position,
109
- filter_padding,
110
- LABEL_GAP_DEFAULT,
111
- measure_max_tick_width,
112
- } from './layout'
113
- import type { IndexedRefLine } from './reference-line'
114
- import { group_ref_lines_by_z, index_ref_lines } from './reference-line'
115
- import {
116
- create_color_scale,
117
- create_scale,
118
- create_size_scale,
119
- generate_ticks,
120
- get_nice_data_range,
121
- } from './scales'
122
-
123
- let {
124
- series = $bindable([]),
125
- x_axis = $bindable({}),
126
- x2_axis = $bindable({}),
127
- y_axis = $bindable({}),
128
- y2_axis = $bindable({}),
129
- display = $bindable(DEFAULTS.scatter.display),
130
- styles: styles_init = {},
131
- controls: controls_init = {},
132
- padding = {},
133
- range_padding = 0.05,
134
- current_x_value = null,
135
- tooltip_point = $bindable(null),
136
- selected_point = null,
137
- hovered = $bindable(false),
138
- tooltip,
139
- user_content,
140
- change = () => {},
141
- color_scale = {
142
- type: `linear`,
143
- scheme: `interpolateViridis`,
144
- value_range: undefined,
145
- },
146
- color_bar = {},
147
- size_scale = { type: `linear`, radius_range: [2, 10], value_range: undefined },
148
- label_placement_config = {},
149
- hover_config = {},
150
- legend = {},
151
- point_tween,
152
- line_tween,
153
- point_events,
154
- on_point_click,
155
- on_point_hover,
156
- fill_regions = $bindable([]),
157
- error_bands = [],
158
- on_fill_click,
159
- on_fill_hover,
160
- ref_lines = $bindable([]),
161
- on_ref_line_click,
162
- on_ref_line_hover,
163
- selected_series_idx = $bindable(0),
164
- wrapper = $bindable(),
165
- fullscreen = $bindable(false),
166
- fullscreen_toggle = true,
167
- children,
168
- header_controls,
169
- controls_extra,
170
- data_loader,
171
- on_axis_change,
172
- on_error,
173
- pan = {},
174
- ...rest
175
- }: HTMLAttributes<HTMLDivElement> & Omit<BasePlotProps, `change`> & PlotConfig & {
176
- series?: DataSeries<Metadata>[]
177
- styles?: StyleOverrides
178
- controls?: ControlsConfig
179
- current_x_value?: number | null
180
- tooltip_point?: InternalPoint<Metadata> | null
181
- selected_point?: { series_idx: number; point_idx: number } | null
182
- tooltip?: Snippet<[ScatterHandlerProps<Metadata>]>
183
- user_content?: Snippet<[UserContentProps]>
184
- header_controls?: Snippet<
185
- [{ height: number; width: number; fullscreen: boolean }]
186
- >
187
- controls_extra?: Snippet<
188
- [
189
- & { styles: StyleOverrides; selected_series_idx: number }
190
- & Required<PlotConfig>,
191
- ]
192
- >
193
- change?: (
194
- data: (Point<Metadata> & { series: DataSeries<Metadata> }) | null,
195
- ) => void
196
- color_scale?: {
197
- type?: ScaleType
198
- scheme?: D3ColorSchemeName | D3InterpolateName
199
- value_range?: [number, number]
200
- } | D3InterpolateName
201
- size_scale?: {
202
- type?: ScaleType
203
- radius_range?: [number, number]
204
- value_range?: [number, number]
205
- }
206
- color_bar?:
207
- | (ComponentProps<typeof ColorBar> & {
208
- margin?: number | Sides
209
- tween?: TweenedOptions<XyObj>
210
- responsive?: boolean // Allow colorbar to reposition if density changes (default: false)
211
- })
212
- | null
213
- label_placement_config?: Partial<LabelPlacementConfig>
214
- hover_config?: Partial<HoverConfig>
215
- legend?: LegendConfig | null
216
- point_tween?: TweenedOptions<XyObj>
217
- line_tween?: TweenedOptions<string>
218
- point_events?: Record<
219
- string,
220
- (payload: { point: InternalPoint<Metadata>; event: Event }) => void
221
- >
222
- on_point_click?: (data: ScatterHandlerEvent<Metadata>) => void
223
- on_point_hover?: (data: ScatterHandlerEvent<Metadata> | null) => void
224
- fill_regions?: FillRegion[] // Bindable for legend toggle support
225
- error_bands?: ErrorBand[]
226
- on_fill_click?: (event: FillHandlerEvent) => void
227
- on_fill_hover?: (event: FillHandlerEvent | null) => void
228
- ref_lines?: RefLine[] // Bindable for legend toggle support
229
- on_ref_line_click?: (event: RefLineEvent) => void
230
- on_ref_line_hover?: (event: RefLineEvent | null) => void
231
- selected_series_idx?: number
232
- wrapper?: HTMLDivElement
233
- // Interactive axis props
234
- data_loader?: DataLoaderFn<Metadata>
235
- on_axis_change?: (
236
- axis: `x` | `x2` | `y` | `y2`,
237
- key: string,
238
- new_series: DataSeries<Metadata>[],
239
- ) => void
240
- on_error?: (error: AxisLoadError) => void
241
- pan?: PanConfig
242
- } = $props()
243
-
244
- // Merged axis/display values with defaults (use $derived to avoid breaking $bindable)
245
- const final_x_axis = $derived({
246
- ...AXIS_DEFAULTS,
247
- label_shift: { x: 0, y: -40 }, // x-axis needs different label position
248
- ...(x_axis ?? {}),
249
- })
250
- const final_y_axis = $derived({ ...AXIS_DEFAULTS, ...(y_axis ?? {}) })
251
- const final_x2_axis = $derived({
252
- ...AXIS_DEFAULTS,
253
- label_shift: { x: 0, y: 40 }, // x2-axis label above top edge
254
- ...(x2_axis ?? {}),
255
- })
256
- const final_y2_axis = $derived({ ...AXIS_DEFAULTS, ...(y2_axis ?? {}) })
257
- // Cache time-axis check — used in ~10 places for scale/tick/tooltip logic
258
- let is_time_x = $derived(
259
- is_time_scale(final_x_axis.scale_type, final_x_axis.format),
260
- )
261
- let is_time_x2 = $derived(
262
- is_time_scale(final_x2_axis.scale_type, final_x2_axis.format),
263
- )
264
- const final_display = $derived({ ...DEFAULTS.scatter.display, ...(display ?? {}) })
265
- // Local state for styles (initialized from prop, owned by this component for controls)
266
- // Using $state because styles has bindings in ScatterPlotControls
267
- // untrack() explicitly captures initial prop value (intentional - props provide initial config)
268
- let styles = $state(untrack(() => ({
269
- show_points: DEFAULTS.scatter.show_points,
270
- show_lines: DEFAULTS.scatter.show_lines,
271
- point: { ...DEFAULTS.scatter.point, ...(styles_init?.point ?? {}) },
272
- line: { ...DEFAULTS.scatter.line, ...(styles_init?.line ?? {}) },
273
- ...(styles_init ?? {}),
274
- })))
275
- let controls = $derived({ show: true, open: false, ...controls_init })
276
-
277
- let [width, height] = $state([0, 0])
278
- let svg_element: SVGElement | null = $state(null) // Bind the SVG element
279
- let svg_bounding_box: DOMRect | null = $state(null) // Store SVG bounds during drag
280
-
281
- // Track which specific control properties user has modified
282
- let touched = new SvelteSet<string>()
283
-
284
- // Unique component ID to avoid clipPath conflicts between multiple instances
285
- let component_id = $state(`scatter-${crypto.randomUUID()}`)
286
- let clip_path_id = $derived(`plot-area-clip-${component_id}`)
287
-
288
- // Assign stable IDs to series for keying
289
- let series_with_ids = $derived(
290
- series.map((srs: DataSeries<Metadata>, idx: number) => {
291
- if (!srs || typeof srs !== `object`) return srs
292
- // Use series.id if provided, otherwise fall back to index
293
- // prevents re-mounts when series are reordered if stable IDs are provided
294
- return { ...srs, _id: srs.id ?? idx }
295
- }),
296
- )
297
-
298
- // State for rectangle zoom selection
299
- let drag_start_coords = $state<XyObj | null>(null)
300
- let drag_current_coords = $state<XyObj | null>(null)
301
-
302
- // Zoom/pan state - track both initial (data-driven) and current (after pan/zoom) ranges
303
- let initial_x_range = $state<[number, number]>([0, 1])
304
- let initial_x2_range = $state<[number, number]>([0, 1])
305
- let initial_y_range = $state<[number, number]>([0, 1])
306
- let initial_y2_range = $state<[number, number]>([0, 1])
307
- let zoom_x_range = $state<[number, number]>([0, 1])
308
- let zoom_x2_range = $state<[number, number]>([0, 1])
309
- let zoom_y_range = $state<[number, number]>([0, 1])
310
- let zoom_y2_range = $state<[number, number]>([0, 1])
311
- let previous_series_visibility: boolean[] | null = $state(null)
312
-
313
- // Y2 axis sync configuration
314
- let y2_sync_config = $derived(normalize_y2_sync(y2_axis?.sync))
315
- // Track previous sync mode to detect changes (updated in $effect.pre to avoid race conditions)
316
- let prev_sync_mode = $state<string>(`none`)
317
-
318
- // Helper to compute synced y2 range or return fallback when sync disabled
319
- const get_synced_y2 = (y1_range: Vec2, fallback: Vec2): Vec2 =>
320
- y2_sync_config.mode !== `none`
321
- ? sync_y2_range(y1_range, initial_y2_range, y2_sync_config)
322
- : fallback
323
-
324
- // Effect to update y2 range when sync mode changes - use $effect.pre to capture
325
- // mode change before the main range-update effect runs, ensuring sync is applied
326
- // immediately when toggled (not delayed until next data change)
327
- $effect.pre(() => {
328
- const mode = y2_sync_config.mode
329
- if (mode !== prev_sync_mode) {
330
- // When sync mode becomes enabled (or changes), apply sync immediately
331
- if (mode !== `none`) {
332
- zoom_y2_range = sync_y2_range(zoom_y_range, initial_y2_range, y2_sync_config)
333
- } else {
334
- // When switching to independent mode, reset Y2 to its data range
335
- zoom_y2_range = [...initial_y2_range] as [number, number]
336
- }
337
- prev_sync_mode = mode
338
- }
339
- })
340
-
341
- // Pan state
342
- let is_focused = $state(false)
343
- let shift_held = $state(false)
344
- let pan_drag_state = $state<
345
- InitialRanges & { start: { x: number; y: number } } | null
346
- >(null)
347
- let touch_state = $state<
348
- InitialRanges & { start_touches: { x: number; y: number }[] } | null
349
- >(null)
350
-
351
- // Fill region hover state
352
- let hovered_fill_idx = $state<number | null>(null)
353
-
354
- // Reference line hover state
355
- let hovered_ref_line_idx = $state<number | null>(null)
356
-
357
- // Interactive axis loading state
358
- let axis_loading = $state<`x` | `x2` | `y` | `y2` | null>(null)
359
-
360
- // State to hold the calculated label positions after simulation
361
- let label_positions = $state<Record<string, XyObj>>({})
362
-
363
- // State for legend dragging
364
- let legend_is_dragging = $state(false)
365
- let legend_drag_offset = $state<{ x: number; y: number }>({ x: 0, y: 0 })
366
- let legend_manual_position = $state<{ x: number; y: number } | null>(null)
367
-
368
- // State for legend/colorbar placement stability
369
- let legend_element = $state<HTMLDivElement | undefined>()
370
- let colorbar_element = $state<HTMLDivElement | undefined>()
371
- const legend_hover = create_hover_lock()
372
- const colorbar_hover = create_hover_lock()
373
- const dim_tracker = create_dimension_tracker()
374
- let has_initial_legend_placement = $state(false)
375
- let has_initial_colorbar_placement = $state(false)
376
-
377
- // Clear pending hover lock timeouts on unmount
378
- $effect(() => () => {
379
- legend_hover.cleanup()
380
- colorbar_hover.cleanup()
381
- })
382
-
383
- // Tooltip element reference for dynamic sizing
384
- let tooltip_el = $state<HTMLDivElement | undefined>()
385
-
386
- // Module-level constants to avoid repeated allocations
387
- // Create and categorize points in a single pass (instead of 3 separate iterations)
388
- type SimplePoint = { x: number; y: number }
389
- let points_by_axis = $derived.by(() => {
390
- const all: SimplePoint[] = []
391
- const y1: SimplePoint[] = []
392
- const y2: SimplePoint[] = []
393
- const x2: SimplePoint[] = []
394
-
395
- for (const srs of series_with_ids) {
396
- if (!srs) continue
397
- const { x: xs, y: ys, visible = true, y_axis = `y1`, x_axis: x_ax = `x1` } =
398
- srs as DataSeries
399
- for (let idx = 0; idx < xs.length; idx++) {
400
- const point = { x: xs[idx], y: ys[idx] }
401
- all.push(point)
402
- if (visible) {
403
- if (y_axis === `y2`) y2.push(point)
404
- else y1.push(point)
405
- if (x_ax === `x2`) x2.push(point)
406
- }
407
- }
408
- }
409
- return { all, y1, y2, x2 }
410
- })
411
-
412
- let all_points = $derived(points_by_axis.all)
413
- let y1_points = $derived(points_by_axis.y1)
414
- let y2_points = $derived(points_by_axis.y2)
415
- let x2_points = $derived(points_by_axis.x2)
416
-
417
- // Layout: dynamic padding based on tick label widths
418
- const default_padding = { t: 5, b: 50, l: 50, r: 20 }
419
- let pad = $state(untrack(() => filter_padding(padding, default_padding)))
420
-
421
- // Update padding when format or ticks change
422
- $effect(() => {
423
- const new_pad = width && height &&
424
- (y_tick_values.length || y2_tick_values.length || x2_tick_values.length)
425
- ? calc_auto_padding({
426
- padding,
427
- default_padding,
428
- x2_axis: { ...final_x2_axis, tick_values: x2_tick_values },
429
- y_axis: { ...final_y_axis, tick_values: y_tick_values },
430
- y2_axis: { ...final_y2_axis, tick_values: y2_tick_values },
431
- })
432
- : filter_padding(padding, default_padding)
433
-
434
- if (
435
- pad.t !== new_pad.t ||
436
- pad.b !== new_pad.b ||
437
- pad.l !== new_pad.l ||
438
- pad.r !== new_pad.r
439
- ) pad = new_pad
440
- })
441
-
442
- // Reactive clip area dimensions to ensure proper responsiveness
443
- let clip_area = $derived({
444
- x: pad.l || 0,
445
- y: pad.t || 0,
446
- width: isFinite(width - pad.l - pad.r) ? Math.max(1, width - pad.l - pad.r) : 1,
447
- height: isFinite(height - pad.t - pad.b)
448
- ? Math.max(1, height - pad.t - pad.b)
449
- : 1,
450
- })
451
-
452
- // Calculate plot area center coordinates
453
- let plot_center_x = $derived(pad.l + (width - pad.r - pad.l) / 2)
454
- let plot_center_y = $derived(pad.t + (height - pad.b - pad.t) / 2)
455
-
456
- // Extract color and size values in single pass (used for scale computations)
457
- let series_value_arrays = $derived.by(() => {
458
- const color_values: number[] = []
459
- const size_values: number[] = []
460
- for (const srs of series_with_ids) {
461
- if (!srs) continue
462
- const { color_values: cvs, size_values: svs } = srs as DataSeries
463
- if (cvs) { for (const val of cvs) if (val != null) color_values.push(val) }
464
- if (svs) { for (const val of svs) if (val != null) size_values.push(val) }
465
- }
466
- return { color_values, size_values }
467
- })
468
- let all_color_values = $derived(series_value_arrays.color_values)
469
-
470
- // Compute auto ranges based on data and limits
471
- let auto_x_range = $derived(
472
- get_nice_data_range(
473
- all_points,
474
- ({ x }) => x,
475
- final_x_axis.range ?? [null, null],
476
- final_x_axis.scale_type ?? `linear`,
477
- range_padding,
478
- is_time_x,
479
- ),
480
- )
481
-
482
- let auto_y_range = $derived(
483
- get_nice_data_range(
484
- y1_points,
485
- ({ y }) => y,
486
- final_y_axis.range ?? [null, null],
487
- final_y_axis.scale_type ?? `linear`,
488
- range_padding,
489
- false,
490
- ),
491
- )
492
-
493
- let auto_x2_range = $derived(
494
- get_nice_data_range(
495
- x2_points,
496
- ({ x }) => x,
497
- final_x2_axis.range ?? [null, null],
498
- final_x2_axis.scale_type ?? `linear`,
499
- range_padding,
500
- is_time_x2,
501
- ),
502
- )
503
-
504
- let auto_y2_range = $derived(
505
- get_nice_data_range(
506
- y2_points,
507
- ({ y }) => y,
508
- final_y2_axis.range ?? [null, null],
509
- final_y2_axis.scale_type ?? `linear`,
510
- range_padding,
511
- false,
512
- ),
513
- )
514
-
515
- // Update zoom ranges when auto ranges or explicit ranges change
516
- // - Explicit ranges (from zoom/pan): apply directly
517
- // - Auto ranges (from data changes): use lazy expansion to preserve view context
518
- $effect(() => {
519
- // Helper to get effective range (explicit ?? auto) and check if explicit
520
- const get_range = (
521
- axis: { range?: [number | null, number | null] },
522
- auto: Vec2,
523
- ): { explicit: boolean; range: Vec2 } => {
524
- const explicit = axis.range?.[0] != null && axis.range?.[1] != null
525
- const range = [axis.range?.[0] ?? auto[0], axis.range?.[1] ?? auto[1]] as Vec2
526
- return { explicit, range }
527
- }
528
-
529
- const x = get_range(final_x_axis, auto_x_range)
530
- const x2 = get_range(final_x2_axis, auto_x2_range)
531
- const y = get_range(final_y_axis, auto_y_range)
532
- const y2 = get_range(final_y2_axis, auto_y2_range)
533
-
534
- // X axis: explicit → direct, auto → lazy expand
535
- if (x.explicit) {
536
- zoom_x_range = x.range
537
- } else {
538
- const result = expand_range_if_needed(initial_x_range, x.range)
539
- if (result.changed) {
540
- ;[initial_x_range, zoom_x_range] = [result.range, result.range]
541
- }
542
- }
543
-
544
- // X2 axis: explicit → direct, auto → lazy expand
545
- if (x2.explicit) {
546
- zoom_x2_range = x2.range
547
- } else {
548
- const result = expand_range_if_needed(initial_x2_range, x2.range)
549
- if (result.changed) {
550
- ;[initial_x2_range, zoom_x2_range] = [result.range, result.range]
551
- }
552
- }
553
-
554
- // Y axis: explicit → direct, auto → lazy expand
555
- if (y.explicit) {
556
- zoom_y_range = y.range
557
- } else {
558
- const result = expand_range_if_needed(initial_y_range, y.range)
559
- if (result.changed) {
560
- ;[initial_y_range, zoom_y_range] = [result.range, result.range]
561
- }
562
- }
563
-
564
- // Y2 axis: explicit → direct, else expand initial range then optionally sync
565
- if (y2.explicit) {
566
- zoom_y2_range = y2.range
567
- } else {
568
- const result = expand_range_if_needed(initial_y2_range, y2.range)
569
- if (result.changed) initial_y2_range = result.range
570
- // Apply sync if enabled, otherwise use expanded range (or keep current if unchanged)
571
- if (y2_sync_config.mode !== `none`) {
572
- zoom_y2_range = sync_y2_range(zoom_y_range, initial_y2_range, y2_sync_config)
573
- } else if (result.changed) {
574
- zoom_y2_range = result.range
575
- }
576
- }
577
- })
578
-
579
- let [x_min, x_max] = $derived(zoom_x_range)
580
- let [x2_min, x2_max] = $derived(zoom_x2_range)
581
- let [y_min, y_max] = $derived(zoom_y_range)
582
- let [y2_min, y2_max] = $derived(zoom_y2_range)
583
-
584
- // Create auto color range
585
- let auto_color_range = $derived(
586
- // Ensure we only calculate extent on actual numbers, filtering out nulls/undefined
587
- all_color_values.length > 0
588
- ? extent(
589
- all_color_values.filter((color_val: number | null): color_val is number =>
590
- typeof color_val === `number`
591
- ),
592
- )
593
- : [0, 1],
594
- ) as Vec2
595
-
596
- // Create scale functions
597
- // For time scales, use scaleTime directly; otherwise use create_scale (supports linear/log/arcsinh)
598
- let x_scale_fn = $derived(
599
- is_time_x
600
- ? scaleTime()
601
- .domain([new Date(x_min), new Date(x_max)])
602
- .range([pad.l, width - pad.r])
603
- : create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [
604
- pad.l,
605
- width - pad.r,
606
- ]),
607
- )
608
-
609
- let x2_scale_fn = $derived(
610
- is_time_x2
611
- ? scaleTime()
612
- .domain([new Date(x2_min), new Date(x2_max)])
613
- .range([pad.l, width - pad.r])
614
- : create_scale(final_x2_axis.scale_type ?? `linear`, [x2_min, x2_max], [
615
- pad.l,
616
- width - pad.r,
617
- ]),
618
- )
619
-
620
- let y_scale_fn = $derived(
621
- create_scale(final_y_axis.scale_type ?? `linear`, [y_min, y_max], [
622
- height - pad.b,
623
- pad.t,
624
- ]),
625
- )
626
-
627
- let y2_scale_fn = $derived(
628
- create_scale(final_y2_axis.scale_type ?? `linear`, [y2_min, y2_max], [
629
- height - pad.b,
630
- pad.t,
631
- ]),
632
- )
633
-
634
- // All size values from series (for size scale) - extracted in series_value_arrays
635
- let all_size_values = $derived(series_value_arrays.size_values)
636
-
637
- // Size scale function (using shared utility)
638
- let size_scale_fn = $derived(create_size_scale(size_scale, all_size_values))
639
-
640
- // Color scale function (using shared utility)
641
- let color_scale_fn = $derived(create_color_scale(color_scale, auto_color_range))
642
-
643
- // Filter series data to only include points within bounds and augment with internal data
644
- let filtered_series = $derived(
645
- series_with_ids
646
- .map((data_series: DataSeries<Metadata>, series_idx): DataSeries<Metadata> => {
647
- // Handle null/undefined series first
648
- if (!data_series) {
649
- return {
650
- x: [],
651
- y: [],
652
- visible: true,
653
- filtered_data: [],
654
- _id: series_idx,
655
- orig_series_idx: series_idx,
656
- }
657
- }
658
-
659
- // Handle explicitly hidden series
660
- if (!(data_series.visible ?? true)) {
661
- return {
662
- ...data_series,
663
- visible: false,
664
- filtered_data: [],
665
- orig_series_idx: series_idx,
666
- }
667
- }
668
-
669
- const { x: xs, y: ys, color_values, size_values, ...rest } = data_series
670
-
671
- // Process points internally, adding properties beyond the base Point type
672
- const processed_points: InternalPoint<Metadata>[] = xs.map(
673
- (x_val: number, point_idx: number) => ({
674
- x: x_val,
675
- y: ys[point_idx],
676
- color_value: color_values?.[point_idx],
677
- metadata: process_prop(rest.metadata, point_idx) as Metadata | undefined,
678
- point_style: process_prop(rest.point_style, point_idx),
679
- point_hover: process_prop(rest.point_hover, point_idx),
680
- point_label: process_prop(rest.point_label, point_idx),
681
- point_offset: process_prop(rest.point_offset, point_idx),
682
- series_idx,
683
- point_idx,
684
- size_value: size_values?.[point_idx],
685
- }),
686
- )
687
-
688
- // Filter to points within the plot bounds (handles inverted ranges like [3.5, 1.4])
689
- const in_range = (val: number | null | undefined, lo: number, hi: number) =>
690
- val != null && !isNaN(val) && val >= Math.min(lo, hi) &&
691
- val <= Math.max(lo, hi)
692
-
693
- // Determine which ranges to use based on series axis properties
694
- const [series_x_min, series_x_max] = (data_series.x_axis ?? `x1`) === `x2`
695
- ? [x2_min, x2_max]
696
- : [x_min, x_max]
697
- const [series_y_min, series_y_max] = (data_series.y_axis ?? `y1`) === `y2`
698
- ? [y2_min, y2_max]
699
- : [y_min, y_max]
700
-
701
- const filtered_data_with_extras = processed_points.filter(
702
- ({ x, y }) =>
703
- in_range(x, series_x_min, series_x_max) &&
704
- in_range(y, series_y_min, series_y_max),
705
- )
706
-
707
- // Return structure consistent with DataSeries but acknowledge internal data structure (filtered_data)
708
- return {
709
- ...data_series,
710
- visible: true, // Mark series as visible here
711
- filtered_data: filtered_data_with_extras,
712
- orig_series_idx: series_idx, // Store original index for auto-cycling colors/symbols
713
- }
714
- })
715
- // Filter series end up completely empty after point filtering
716
- .filter((
717
- srs,
718
- ): srs is DataSeries<Metadata> & { filtered_data: InternalPoint<Metadata>[] } =>
719
- !!srs.filtered_data && srs.filtered_data.length > 0
720
- ),
721
- )
722
-
723
- // Collect all plot points for legend placement calculation
724
- let plot_points_for_placement = $derived.by(() => {
725
- if (!width || !height || !filtered_series) return []
726
-
727
- const points: { x: number; y: number }[] = []
728
-
729
- for (const series_data of filtered_series) {
730
- if (!series_data?.filtered_data) continue
731
- const use_x2_scale = series_data.x_axis === `x2`
732
- for (const point of series_data.filtered_data) {
733
- const active_x_scale = use_x2_scale ? x2_scale_fn : x_scale_fn
734
- const active_is_time_x = use_x2_scale ? is_time_x2 : is_time_x
735
- const point_x_coord = active_is_time_x
736
- ? active_x_scale(new Date(point.x))
737
- : active_x_scale(point.x)
738
- const point_y_coord =
739
- (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(
740
- point.y,
741
- )
742
-
743
- if (isFinite(point_x_coord) && isFinite(point_y_coord)) {
744
- points.push({ x: point_x_coord, y: point_y_coord })
745
- }
746
- }
747
- }
748
- return points
749
- })
750
-
751
- // Explicitly define the type for display_style matching PlotLegend expectations
752
- type LegendDisplayStyle = {
753
- symbol_type?: D3SymbolName
754
- symbol_color?: string
755
- line_color?: string
756
- line_dash?: string
757
- }
758
-
759
- // Computed fill regions: merge fill_regions and converted error_bands, resolve boundaries
760
- type ComputedFill = FillRegion & {
761
- idx: number
762
- source_type: `fill_region` | `error_band`
763
- source_idx: number
764
- path_segments: string[]
765
- }
766
- let computed_fills = $derived.by((): ComputedFill[] => {
767
- // Early exit: skip expensive computation if no fills to render
768
- const has_fill_regions = fill_regions && fill_regions.length > 0
769
- const has_error_bands = error_bands && error_bands.length > 0
770
- if (!has_fill_regions && !has_error_bands) return []
771
-
772
- // Merge fill_regions and converted error_bands, tracking source
773
- const all_regions: {
774
- region: FillRegion | null
775
- source_type: `fill_region` | `error_band`
776
- source_idx: number
777
- }[] = [
778
- ...(fill_regions ?? []).map((region, source_idx) => ({
779
- region,
780
- source_type: `fill_region` as const,
781
- source_idx,
782
- })),
783
- ...(error_bands ?? []).map((band, source_idx) => ({
784
- region: convert_error_band_to_fill_region(band, series_with_ids),
785
- source_type: `error_band` as const,
786
- source_idx,
787
- })),
788
- ]
789
-
790
- // Compute unique x-values once for all fills
791
- // Optimization: deduplicate first (O(n)), then sort only unique values (O(k log k))
792
- // This is faster for datasets with many duplicate x-values across series
793
- const x_set = new SvelteSet<number>()
794
- for (const data_series of series_with_ids) {
795
- if (!data_series) continue
796
- for (const val of data_series.x) {
797
- if (typeof val === `number` && isFinite(val)) x_set.add(val)
798
- }
799
- }
800
- const unique_x = [...x_set].sort((val_a, val_b) => val_a - val_b)
801
-
802
- if (unique_x.length === 0) return []
803
-
804
- return all_regions
805
- .filter((
806
- entry,
807
- ): entry is {
808
- region: FillRegion
809
- source_type: `fill_region` | `error_band`
810
- source_idx: number
811
- } => entry.region !== null)
812
- .map(({ region, source_type, source_idx }, idx) => {
813
- if (region.visible === false) return null
814
-
815
- // Domain context for boundary resolution
816
- const domains = {
817
- y_domain: [y_min, y_max] as Vec2,
818
- y2_domain: [y2_min, y2_max] as Vec2,
819
- }
820
-
821
- // Resolve upper and lower boundaries
822
- const upper_values = resolve_boundary(
823
- region.upper,
824
- series_with_ids,
825
- unique_x,
826
- domains,
827
- )
828
- const lower_values = resolve_boundary(
829
- region.lower,
830
- series_with_ids,
831
- unique_x,
832
- domains,
833
- )
834
-
835
- if (!upper_values || !lower_values) return null
836
-
837
- // Apply range constraints
838
- const range_filtered = apply_range_constraints(
839
- unique_x,
840
- lower_values,
841
- upper_values,
842
- region,
843
- )
844
-
845
- // Clamp for log scale if needed
846
- const y_scale_type = final_y_axis.scale_type ?? `linear`
847
- const x_scale_type = final_x_axis.scale_type ?? `linear`
848
- const clamped = clamp_for_log_scale(
849
- range_filtered.x,
850
- range_filtered.y1,
851
- range_filtered.y2,
852
- y_scale_type,
853
- x_scale_type,
854
- )
855
-
856
- // Apply where condition (splits into segments)
857
- const conditioned = apply_where_condition(
858
- clamped.x,
859
- clamped.y1,
860
- clamped.y2,
861
- region,
862
- )
863
-
864
- // Generate paths for each segment (convert to pixel coordinates)
865
- const path_segments = conditioned.segments
866
- .filter((segment) => segment.length > 1)
867
- .map((segment) => {
868
- const pixel_data: FillPathPoint[] = segment.map((point) => ({
869
- x: x_scale_fn(point.x),
870
- y1: y_scale_fn(point.y1),
871
- y2: y_scale_fn(point.y2),
872
- }))
873
- return generate_fill_path(pixel_data, region.curve ?? `monotoneX`)
874
- })
875
- .filter((path) => path.length > 0)
876
-
877
- if (path_segments.length === 0) return null
878
-
879
- return { ...region, idx, source_type, source_idx, path_segments }
880
- })
881
- .filter((fill): fill is ComputedFill => fill !== null)
882
- })
883
-
884
- // Prepare data needed for the legend component
885
- let legend_data = $derived.by(() => {
886
- const items = series_with_ids.map(
887
- (data_series: DataSeries & { _id?: string | number }, series_idx: number) => {
888
- const is_visible = data_series?.visible ?? true
889
- // Prefer top-level label, fallback to metadata label
890
- const explicit_label = data_series?.label ??
891
- (typeof data_series?.metadata === `object` &&
892
- data_series.metadata !== null &&
893
- `label` in data_series.metadata &&
894
- typeof data_series.metadata.label === `string`
895
- ? data_series.metadata.label
896
- : null)
897
- // Use explicit label or generate default
898
- const label = explicit_label ?? `Series ${series_idx + 1}`
899
- const has_explicit_label = explicit_label != null
900
-
901
- // Use series-specific defaults for auto-differentiation
902
- const series_default_color = get_series_color(series_idx)
903
- const series_default_symbol = get_series_symbol(series_idx)
904
-
905
- const display_style: LegendDisplayStyle = {
906
- symbol_type: series_default_symbol,
907
- symbol_color: series_default_color,
908
- line_color: series_default_color,
909
- }
910
- const series_markers = data_series?.markers ?? DEFAULT_MARKERS
911
-
912
- // Check point_style (could be object or array)
913
- const first_point_style = Array.isArray(data_series?.point_style)
914
- ? data_series.point_style[0]
915
- : data_series?.point_style
916
-
917
- if (series_markers?.includes(`points`)) {
918
- if (first_point_style) {
919
- // Use explicit symbol_type if provided and valid, otherwise keep series default
920
- if (
921
- typeof first_point_style.symbol_type === `string` &&
922
- symbol_names.includes(first_point_style.symbol_type as D3SymbolName)
923
- ) {
924
- display_style.symbol_type = first_point_style
925
- .symbol_type as D3SymbolName
926
- }
927
-
928
- // Use explicit fill color if provided
929
- if (first_point_style.fill) {
930
- display_style.symbol_color = first_point_style.fill
931
- }
932
- if (first_point_style.stroke) {
933
- // Use stroke color if fill is none or transparent
934
- if (
935
- !display_style.symbol_color ||
936
- display_style.symbol_color === `none` ||
937
- display_style.symbol_color.startsWith(`rgba(`, 0) // Check if transparent
938
- ) display_style.symbol_color = first_point_style.stroke
939
- }
940
- }
941
- // else: keep series-specific defaults for symbol_type and symbol_color
942
- } else {
943
- // If no points marker, explicitly remove marker style for legend
944
- display_style.symbol_type = undefined
945
- display_style.symbol_color = undefined
946
- }
947
-
948
- // Check line_style
949
- if (series_markers?.includes(`line`)) {
950
- // Prefer explicit line stroke, then other explicit colors, then series default
951
- let legend_line_color = data_series?.line_style?.stroke
952
- if (!legend_line_color) {
953
- // Try color scale if available
954
- const first_cv = Array.isArray(data_series?.color_values)
955
- ? data_series?.color_values?.find((color_val: number | null) =>
956
- color_val != null
957
- )
958
- : undefined
959
- legend_line_color =
960
- (first_cv != null ? color_scale_fn(first_cv) : undefined) ||
961
- first_point_style?.fill ||
962
- first_point_style?.stroke ||
963
- series_default_color
964
- }
965
- display_style.line_color = legend_line_color
966
- display_style.line_dash = data_series?.line_style?.line_dash
967
- } else {
968
- // If no line marker, explicitly remove line style for legend
969
- display_style.line_dash = undefined
970
- display_style.line_color = undefined
971
- }
972
-
973
- return {
974
- series_idx,
975
- label,
976
- visible: is_visible,
977
- display_style,
978
- has_explicit_label,
979
- legend_group: data_series?.legend_group,
980
- }
981
- },
982
- )
983
-
984
- // Deduplicate by label+legend_group - keep first occurrence of each unique combination
985
- const seen_labels = new SvelteSet<string>()
986
- const series_items = items.filter(
987
- (
988
- legend_item: {
989
- label: string
990
- series_idx: number
991
- visible: boolean
992
- display_style: LegendDisplayStyle
993
- has_explicit_label: boolean
994
- legend_group?: string
995
- },
996
- ) => {
997
- // Use label+group as unique key (group may be undefined)
998
- const unique_key = `${legend_item.legend_group ?? ``}::${legend_item.label}`
999
- if (seen_labels.has(unique_key)) return false
1000
- seen_labels.add(unique_key)
1001
- return true
1002
- },
1003
- )
1004
-
1005
- // Add fill region items to legend (deduplicated using same key format as series)
1006
- const fill_items = computed_fills
1007
- .filter((fill) => fill.show_in_legend !== false && fill.label)
1008
- .filter((fill) => {
1009
- // Use same composite key as series: legend_group::label
1010
- const unique_key = `${fill.legend_group ?? ``}::${fill.label ?? ``}`
1011
- if (seen_labels.has(unique_key)) return false
1012
- seen_labels.add(unique_key)
1013
- return true
1014
- })
1015
- .map((fill) => {
1016
- // Pass gradient for swatch rendering, or solid color as fallback
1017
- const fill_gradient = is_fill_gradient(fill.fill) ? fill.fill : undefined
1018
- const fill_color = typeof fill.fill === `string` ? fill.fill : undefined
1019
-
1020
- return {
1021
- series_idx: -1, // Not a series
1022
- fill_idx: fill.idx,
1023
- fill_source_type: fill.source_type,
1024
- fill_source_idx: fill.source_idx,
1025
- item_type: `fill` as const,
1026
- label: fill.label ?? ``,
1027
- visible: fill.visible !== false,
1028
- legend_group: fill.legend_group,
1029
- display_style: {
1030
- fill_color,
1031
- fill_opacity: fill.fill_opacity ?? 0.3,
1032
- edge_color: fill.edge_upper?.color,
1033
- fill_gradient,
1034
- },
1035
- }
1036
- })
1037
-
1038
- return [...series_items, ...fill_items]
1039
- })
1040
-
1041
- // Group fills by z-index for ordered rendering (single pass instead of 4 filters)
1042
- let fills_by_z = $derived.by(() => {
1043
- const groups: {
1044
- below_grid: typeof computed_fills
1045
- below_lines: typeof computed_fills
1046
- below_points: typeof computed_fills
1047
- above_all: typeof computed_fills
1048
- } = { below_grid: [], below_lines: [], below_points: [], above_all: [] }
1049
-
1050
- for (const fill of computed_fills) {
1051
- if (fill.z_index === `below-grid`) groups.below_grid.push(fill)
1052
- else if (fill.z_index === `below-points`) groups.below_points.push(fill)
1053
- else if (fill.z_index === `above-all`) groups.above_all.push(fill)
1054
- else groups.below_lines.push(fill) // default: no z_index or 'below-lines'
1055
- }
1056
- return groups
1057
- })
1058
-
1059
- // Compute ref_lines with index and group by z-index (using shared utilities)
1060
- let indexed_ref_lines = $derived(index_ref_lines(ref_lines))
1061
- let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines))
1062
-
1063
- // Calculate best legend placement using continuous grid sampling
1064
- let legend_placement = $derived.by(() => {
1065
- const should_place = legend != null &&
1066
- (legend_data.length > 1 || Object.keys(legend).length > 0)
1067
-
1068
- if (!should_place || !width || !height) return null
1069
-
1070
- const plot_width = width - pad.l - pad.r
1071
- const plot_height = height - pad.t - pad.b
1072
-
1073
- // Use measured size if available, otherwise estimate
1074
- const legend_size = legend_element
1075
- ? { width: legend_element.offsetWidth, height: legend_element.offsetHeight }
1076
- : { width: 120, height: 80 }
1077
-
1078
- const placement_config = {
1079
- plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
1080
- element_size: legend_size,
1081
- axis_clearance: legend?.axis_clearance,
1082
- exclude_rects: [],
1083
- points: plot_points_for_placement,
1084
- }
1085
-
1086
- return compute_element_placement(placement_config)
1087
- })
1088
-
1089
- // Calculate color bar placement (coordinates with legend to avoid overlap)
1090
- let color_bar_placement = $derived.by(() => {
1091
- if (!color_bar || !all_color_values.length || !width || !height) return null
1092
-
1093
- const plot_width = width - pad.l - pad.r
1094
- const plot_height = height - pad.t - pad.b
1095
-
1096
- // Use measured size if available, otherwise estimate based on orientation
1097
- const is_horizontal = color_bar.orientation === `horizontal`
1098
- const colorbar_size = colorbar_element
1099
- ? { width: colorbar_element.offsetWidth, height: colorbar_element.offsetHeight }
1100
- : is_horizontal
1101
- ? { width: 220, height: 40 }
1102
- : { width: 40, height: 100 }
1103
-
1104
- // Build exclusion rects (avoid legend if it's placed)
1105
- const exclude_rects: Rect[] = []
1106
- if (legend_element && legend_placement) {
1107
- exclude_rects.push({
1108
- x: legend_placement.x,
1109
- y: legend_placement.y,
1110
- width: legend_element.offsetWidth || 120,
1111
- height: legend_element.offsetHeight || 80,
1112
- })
1113
- }
1114
-
1115
- return compute_element_placement({
1116
- plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
1117
- element_size: colorbar_size,
1118
- // Colorbar needs slightly more clearance than legend to avoid axis labels
1119
- axis_clearance: 15,
1120
- exclude_rects,
1121
- points: plot_points_for_placement,
1122
- })
1123
- })
1124
-
1125
- // Active legend placement (null if user set explicit position)
1126
- let active_legend_placement = $derived.by(() => {
1127
- if (!legend_placement) return null
1128
-
1129
- // Skip auto-placement if user set explicit position in style
1130
- const legend_style = legend?.style ?? ``
1131
- if (
1132
- /(^|[;{]\s*)(top|bottom|left|right)\s*:|position\s*:\s*absolute/.test(
1133
- legend_style,
1134
- )
1135
- ) return null
1136
-
1137
- return legend_placement
1138
- })
1139
-
1140
- // Initialize tweened values for color bar position - create once, update target via effect
1141
- // untrack() explicitly captures initial tween config (intentional - config set once at mount)
1142
- const tweened_colorbar_coords = new Tween(
1143
- { x: 0, y: 0 },
1144
- untrack(() => ({ duration: 400, ...(color_bar?.tween ?? {}) })),
1145
- )
1146
- // Initialize tweened values for legend position - create once, update target via effect
1147
- const tweened_legend_coords = new Tween(
1148
- { x: 0, y: 0 },
1149
- untrack(() => ({ duration: 400, ...(legend?.tween ?? {}) })),
1150
- )
1151
-
1152
- // Update placement positions (with animation and stability checks)
1153
- $effect(() => {
1154
- if (!width || !height) return
1155
-
1156
- // Track dimensions for resize detection
1157
- const dims_changed = dim_tracker.has_changed(width, height)
1158
- if (dims_changed) dim_tracker.update(width, height)
1159
-
1160
- // Update colorbar position (stable after initial placement unless responsive)
1161
- if (color_bar_placement) {
1162
- const is_responsive = color_bar?.responsive ?? false
1163
- const should_update = dims_changed || (!colorbar_hover.is_locked.current &&
1164
- (is_responsive || !has_initial_colorbar_placement))
1165
-
1166
- if (should_update) {
1167
- tweened_colorbar_coords.set(
1168
- { x: color_bar_placement.x, y: color_bar_placement.y },
1169
- has_initial_colorbar_placement ? undefined : { duration: 0 },
1170
- )
1171
- if (colorbar_element && !has_initial_colorbar_placement) {
1172
- has_initial_colorbar_placement = true
1173
- }
1174
- }
1175
- }
1176
-
1177
- // Update legend position (stable after initial placement unless responsive)
1178
- if (legend_manual_position && !legend_is_dragging) {
1179
- // Immediate update (no animation) for manually dragged positions
1180
- tweened_legend_coords.set(legend_manual_position, { duration: 0 })
1181
- } else if (active_legend_placement && !legend_is_dragging) {
1182
- const is_responsive = legend?.responsive ?? false
1183
- const should_update = dims_changed || (!legend_hover.is_locked.current &&
1184
- (is_responsive || !has_initial_legend_placement))
1185
-
1186
- if (should_update) {
1187
- tweened_legend_coords.set(
1188
- { x: active_legend_placement.x, y: active_legend_placement.y },
1189
- has_initial_legend_placement ? undefined : { duration: 0 },
1190
- )
1191
- if (legend_element) has_initial_legend_placement = true
1192
- }
1193
- }
1194
- })
1195
-
1196
- // Generate axis ticks - consolidated into single derived for efficiency
1197
- let axis_ticks = $derived.by(() => {
1198
- if (!width || !height) return { x: [], x2: [], y: [], y2: [] }
1199
-
1200
- // X-axis ticks: choose appropriate scale for tick generation
1201
- // Time scales (format starts with %) use scaleTime for better tick placement
1202
- const x_scale_for_ticks = is_time_x
1203
- ? scaleTime().domain([new Date(x_min), new Date(x_max)])
1204
- : create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [0, 1])
1205
-
1206
- const x2_scale_for_ticks = is_time_x2
1207
- ? scaleTime().domain([new Date(x2_min), new Date(x2_max)])
1208
- : create_scale(final_x2_axis.scale_type ?? `linear`, [x2_min, x2_max], [0, 1])
1209
-
1210
- return {
1211
- x: generate_ticks(
1212
- [x_min, x_max],
1213
- final_x_axis.scale_type ?? `linear`,
1214
- final_x_axis.ticks,
1215
- x_scale_for_ticks,
1216
- { format: final_x_axis.format },
1217
- ),
1218
- x2: x2_points.length > 0
1219
- ? generate_ticks(
1220
- [x2_min, x2_max],
1221
- final_x2_axis.scale_type ?? `linear`,
1222
- final_x2_axis.ticks,
1223
- x2_scale_for_ticks,
1224
- { format: final_x2_axis.format },
1225
- )
1226
- : [],
1227
- y: generate_ticks(
1228
- [y_min, y_max],
1229
- final_y_axis.scale_type ?? `linear`,
1230
- final_y_axis.ticks,
1231
- y_scale_fn,
1232
- { default_count: 5 },
1233
- ),
1234
- y2: y2_points.length > 0
1235
- ? generate_ticks(
1236
- [y2_min, y2_max],
1237
- final_y2_axis.scale_type ?? `linear`,
1238
- final_y2_axis.ticks,
1239
- y2_scale_fn,
1240
- { default_count: 5 },
1241
- )
1242
- : [],
1243
- }
1244
- })
1245
-
1246
- let x_tick_values = $derived(axis_ticks.x)
1247
- let x2_tick_values = $derived(axis_ticks.x2)
1248
- let y_tick_values = $derived(axis_ticks.y)
1249
- let y2_tick_values = $derived(axis_ticks.y2)
1250
-
1251
- // Cache measured tick-label widths so expensive text measurement only runs
1252
- // when tick values/format change, not on every template rerender.
1253
- let tick_label_widths = $derived({
1254
- x2_max: measure_max_tick_width(x2_tick_values, final_x2_axis.format ?? ``),
1255
- y_max: measure_max_tick_width(y_tick_values, final_y_axis.format ?? ``),
1256
- y2_max: measure_max_tick_width(y2_tick_values, final_y2_axis.format ?? ``),
1257
- })
1258
-
1259
- // Define global handlers reference for adding/removing listeners
1260
- const on_window_mouse_move = (evt: MouseEvent) => {
1261
- if (!drag_start_coords || !svg_bounding_box) return // Exit if not dragging or no bounds
1262
-
1263
- // Calculate mouse position relative to the stored SVG bounding box
1264
- const current_x = evt.clientX - svg_bounding_box.left
1265
- const current_y = evt.clientY - svg_bounding_box.top
1266
- drag_current_coords = { x: current_x, y: current_y }
1267
-
1268
- // Optional: update tooltip only if inside SVG bounds
1269
- const is_inside_svg = current_x >= 0 &&
1270
- current_x <= svg_bounding_box.width &&
1271
- current_y >= 0 &&
1272
- current_y <= svg_bounding_box.height
1273
-
1274
- if (is_inside_svg) {
1275
- // Use the already calculated relative coordinates
1276
- update_tooltip_point(current_x, current_y)
1277
- } else tooltip_point = null // Clear tooltip if outside
1278
- }
1279
-
1280
- const on_window_mouse_up = (_evt: MouseEvent) => {
1281
- if (drag_start_coords && drag_current_coords) {
1282
- // Use current scales to invert screen coords to data coords
1283
- const start_data_x_val = x_scale_fn.invert(drag_start_coords.x)
1284
- const end_data_x_val = x_scale_fn.invert(drag_current_coords.x)
1285
- const start_data_y_val = y_scale_fn.invert(drag_start_coords.y)
1286
- const end_data_y_val = y_scale_fn.invert(drag_current_coords.y)
1287
-
1288
- // Ensure range is not zero and order is correct
1289
- let x1: number, x2: number
1290
- if (start_data_x_val instanceof Date && end_data_x_val instanceof Date) {
1291
- x1 = start_data_x_val.getTime()
1292
- x2 = end_data_x_val.getTime()
1293
- } else if (
1294
- typeof start_data_x_val === `number` &&
1295
- typeof end_data_x_val === `number`
1296
- ) {
1297
- x1 = start_data_x_val
1298
- x2 = end_data_x_val
1299
- } else {
1300
- console.error(`Mismatched types for x-axis zoom calculation`)
1301
- // Reset states without zooming if types are wrong
1302
- drag_start_coords = null
1303
- drag_current_coords = null
1304
- window.removeEventListener(`mousemove`, on_window_mouse_move)
1305
- window.removeEventListener(`mouseup`, on_window_mouse_up)
1306
- return
1307
- }
1308
-
1309
- const next_x_range: [number, number] = [Math.min(x1, x2), Math.max(x1, x2)]
1310
- // Y axis is always number
1311
- const next_y_range: [number, number] = [
1312
- Math.min(start_data_y_val, end_data_y_val),
1313
- Math.max(start_data_y_val, end_data_y_val),
1314
- ]
1315
-
1316
- // Check for minuscule zoom box (e.g. accidental click)
1317
- const min_zoom_size = 5 // Minimum pixels to trigger zoom
1318
- const dx = Math.abs(drag_start_coords.x - drag_current_coords.x)
1319
- const dy = Math.abs(drag_start_coords.y - drag_current_coords.y)
1320
-
1321
- if (
1322
- dx > min_zoom_size &&
1323
- dy > min_zoom_size &&
1324
- next_x_range[0] !== next_x_range[1] &&
1325
- next_y_range[0] !== next_y_range[1]
1326
- ) {
1327
- // Update axis ranges to trigger reactivity (like BarPlot/Histogram do)
1328
- // Y2 sync is handled by the effect that reacts to y_axis changes
1329
- x_axis = { ...x_axis, range: next_x_range }
1330
- y_axis = { ...y_axis, range: next_y_range }
1331
-
1332
- // X2 axis: invert screen coords using x2 scale
1333
- if (x2_points.length > 0) {
1334
- const start_x2_val = x2_scale_fn.invert(drag_start_coords.x)
1335
- const end_x2_val = x2_scale_fn.invert(drag_current_coords.x)
1336
- const x2_a = start_x2_val instanceof Date
1337
- ? start_x2_val.getTime()
1338
- : start_x2_val as number
1339
- const x2_b = end_x2_val instanceof Date
1340
- ? end_x2_val.getTime()
1341
- : end_x2_val as number
1342
- x2_axis = {
1343
- ...x2_axis,
1344
- range: [Math.min(x2_a, x2_b), Math.max(x2_a, x2_b)],
1345
- }
1346
- }
1347
- }
1348
- }
1349
-
1350
- // Reset states and remove listeners
1351
- drag_start_coords = null
1352
- drag_current_coords = null
1353
- svg_bounding_box = null
1354
- window.removeEventListener(`mousemove`, on_window_mouse_move)
1355
- window.removeEventListener(`mouseup`, on_window_mouse_up)
1356
- document.body.style.cursor = `default`
1357
- }
1358
-
1359
- // Pan drag handlers
1360
- const on_pan_move = (evt: MouseEvent) => {
1361
- if (!pan_drag_state) return
1362
- const dx = evt.clientX - pan_drag_state.start.x
1363
- const dy = evt.clientY - pan_drag_state.start.y
1364
-
1365
- // Convert pixel delta to data delta (note: drag direction is inverted for natural pan feel)
1366
- // Clamp to at least 1 to avoid Infinity deltas when padding equals container size
1367
- const plot_width = Math.max(1, width - pad.l - pad.r)
1368
- const plot_height = Math.max(1, height - pad.t - pad.b)
1369
- const sensitivity = pan?.drag_sensitivity ?? 1
1370
-
1371
- const x_delta = pixels_to_data_delta(
1372
- -dx * sensitivity,
1373
- pan_drag_state.initial_x_range,
1374
- plot_width,
1375
- )
1376
- const x2_delta = pixels_to_data_delta(
1377
- -dx * sensitivity,
1378
- pan_drag_state.initial_x2_range,
1379
- plot_width,
1380
- )
1381
- const y_delta = pixels_to_data_delta(
1382
- dy * sensitivity,
1383
- pan_drag_state.initial_y_range,
1384
- plot_height,
1385
- )
1386
- const y2_delta = pixels_to_data_delta(
1387
- dy * sensitivity,
1388
- pan_drag_state.initial_y2_range,
1389
- plot_height,
1390
- )
1391
-
1392
- zoom_x_range = pan_range(pan_drag_state.initial_x_range, x_delta)
1393
- zoom_x2_range = pan_range(pan_drag_state.initial_x2_range, x2_delta)
1394
- zoom_y_range = pan_range(pan_drag_state.initial_y_range, y_delta)
1395
- zoom_y2_range = get_synced_y2(
1396
- zoom_y_range,
1397
- pan_range(pan_drag_state.initial_y2_range, y2_delta),
1398
- )
1399
- }
1400
-
1401
- const on_pan_end = () => {
1402
- pan_drag_state = null
1403
- document.body.style.cursor = ``
1404
- window.removeEventListener(`mousemove`, on_pan_move)
1405
- window.removeEventListener(`mouseup`, on_pan_end)
1406
- }
1407
-
1408
- function handle_mouse_down(evt: MouseEvent) {
1409
- if (!svg_element) return
1410
-
1411
- // Check if pan is enabled and shift is held for pan mode
1412
- const pan_enabled = pan?.enabled !== false
1413
- if (pan_enabled && evt.shiftKey) {
1414
- evt.preventDefault()
1415
- pan_drag_state = {
1416
- start: { x: evt.clientX, y: evt.clientY },
1417
- initial_x_range: [...zoom_x_range] as [number, number],
1418
- initial_x2_range: [...zoom_x2_range] as [number, number],
1419
- initial_y_range: [...zoom_y_range] as [number, number],
1420
- initial_y2_range: [...zoom_y2_range] as [number, number],
1421
- }
1422
- document.body.style.cursor = `grabbing`
1423
- window.addEventListener(`mousemove`, on_pan_move)
1424
- window.addEventListener(`mouseup`, on_pan_end)
1425
- return
1426
- }
1427
-
1428
- // Store bounding box first, then calculate coords using it
1429
- svg_bounding_box = svg_element.getBoundingClientRect()
1430
-
1431
- // Calculate initial coords using the same bounding box that will be used during drag
1432
- const initial_x = evt.clientX - svg_bounding_box.left
1433
- const initial_y = evt.clientY - svg_bounding_box.top
1434
- const coords = { x: initial_x, y: initial_y }
1435
-
1436
- drag_start_coords = coords
1437
- drag_current_coords = coords
1438
-
1439
- window.addEventListener(`mousemove`, on_window_mouse_move)
1440
- window.addEventListener(`mouseup`, on_window_mouse_up)
1441
- document.body.style.cursor = `crosshair`
1442
- evt.preventDefault()
1443
- }
1444
-
1445
- // Wheel handler for pan (requires focus and shift)
1446
- function handle_wheel(evt: WheelEvent) {
1447
- const pan_enabled = pan?.enabled !== false
1448
- // Only capture wheel when focused AND Shift is held
1449
- // Use shift_held state (tracked via keydown/keyup) for compatibility with synthetic events
1450
- if (!pan_enabled || !is_focused || !shift_held) return
1451
-
1452
- evt.preventDefault()
1453
-
1454
- // Clamp to at least 1 to avoid Infinity deltas when padding equals container size
1455
- const plot_width = Math.max(1, width - pad.l - pad.r)
1456
- const plot_height = Math.max(1, height - pad.t - pad.b)
1457
- const sensitivity = pan?.wheel_sensitivity ?? 1
1458
-
1459
- // Determine pan direction based on wheel delta
1460
- // deltaX for horizontal scroll (trackpad), deltaY for vertical
1461
- const x_delta = pixels_to_data_delta(
1462
- evt.deltaX * sensitivity,
1463
- zoom_x_range,
1464
- plot_width,
1465
- )
1466
- const x2_delta = pixels_to_data_delta(
1467
- evt.deltaX * sensitivity,
1468
- zoom_x2_range,
1469
- plot_width,
1470
- )
1471
- const y_delta = pixels_to_data_delta(
1472
- evt.deltaY * sensitivity,
1473
- zoom_y_range,
1474
- plot_height,
1475
- )
1476
- const y2_delta = pixels_to_data_delta(
1477
- evt.deltaY * sensitivity,
1478
- zoom_y2_range,
1479
- plot_height,
1480
- )
1481
-
1482
- if (Math.abs(evt.deltaX) > Math.abs(evt.deltaY)) {
1483
- zoom_x_range = pan_range(zoom_x_range, x_delta)
1484
- zoom_x2_range = pan_range(zoom_x2_range, x2_delta)
1485
- } else {
1486
- zoom_y_range = pan_range(zoom_y_range, y_delta)
1487
- zoom_y2_range = get_synced_y2(zoom_y_range, pan_range(zoom_y2_range, y2_delta))
1488
- }
1489
- }
1490
-
1491
- // Touch handlers for pinch-zoom and two-finger pan
1492
- function handle_touch_start(evt: TouchEvent) {
1493
- const touch_enabled = pan?.enabled !== false && pan?.touch_enabled !== false
1494
- if (!touch_enabled || evt.touches.length !== 2) return
1495
-
1496
- evt.preventDefault()
1497
- const touches = Array.from(evt.touches)
1498
- touch_state = {
1499
- start_touches: touches.map((touch) => ({ x: touch.clientX, y: touch.clientY })),
1500
- initial_x_range: [...zoom_x_range] as [number, number],
1501
- initial_x2_range: [...zoom_x2_range] as [number, number],
1502
- initial_y_range: [...zoom_y_range] as [number, number],
1503
- initial_y2_range: [...zoom_y2_range] as [number, number],
1504
- }
1505
- }
1506
-
1507
- function handle_touch_move(evt: TouchEvent) {
1508
- if (!touch_state || evt.touches.length !== 2) return
1509
- evt.preventDefault()
1510
-
1511
- const [t1, t2] = Array.from(evt.touches)
1512
- const [s1, s2] = touch_state.start_touches
1513
-
1514
- // Calculate center movement for pan
1515
- const start_center = { x: (s1.x + s2.x) / 2, y: (s1.y + s2.y) / 2 }
1516
- const curr_center = {
1517
- x: (t1.clientX + t2.clientX) / 2,
1518
- y: (t1.clientY + t2.clientY) / 2,
1519
- }
1520
- const dx = curr_center.x - start_center.x
1521
- const dy = curr_center.y - start_center.y
1522
-
1523
- // Calculate pinch scale (curr/start so spread = zoom out, pinch = zoom in)
1524
- const start_dist = Math.hypot(s2.x - s1.x, s2.y - s1.y)
1525
- // Guard against zero-distance pinch to avoid Infinity scale
1526
- if (start_dist < Number.EPSILON) return
1527
- const curr_dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY)
1528
- const scale = curr_dist / start_dist
1529
-
1530
- // Clamp to at least 1 to avoid Infinity deltas when padding equals container size
1531
- const plot_width = Math.max(1, width - pad.l - pad.r)
1532
- const plot_height = Math.max(1, height - pad.t - pad.b)
1533
-
1534
- // If scale changed significantly, treat as pinch-zoom
1535
- // Also guard against scale being too small to avoid division by zero
1536
- if (Math.abs(scale - 1) > PINCH_ZOOM_THRESHOLD && scale > Number.EPSILON) {
1537
- // Pinch zoom centered on gesture center
1538
- // Divide by scale so spread (scale > 1) = smaller span (zoom in)
1539
- const x_span = touch_state.initial_x_range[1] - touch_state.initial_x_range[0]
1540
- const x2_span = touch_state.initial_x2_range[1] -
1541
- touch_state.initial_x2_range[0]
1542
- const y_span = touch_state.initial_y_range[1] - touch_state.initial_y_range[0]
1543
- const y2_span = touch_state.initial_y2_range[1] -
1544
- touch_state.initial_y2_range[0]
1545
- const x_center =
1546
- (touch_state.initial_x_range[0] + touch_state.initial_x_range[1]) / 2
1547
- const x2_center =
1548
- (touch_state.initial_x2_range[0] + touch_state.initial_x2_range[1]) / 2
1549
- const y_center =
1550
- (touch_state.initial_y_range[0] + touch_state.initial_y_range[1]) / 2
1551
- const y2_center =
1552
- (touch_state.initial_y2_range[0] + touch_state.initial_y2_range[1]) / 2
1553
-
1554
- zoom_x_range = [x_center - x_span / scale / 2, x_center + x_span / scale / 2]
1555
- zoom_x2_range = [
1556
- x2_center - x2_span / scale / 2,
1557
- x2_center + x2_span / scale / 2,
1558
- ]
1559
- zoom_y_range = [y_center - y_span / scale / 2, y_center + y_span / scale / 2]
1560
- zoom_y2_range = get_synced_y2(zoom_y_range, [
1561
- y2_center - y2_span / scale / 2,
1562
- y2_center + y2_span / scale / 2,
1563
- ])
1564
- } else {
1565
- // Pan
1566
- const x_delta = pixels_to_data_delta(
1567
- -dx,
1568
- touch_state.initial_x_range,
1569
- plot_width,
1570
- )
1571
- const x2_delta = pixels_to_data_delta(
1572
- -dx,
1573
- touch_state.initial_x2_range,
1574
- plot_width,
1575
- )
1576
- const y_delta = pixels_to_data_delta(
1577
- dy,
1578
- touch_state.initial_y_range,
1579
- plot_height,
1580
- )
1581
- const y2_delta = pixels_to_data_delta(
1582
- dy,
1583
- touch_state.initial_y2_range,
1584
- plot_height,
1585
- )
1586
- zoom_x_range = pan_range(touch_state.initial_x_range, x_delta)
1587
- zoom_x2_range = pan_range(touch_state.initial_x2_range, x2_delta)
1588
- zoom_y_range = pan_range(touch_state.initial_y_range, y_delta)
1589
- zoom_y2_range = get_synced_y2(
1590
- zoom_y_range,
1591
- pan_range(touch_state.initial_y2_range, y2_delta),
1592
- )
1593
- }
1594
- }
1595
-
1596
- function handle_touch_end() {
1597
- touch_state = null
1598
- }
1599
-
1600
- // tooltip logic: find closest point and update tooltip state
1601
- function update_tooltip_point(x_rel: number, y_rel: number, evt?: MouseEvent) {
1602
- if (!width || !height) return
1603
-
1604
- let closest_point: InternalPoint<Metadata> | null = null
1605
- let closest_series: DataSeries<Metadata> | null = null
1606
- let min_screen_dist_sq = Infinity
1607
- const { threshold_px = 20 } = hover_config // Use configured threshold
1608
- const hover_threshold_px_sq = threshold_px * threshold_px
1609
-
1610
- // Iterate through points to find the closest one in screen coordinates
1611
- for (const series_data of filtered_series) {
1612
- if (!series_data?.filtered_data) continue
1613
-
1614
- const tooltip_use_x2 = series_data.x_axis === `x2`
1615
- const tooltip_x_scale = tooltip_use_x2 ? x2_scale_fn : x_scale_fn
1616
- const tooltip_is_time_x = tooltip_use_x2 ? is_time_x2 : is_time_x
1617
- for (const point of series_data.filtered_data) {
1618
- // Calculate screen coordinates of the point
1619
- const point_cx = tooltip_is_time_x
1620
- ? tooltip_x_scale(new Date(point.x))
1621
- : tooltip_x_scale(point.x)
1622
- const point_cy = (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(
1623
- point.y,
1624
- )
1625
-
1626
- // Calculate squared screen distance between mouse and point
1627
- const screen_dx = x_rel - point_cx
1628
- const screen_dy = y_rel - point_cy
1629
- const screen_distance_sq = screen_dx * screen_dx + screen_dy * screen_dy
1630
-
1631
- // Update if this point is closer
1632
- if (screen_distance_sq < min_screen_dist_sq) {
1633
- min_screen_dist_sq = screen_distance_sq
1634
- closest_point = point
1635
- closest_series = series_data
1636
- }
1637
- }
1638
- }
1639
-
1640
- // Check if the closest point is within the hover threshold
1641
- if (
1642
- closest_point &&
1643
- closest_series &&
1644
- min_screen_dist_sq <= hover_threshold_px_sq
1645
- ) {
1646
- // Construct handler props synchronously to avoid stale derived reads
1647
- const props = construct_handler_props(closest_point)
1648
- tooltip_point = closest_point
1649
- // Construct object matching change signature
1650
- const { x, y, metadata } = closest_point
1651
- change({ x, y, metadata, series: closest_series })
1652
- // Call hover handler with synchronously constructed props
1653
- if (evt && props) {
1654
- on_point_hover?.({ ...props, event: evt, point: closest_point })
1655
- }
1656
- } else {
1657
- tooltip_point = null
1658
- change(null)
1659
- on_point_hover?.(null)
1660
- }
1661
- }
1662
-
1663
- function on_mouse_move(evt: MouseEvent) {
1664
- hovered = true
1665
-
1666
- const coords = get_relative_coords(evt)
1667
- if (!coords) return
1668
-
1669
- update_tooltip_point(coords.x, coords.y, evt)
1670
- }
1671
-
1672
- // Merge user config with defaults before the effect that uses it
1673
- let actual_label_config = $derived({
1674
- sa_iterations: 2000,
1675
- max_labels: 300,
1676
- leader_line_threshold: 15,
1677
- ...label_placement_config,
1678
- })
1679
-
1680
- $effect(() => {
1681
- if (!width || !height) {
1682
- label_positions = {}
1683
- return
1684
- }
1685
-
1686
- label_positions = compute_label_positions(
1687
- filtered_series,
1688
- actual_label_config,
1689
- { x_scale_fn, y_scale_fn, y2_scale_fn, x_axis: final_x_axis },
1690
- { width, height, pad },
1691
- )
1692
- })
1693
-
1694
- // Legend drag handlers
1695
- function handle_legend_drag_start(event: MouseEvent) {
1696
- if (!svg_element) return
1697
-
1698
- legend_is_dragging = true
1699
-
1700
- // Get the actual rendered position of the legend element (accounts for transforms)
1701
- const legend_el = event.currentTarget
1702
- if (!(legend_el instanceof HTMLElement)) return
1703
- const legend_rect = legend_el.getBoundingClientRect()
1704
-
1705
- // Calculate offset from mouse to legend's actual rendered position relative to SVG
1706
- const [x, y] = [event.clientX - legend_rect.left, event.clientY - legend_rect.top]
1707
- legend_drag_offset = { x, y }
1708
- }
1709
-
1710
- function handle_legend_drag(event: MouseEvent) {
1711
- if (!legend_is_dragging || !svg_element || !legend_element) return
1712
-
1713
- const svg_rect = svg_element.getBoundingClientRect()
1714
-
1715
- // Calculate new position: mouse position relative to SVG, minus the offset within the legend
1716
- const new_x = event.clientX - svg_rect.left - legend_drag_offset.x
1717
- const new_y = event.clientY - svg_rect.top - legend_drag_offset.y
1718
-
1719
- // Get actual legend dimensions for accurate bounds checking using the bound element reference
1720
- const { width: legend_width, height: legend_height } = legend_element
1721
- .getBoundingClientRect()
1722
-
1723
- // Constrain to plot bounds using measured legend size
1724
- const constrained_x = Math.max(0, Math.min(width - legend_width, new_x))
1725
- const constrained_y = Math.max(0, Math.min(height - legend_height, new_y))
1726
-
1727
- legend_manual_position = { x: constrained_x, y: constrained_y }
1728
- }
1729
-
1730
- function get_screen_coords(point: Point, series?: DataSeries): [number, number] {
1731
- // convert data coordinates to potentially non-finite screen coordinates
1732
- const use_x2 = series?.x_axis === `x2`
1733
- const active_x_scale = use_x2 ? x2_scale_fn : x_scale_fn
1734
- const active_is_time_x = use_x2 ? is_time_x2 : is_time_x
1735
- const screen_x = active_is_time_x
1736
- ? active_x_scale(new Date(point.x))
1737
- : active_x_scale(point.x)
1738
-
1739
- const y_val = point.y
1740
- // Determine which y-scale to use based on series y_axis property
1741
- const use_y2 = series?.y_axis === `y2`
1742
- const y_scale = use_y2 ? y2_scale_fn : y_scale_fn
1743
- const y_scale_type = use_y2
1744
- ? get_scale_type_name(final_y2_axis.scale_type)
1745
- : get_scale_type_name(final_y_axis.scale_type)
1746
- // Only log scale needs domain clamping; linear and arcsinh can handle any value
1747
- const min_domain_y = y_scale_type === `log` ? y_scale.domain()[0] : -Infinity
1748
- const safe_y_val = y_scale_type === `log` ? Math.max(y_val, min_domain_y) : y_val
1749
- const screen_y = y_scale(safe_y_val) // This might be non-finite
1750
-
1751
- return [screen_x, screen_y]
1752
- }
1753
-
1754
- // Helper function to construct ScatterHandlerProps synchronously from InternalPoint
1755
- function construct_handler_props(
1756
- point: InternalPoint<Metadata>,
1757
- ): ScatterHandlerProps<Metadata> | null {
1758
- const hovered_series = series_with_ids[point.series_idx]
1759
- if (!hovered_series) return null
1760
- const { x, y, color_value, metadata, series_idx } = point
1761
- const handler_use_x2 = hovered_series.x_axis === `x2`
1762
- const handler_x_scale = handler_use_x2 ? x2_scale_fn : x_scale_fn
1763
- const handler_is_time_x = handler_use_x2 ? is_time_x2 : is_time_x
1764
- const cx = handler_is_time_x ? handler_x_scale(new Date(x)) : handler_x_scale(x)
1765
- const cy = (hovered_series.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(y)
1766
- const active_x_config = handler_use_x2 ? final_x2_axis : final_x_axis
1767
- const active_y_config = hovered_series.y_axis === `y2`
1768
- ? final_y2_axis
1769
- : final_y_axis
1770
- const coords = {
1771
- x,
1772
- y,
1773
- cx,
1774
- cy,
1775
- x_axis: active_x_config,
1776
- x2_axis: final_x2_axis,
1777
- y_axis: active_y_config,
1778
- y2_axis: final_y2_axis,
1779
- }
1780
- return {
1781
- ...coords,
1782
- fullscreen,
1783
- metadata,
1784
- label: hovered_series.label ?? null,
1785
- series_idx,
1786
- x_formatted: format_value(x, active_x_config.format || `.3~s`),
1787
- y_formatted: format_value(y, active_y_config.format || `.3~s`),
1788
- color_value: color_value ?? null,
1789
- colorbar: {
1790
- value: color_value ?? null,
1791
- title: color_bar?.title ?? null,
1792
- scale: color_scale,
1793
- tick_format: color_bar?.tick_format ?? null,
1794
- },
1795
- }
1796
- }
1797
-
1798
- // Derive handler props from hovered point for both tooltip and event handlers
1799
- let handler_props = $derived.by((): ScatterHandlerProps<Metadata> | null => {
1800
- if (!tooltip_point) return null
1801
- return construct_handler_props(tooltip_point)
1802
- })
1803
-
1804
- let using_controls = $derived(controls.show)
1805
- let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1)
1806
-
1807
- // Precompute non-click event names from point_events so we don't rebuild
1808
- // the entries array on every point render.
1809
- let point_event_names = $derived(
1810
- point_events
1811
- ? Object.keys(point_events).filter((name) => name !== `onclick`)
1812
- : [],
1813
- )
1814
-
1815
- // Set theme-aware background when entering fullscreen
1816
- $effect(() => {
1817
- set_fullscreen_bg(wrapper, fullscreen, `--scatter-fullscreen-bg`)
1818
- })
1819
-
1820
- // State accessors for shared axis change handler
1821
- const axis_state: AxisChangeState<DataSeries<Metadata>> = {
1822
- get_axis: (axis) => {
1823
- if (axis === `x`) return x_axis
1824
- if (axis === `x2`) return x2_axis
1825
- if (axis === `y`) return y_axis
1826
- return y2_axis
1827
- },
1828
- set_axis: (axis, config) => {
1829
- // Spread into existing state to preserve merged type structure
1830
- if (axis === `x`) x_axis = { ...x_axis, ...config }
1831
- else if (axis === `x2`) x2_axis = { ...x2_axis, ...config }
1832
- else if (axis === `y`) y_axis = { ...y_axis, ...config }
1833
- else y2_axis = { ...y2_axis, ...config }
1834
- },
1835
- get_series: () => series,
1836
- set_series: (new_series) => (series = new_series),
1837
- get_loading: () => axis_loading,
1838
- set_loading: (axis) => (axis_loading = axis),
1839
- }
1840
-
1841
- // Create shared handler bound to this component's state
1842
- // Using $derived so handler updates when callback props change
1843
- const handle_axis_change = $derived(create_axis_change_handler(
1844
- axis_state,
1845
- data_loader,
1846
- on_axis_change,
1847
- on_error,
1848
- ))
1849
-
1850
- let auto_load_attempted = false // prevent infinite retries on failure
1851
-
1852
- // Auto-load data if series is empty but options exist (runs once)
1853
- $effect(() => {
1854
- if (series.length === 0 && data_loader && !auto_load_attempted) {
1855
- // Check x-axis first, then y-axis
1856
- if (x_axis.options?.length) {
1857
- auto_load_attempted = true
1858
- const first_key = x_axis.selected_key ?? x_axis.options[0].key
1859
- handle_axis_change(`x`, first_key).catch(() => {})
1860
- } else if (y_axis.options?.length) {
1861
- auto_load_attempted = true
1862
- const first_key = y_axis.selected_key ?? y_axis.options[0].key
1863
- handle_axis_change(`y`, first_key).catch(() => {})
1864
- }
1865
- }
1866
- })
1867
- </script>
1868
-
1869
- {#snippet fill_regions_layer(fills: typeof computed_fills)}
1870
- {#each fills as fill (fill.id ?? fill.idx)}
1871
- {#each fill.path_segments as
1872
- path_d,
1873
- segment_idx
1874
- (`${fill.id ?? fill.idx}-${segment_idx}`)
1875
- }
1876
- <FillArea
1877
- region={fill}
1878
- region_idx={fill.idx}
1879
- path={path_d}
1880
- {clip_path_id}
1881
- {x_scale_fn}
1882
- {y_scale_fn}
1883
- hovered_region={hovered_fill_idx}
1884
- on_click={(event) => {
1885
- fill.on_click?.(event)
1886
- on_fill_click?.(event)
1887
- }}
1888
- on_hover={(event) => {
1889
- hovered_fill_idx = event?.region_idx ?? null
1890
- fill.on_hover?.(event)
1891
- on_fill_hover?.(event)
1892
- }}
1893
- />
1894
- {/each}
1895
- {/each}
1896
- {/snippet}
1897
-
1898
- {#snippet ref_lines_layer(lines: IndexedRefLine[])}
1899
- {#each lines as line (line.id ?? line.idx)}
1900
- <ReferenceLine
1901
- ref_line={line}
1902
- line_idx={line.idx}
1903
- x_min={line.x_axis === `x2` ? x2_min : x_min}
1904
- x_max={line.x_axis === `x2` ? x2_max : x_max}
1905
- y_min={line.y_axis === `y2` ? y2_min : y_min}
1906
- y_max={line.y_axis === `y2` ? y2_max : y_max}
1907
- x_scale={x_scale_fn}
1908
- x2_scale={x2_scale_fn}
1909
- y_scale={y_scale_fn}
1910
- y2_scale={y2_scale_fn}
1911
- {clip_path_id}
1912
- hovered_line_idx={hovered_ref_line_idx}
1913
- on_click={(event: RefLineEvent) => {
1914
- line.on_click?.(event)
1915
- on_ref_line_click?.(event)
1916
- }}
1917
- on_hover={(event: RefLineEvent | null) => {
1918
- hovered_ref_line_idx = event?.line_idx ?? null
1919
- line.on_hover?.(event)
1920
- on_ref_line_hover?.(event)
1921
- }}
1922
- />
1923
- {/each}
1924
- {/snippet}
1925
-
1926
- <svelte:window
1927
- onkeydown={(evt) => {
1928
- if (evt.key === `Escape` && fullscreen) {
1929
- evt.preventDefault()
1930
- fullscreen = false
1931
- }
1932
- if (evt.key === `Shift`) shift_held = true
1933
- }}
1934
- onkeyup={(evt) => {
1935
- if (evt.key === `Shift`) shift_held = false
1936
- }}
1937
- />
1938
-
1939
- <div
1940
- bind:this={wrapper}
1941
- bind:clientWidth={width}
1942
- bind:clientHeight={height}
1943
- {...rest}
1944
- class="scatter {rest.class ?? ``}"
1945
- class:fullscreen
1946
- >
1947
- {#if width && height}
1948
- <div class="header-controls">
1949
- {@render header_controls?.({ height, width, fullscreen })}
1950
- {#if fullscreen_toggle}
1951
- <FullscreenToggle bind:fullscreen />
1952
- {/if}
1953
- </div>
1954
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
1955
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
1956
- <svg
1957
- bind:this={svg_element}
1958
- role="application"
1959
- aria-label={rest[`aria-label`] ??
1960
- ([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) ||
1961
- `Scatter plot`)}
1962
- tabindex="0"
1963
- onfocusin={() => (is_focused = true)}
1964
- onfocusout={() => (is_focused = false)}
1965
- onmouseenter={() => (hovered = true)}
1966
- onmousedown={handle_mouse_down}
1967
- onmousemove={(evt: MouseEvent) => {
1968
- // Only find closest point if not actively dragging
1969
- if (!drag_start_coords && !pan_drag_state) on_mouse_move(evt)
1970
- }}
1971
- onmouseleave={() => {
1972
- hovered = false
1973
- tooltip_point = null
1974
- on_point_hover?.(null)
1975
- }}
1976
- ondblclick={() => {
1977
- // Reset to current auto ranges (not stale initial_*_range which may have expanded)
1978
- // This ensures lazy expansion restarts fresh from current data bounds
1979
- initial_x_range = [...auto_x_range] as [number, number]
1980
- initial_x2_range = [...auto_x2_range] as [number, number]
1981
- initial_y_range = [...auto_y_range] as [number, number]
1982
- initial_y2_range = [...auto_y2_range] as [number, number]
1983
- zoom_x_range = [...auto_x_range] as [number, number]
1984
- zoom_x2_range = [...auto_x2_range] as [number, number]
1985
- zoom_y_range = [...auto_y_range] as [number, number]
1986
- zoom_y2_range = get_synced_y2(auto_y_range, [...auto_y2_range] as Vec2)
1987
- // Also reset axis props so future data changes recalculate auto ranges
1988
- x_axis = { ...x_axis, range: [null, null] }
1989
- x2_axis = { ...x2_axis, range: [null, null] }
1990
- y_axis = { ...y_axis, range: [null, null] }
1991
- y2_axis = { ...y2_axis, range: [null, null] }
1992
- }}
1993
- onwheel={handle_wheel}
1994
- ontouchstart={handle_touch_start}
1995
- ontouchmove={handle_touch_move}
1996
- ontouchend={handle_touch_end}
1997
- style:cursor={pan_drag_state
1998
- ? `grabbing`
1999
- : shift_held && pan?.enabled !== false
2000
- ? `grab`
2001
- : `crosshair`}
2002
- >
2003
- {@render user_content?.({
2004
- height,
2005
- width,
2006
- x_scale_fn,
2007
- x2_scale_fn,
2008
- y_scale_fn,
2009
- y2_scale_fn,
2010
- pad,
2011
- x_range: [x_min, x_max],
2012
- x2_range: [x2_min, x2_max],
2013
- y_range: [y_min, y_max],
2014
- y2_range: [y2_min, y2_max],
2015
- fullscreen,
2016
- })}
2017
-
2018
- <!-- Fill regions: below grid -->
2019
- {@render fill_regions_layer(fills_by_z.below_grid)}
2020
- <!-- Reference lines: below grid -->
2021
- {@render ref_lines_layer(ref_lines_by_z.below_grid)}
2022
-
2023
- <g class="x-axis">
2024
- {#if width > 0 && height > 0}
2025
- {#each x_tick_values as tick (tick)}
2026
- {@const tick_pos_raw = is_time_x ? x_scale_fn(new Date(tick)) : x_scale_fn(tick)}
2027
- {#if isFinite(tick_pos_raw)}
2028
- // Check if tick position is finite
2029
- {@const tick_pos = tick_pos_raw}
2030
- {#if tick_pos >= pad.l && tick_pos <= width - pad.r}
2031
- {@const inside = final_x_axis.tick?.label?.inside ?? false}
2032
- <g class="tick" transform="translate({tick_pos}, {height - pad.b})">
2033
- {#if final_display.x_grid}
2034
- <line
2035
- y1={-(height - pad.b - pad.t)}
2036
- y2="0"
2037
- {...DEFAULT_GRID_STYLE}
2038
- {...(final_x_axis.grid_style ?? {})}
2039
- />
2040
- {/if}
2041
- <line y1="0" y2={inside ? -5 : 5} stroke="var(--border-color, gray)" />
2042
-
2043
- {#if tick >= Math.min(x_min, x_max) && tick <= Math.max(x_min, x_max)}
2044
- {@const base_y = inside ? -8 : 20}
2045
- {@const shift = final_x_axis.tick?.label?.shift ?? { x: 0, y: 0 }}
2046
- {@const x = shift.x ?? 0}
2047
- {@const y = base_y + (shift.y ?? 0)}
2048
- {@const custom_label = get_tick_label(tick, final_x_axis.ticks)}
2049
- {@const dominant_baseline = inside ? `auto` : `hanging`}
2050
- <text
2051
- {x}
2052
- {y}
2053
- dominant-baseline={dominant_baseline}
2054
- fill={final_x_axis.color}
2055
- >
2056
- {custom_label ?? format_value(tick, final_x_axis.format ?? ``)}
2057
- </text>
2058
- {/if}
2059
- </g>
2060
- {/if}
2061
- {/if}
2062
- {/each}
2063
- {/if}
2064
-
2065
- <!-- Current frame indicator -->
2066
- {#if current_x_value != null}
2067
- {@const current_pos_raw = is_time_x
2068
- ? x_scale_fn(new Date(current_x_value))
2069
- : x_scale_fn(current_x_value)}
2070
- {#if isFinite(current_pos_raw)}
2071
- {@const current_pos = current_pos_raw}
2072
- {#if current_pos >= pad.l && current_pos <= width - pad.r}
2073
- {@const active_tick_height = 7}
2074
- <rect
2075
- x={current_pos - 1.5}
2076
- y={height - pad.b - active_tick_height / 2}
2077
- width="3"
2078
- height={active_tick_height}
2079
- fill="var(--scatter-current-frame-color, #ff6b35)"
2080
- stroke="white"
2081
- stroke-width="1"
2082
- class="current-frame-indicator"
2083
- />
2084
- {/if}
2085
- {/if}
2086
- {/if}
2087
-
2088
- {#if final_x_axis.label || final_x_axis.options?.length}
2089
- {@const { label_shift, label = ``, options, selected_key, color } = final_x_axis}
2090
- <AxisLabel
2091
- x={width / 2 + (label_shift?.x ?? 0)}
2092
- y={height - pad.b - (label_shift?.y ?? -40)}
2093
- {label}
2094
- {options}
2095
- {selected_key}
2096
- loading={axis_loading === `x`}
2097
- axis_type="x"
2098
- {color}
2099
- on_select={(key) => handle_axis_change(`x`, key)}
2100
- />
2101
- {/if}
2102
- </g>
2103
-
2104
- <g class="y-axis">
2105
- {#if width > 0 && height > 0}
2106
- {#each y_tick_values as tick, idx (tick)}
2107
- {@const tick_pos_raw = y_scale_fn(tick)}
2108
- {#if isFinite(tick_pos_raw)}
2109
- // Check if tick position is finite
2110
- {@const tick_pos = tick_pos_raw}
2111
- {#if tick_pos >= pad.t && tick_pos <= height - pad.b}
2112
- {@const inside = final_y_axis.tick?.label?.inside ?? false}
2113
- <g class="tick" transform="translate({pad.l}, {tick_pos})">
2114
- {#if final_display.y_grid}
2115
- <line
2116
- x1="0"
2117
- x2={width - pad.l - pad.r}
2118
- {...DEFAULT_GRID_STYLE}
2119
- {...(final_y_axis.grid_style ?? {})}
2120
- />
2121
- {/if}
2122
- <line
2123
- x1={inside ? 0 : -5}
2124
- x2={inside ? 5 : 0}
2125
- stroke="var(--border-color, gray)"
2126
- />
2127
-
2128
- {#if tick >= Math.min(y_min, y_max) && tick <= Math.max(y_min, y_max)}
2129
- {@const base_x = inside ? 8 : -8}
2130
- {@const shift = final_y_axis.tick?.label?.shift ?? { x: 0, y: 0 }}
2131
- {@const x = base_x + (shift.x ?? 0)}
2132
- {@const y = shift.y ?? 0}
2133
- {@const custom_label = get_tick_label(tick, final_y_axis.ticks)}
2134
- {@const text_anchor = inside ? `start` : `end`}
2135
- <text {x} {y} text-anchor={text_anchor} fill={final_y_axis.color}>
2136
- {custom_label ?? format_value(tick, final_y_axis.format ?? ``)}
2137
- {#if final_y_axis.unit && idx === 0}
2138
- &zwnj;&ensp;{final_y_axis.unit}
2139
- {/if}
2140
- </text>
2141
- {/if}
2142
- </g>
2143
- {/if}
2144
- {/if}
2145
- {/each}
2146
- {/if}
2147
-
2148
- {#if height > 0 && (final_y_axis.label || final_y_axis.options?.length)}
2149
- {@const { label_shift, label = ``, options, selected_key, color, tick } =
2150
- final_y_axis}
2151
- {@const y_inside = tick?.label?.inside ?? false}
2152
- {@const y_label_x = Math.max(
2153
- 12,
2154
- pad.l - (y_inside ? 0 : tick_label_widths.y_max) - LABEL_GAP_DEFAULT,
2155
- ) +
2156
- (label_shift?.x ?? 0)}
2157
- <AxisLabel
2158
- x={y_label_x}
2159
- y={pad.t + (height - pad.t - pad.b) / 2 + (label_shift?.y ?? 0)}
2160
- rotate
2161
- {label}
2162
- {options}
2163
- {selected_key}
2164
- loading={axis_loading === `y`}
2165
- axis_type="y"
2166
- {color}
2167
- on_select={(key) => handle_axis_change(`y`, key)}
2168
- />
2169
- {/if}
2170
- </g>
2171
-
2172
- <!-- Y2-axis (Right) -->
2173
- {#if y2_points.length > 0}
2174
- <g class="y2-axis">
2175
- {#if width > 0 && height > 0}
2176
- {#each y2_tick_values as tick, idx (tick)}
2177
- {@const tick_pos_raw = y2_scale_fn(tick)}
2178
- {#if isFinite(tick_pos_raw)}
2179
- // Check if tick position is finite
2180
- {@const tick_pos = tick_pos_raw}
2181
- {#if tick_pos >= pad.t && tick_pos <= height - pad.b}
2182
- {@const inside = final_y2_axis.tick?.label?.inside ?? false}
2183
- <g class="tick" transform="translate({width - pad.r}, {tick_pos})">
2184
- {#if final_display.y2_grid}
2185
- <line
2186
- x1={-(width - pad.l - pad.r)}
2187
- x2="0"
2188
- {...DEFAULT_GRID_STYLE}
2189
- {...(final_y2_axis.grid_style ?? {})}
2190
- />
2191
- {/if}
2192
- <line
2193
- x1={inside ? -5 : 0}
2194
- x2={inside ? 0 : 5}
2195
- stroke="var(--border-color, gray)"
2196
- />
2197
-
2198
- {#if tick >= Math.min(y2_min, y2_max) && tick <= Math.max(y2_min, y2_max)}
2199
- {@const base_x = inside ? -8 : 8}
2200
- {@const shift = final_y2_axis.tick?.label?.shift ?? { x: 0, y: 0 }}
2201
- {@const x = base_x + (shift.x ?? 0)}
2202
- {@const y = shift.y ?? 0}
2203
- {@const custom_label = get_tick_label(tick, final_y2_axis.ticks)}
2204
- {@const text_anchor = inside ? `end` : `start`}
2205
- <text {x} {y} text-anchor={text_anchor} fill={final_y2_axis.color}>
2206
- {custom_label ?? format_value(tick, final_y2_axis.format ?? ``)}
2207
- {#if final_y2_axis.unit && idx === 0}
2208
- &zwnj;&ensp;{final_y2_axis.unit}
2209
- {/if}
2210
- </text>
2211
- {/if}
2212
- </g>
2213
- {/if}
2214
- {/if}
2215
- {/each}
2216
- {/if}
2217
-
2218
- {#if height > 0 && (final_y2_axis.label || final_y2_axis.options?.length)}
2219
- {@const { label_shift, label = ``, options, selected_key, color, tick } =
2220
- final_y2_axis}
2221
- {@const inside = tick?.label?.inside ?? false}
2222
- {@const tick_shift = inside ? 0 : (tick?.label?.shift?.x ?? 0) + 8}
2223
- {@const tick_width_contribution = inside ? 0 : tick_label_widths.y2_max}
2224
- <AxisLabel
2225
- x={width - pad.r + tick_shift + tick_width_contribution +
2226
- LABEL_GAP_DEFAULT + (label_shift?.x ?? 0)}
2227
- y={pad.t + (height - pad.t - pad.b) / 2 + (label_shift?.y ?? 0)}
2228
- rotate
2229
- {label}
2230
- {options}
2231
- {selected_key}
2232
- loading={axis_loading === `y2`}
2233
- axis_type="y2"
2234
- {color}
2235
- on_select={(key) => handle_axis_change(`y2`, key)}
2236
- />
2237
- {/if}
2238
- </g>
2239
- {/if}
2240
-
2241
- <!-- X2-axis (Top) -->
2242
- {#if x2_points.length > 0}
2243
- <g class="x2-axis">
2244
- {#if width > 0 && height > 0}
2245
- {#each x2_tick_values as tick (tick)}
2246
- {@const tick_pos_raw = is_time_x2
2247
- ? x2_scale_fn(new Date(tick))
2248
- : x2_scale_fn(tick)}
2249
- {#if isFinite(tick_pos_raw)}
2250
- {@const tick_pos = tick_pos_raw}
2251
- {#if tick_pos >= pad.l && tick_pos <= width - pad.r}
2252
- {@const inside = final_x2_axis.tick?.label?.inside ?? false}
2253
- <g class="tick" transform="translate({tick_pos}, {pad.t})">
2254
- {#if final_display.x2_grid}
2255
- <line
2256
- y1="0"
2257
- y2={height - pad.b - pad.t}
2258
- {...DEFAULT_GRID_STYLE}
2259
- {...(final_x2_axis.grid_style ?? {})}
2260
- />
2261
- {/if}
2262
- <line
2263
- y1="0"
2264
- y2={inside ? 5 : -5}
2265
- stroke={final_x2_axis.color || `var(--border-color, gray)`}
2266
- />
2267
-
2268
- {#if tick >= Math.min(x2_min, x2_max) && tick <= Math.max(x2_min, x2_max)}
2269
- {@const base_y = inside ? 8 : -20}
2270
- {@const shift = final_x2_axis.tick?.label?.shift ?? { x: 0, y: 0 }}
2271
- {@const x = shift.x ?? 0}
2272
- {@const y = base_y + (shift.y ?? 0)}
2273
- {@const custom_label = get_tick_label(tick, final_x2_axis.ticks)}
2274
- {@const dominant_baseline = inside ? `hanging` : `auto`}
2275
- <text
2276
- {x}
2277
- {y}
2278
- dominant-baseline={dominant_baseline}
2279
- fill={final_x2_axis.color}
2280
- >
2281
- {custom_label ?? format_value(tick, final_x2_axis.format ?? ``)}
2282
- </text>
2283
- {/if}
2284
- </g>
2285
- {/if}
2286
- {/if}
2287
- {/each}
2288
- {/if}
2289
-
2290
- {#if final_x2_axis.label || final_x2_axis.options?.length}
2291
- {@const { label_shift, label = ``, options, selected_key, color } =
2292
- final_x2_axis}
2293
- <AxisLabel
2294
- x={width / 2 + (label_shift?.x ?? 0)}
2295
- y={Math.max(12, pad.t - (label_shift?.y ?? 40))}
2296
- {label}
2297
- {options}
2298
- {selected_key}
2299
- loading={axis_loading === `x2`}
2300
- axis_type="x2"
2301
- {color}
2302
- on_select={(key) => handle_axis_change(`x2`, key)}
2303
- />
2304
- {/if}
2305
- </g>
2306
- {/if}
2307
-
2308
- <!-- Tooltip rendered inside overlay (moved outside SVG for stacking above colorbar) -->
2309
-
2310
- <ZoomRect start={drag_start_coords} current={drag_current_coords} />
2311
-
2312
- <ZeroLines
2313
- display={final_display}
2314
- {x_scale_fn}
2315
- {x2_scale_fn}
2316
- {y_scale_fn}
2317
- {y2_scale_fn}
2318
- x_range={zoom_x_range}
2319
- x2_range={zoom_x2_range}
2320
- y_range={zoom_y_range}
2321
- y2_range={zoom_y2_range}
2322
- x_scale_type={final_x_axis.scale_type}
2323
- x2_scale_type={final_x2_axis.scale_type}
2324
- y_scale_type={final_y_axis.scale_type}
2325
- y2_scale_type={final_y2_axis.scale_type}
2326
- x_is_time={is_time_x}
2327
- x2_is_time={is_time_x2}
2328
- has_x2={x2_points.length > 0}
2329
- has_y2={y2_points.length > 0}
2330
- {width}
2331
- {height}
2332
- {pad}
2333
- />
2334
-
2335
- <defs>
2336
- <clipPath id={clip_path_id}>
2337
- <rect
2338
- x={clip_area.x}
2339
- y={clip_area.y}
2340
- width={clip_area.width}
2341
- height={clip_area.height}
2342
- />
2343
- </clipPath>
2344
- </defs>
2345
-
2346
- <!-- Fill regions: below lines (default z-index) -->
2347
- {@render fill_regions_layer(fills_by_z.below_lines)}
2348
- <!-- Reference lines: below lines (default z-index) -->
2349
- {@render ref_lines_layer(ref_lines_by_z.below_lines)}
2350
-
2351
- <!-- Lines -->
2352
- {#if styles.show_lines}
2353
- {#each filtered_series ?? [] as series_data (series_data._id)}
2354
- {@const series_markers = series_data.markers ?? DEFAULT_MARKERS}
2355
- {@const series_default_color = get_series_color(series_data.orig_series_idx ?? 0)}
2356
- <g data-series-id={series_data._id} clip-path="url(#{clip_path_id})">
2357
- {#if series_markers?.includes(`line`)}
2358
- {@const all_line_points = series_data.x.map((x, idx) => ({
2359
- x,
2360
- y: series_data.y[idx],
2361
- }))}
2362
- {@const finite_screen_points = all_line_points
2363
- .map((point) => get_screen_coords(point, series_data))
2364
- .filter(([sx, sy]) => isFinite(sx) && isFinite(sy))}
2365
- {@const apply_line_controls = using_controls &&
2366
- (!has_multiple_series ||
2367
- series_data._id === series_with_ids[selected_series_idx]?._id)}
2368
- {@const ls = series_data.line_style}
2369
- {@const tc = (key: string) => apply_line_controls && touched.has(key)}
2370
- {@const color_fallback = ls?.stroke ??
2371
- (Array.isArray(series_data.point_style)
2372
- ? series_data.point_style[0]?.fill
2373
- : series_data.point_style?.fill) ??
2374
- (series_data.color_values?.[0] != null
2375
- ? color_scale_fn(series_data.color_values[0])
2376
- : series_default_color)}
2377
- <Line
2378
- points={finite_screen_points}
2379
- origin={[
2380
- is_time_x ? x_scale_fn(new Date(x_min)) : x_scale_fn(x_min),
2381
- series_data.y_axis === `y2` ? y2_scale_fn(y2_min) : y_scale_fn(y_min),
2382
- ]}
2383
- line_color={(tc(`line.color`) ? styles.line?.color : null) ?? color_fallback}
2384
- line_width={(tc(`line.width`) ? styles.line?.width : null) ?? ls?.stroke_width ?? 2}
2385
- line_dash={(tc(`line.dash`) ? styles.line?.dash : null) ?? ls?.line_dash}
2386
- area_color="transparent"
2387
- {line_tween}
2388
- />
2389
- {/if}
2390
- </g>
2391
- {/each}
2392
- {/if}
2393
-
2394
- <!-- Fill regions: below points -->
2395
- {@render fill_regions_layer(fills_by_z.below_points)}
2396
- <!-- Reference lines: below points -->
2397
- {@render ref_lines_layer(ref_lines_by_z.below_points)}
2398
-
2399
- <!-- Points -->
2400
- {#if styles.show_points}
2401
- {#each filtered_series ?? [] as series_data (series_data._id)}
2402
- {@const series_markers = series_data.markers ?? DEFAULT_MARKERS}
2403
- {@const series_default_color = get_series_color(series_data.orig_series_idx ?? 0)}
2404
- {@const series_default_symbol = get_series_symbol(series_data.orig_series_idx ?? 0)}
2405
- <g data-series-id={series_data._id}>
2406
- {#if series_markers?.includes(`points`)}
2407
- {#each series_data.filtered_data as
2408
- point
2409
- (`${point.series_idx}-${point.point_idx}`)
2410
- }
2411
- {@const label_id = `${point.series_idx}-${point.point_idx}`}
2412
- {@const calculated_label_pos = label_positions[label_id]}
2413
- {@const label_style = point.point_label ?? {}}
2414
- {@const final_label = calculated_label_pos
2415
- ? {
2416
- ...label_style,
2417
- offset: {
2418
- x: calculated_label_pos.x -
2419
- (is_time_x ? x_scale_fn(new Date(point.x)) : x_scale_fn(point.x)),
2420
- y: calculated_label_pos.y - (series_data.y_axis === `y2`
2421
- ? y2_scale_fn(point.y)
2422
- : y_scale_fn(point.y)),
2423
- },
2424
- }
2425
- : label_style}
2426
- {@const [raw_screen_x, raw_screen_y] = get_screen_coords(point, series_data)}
2427
- {@const screen_x = isFinite(raw_screen_x) ? raw_screen_x : x_scale_fn.range()[0]}
2428
- {@const screen_y = isFinite(raw_screen_y)
2429
- ? raw_screen_y
2430
- : (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn).range()[0]}
2431
- {@const apply_controls = using_controls &&
2432
- (!has_multiple_series ||
2433
- series_data._id === series_with_ids[selected_series_idx]?._id)}
2434
- {@const pt = point.point_style}
2435
- {@const tc = (key: string) => apply_controls && touched.has(key)}
2436
- {@const computed_radius = point.size_value != null
2437
- ? size_scale_fn(point.size_value)
2438
- : (tc(`point.size`) ? styles.point?.size : null) ?? pt?.radius ?? 4}
2439
- <ScatterPoint
2440
- x={screen_x}
2441
- y={screen_y}
2442
- is_hovered={tooltip_point?.series_idx === point.series_idx &&
2443
- tooltip_point?.point_idx === point.point_idx}
2444
- is_selected={selected_point?.series_idx === point.series_idx &&
2445
- selected_point?.point_idx === point.point_idx}
2446
- leader_line_threshold={actual_label_config.leader_line_threshold}
2447
- style={{
2448
- symbol_type: pt?.symbol_type ?? series_default_symbol,
2449
- ...pt,
2450
- radius: computed_radius,
2451
- stroke_width:
2452
- (tc(`point.stroke_width`) ? styles.point?.stroke_width : null) ??
2453
- pt?.stroke_width ?? 1,
2454
- stroke:
2455
- (tc(`point.stroke_color`) ? styles.point?.stroke_color : null) ??
2456
- pt?.stroke ?? `#000`,
2457
- stroke_opacity:
2458
- (tc(`point.stroke_opacity`) ? styles.point?.stroke_opacity : null) ??
2459
- pt?.stroke_opacity ?? 1,
2460
- fill_opacity: (tc(`point.opacity`) ? styles.point?.opacity : null) ??
2461
- pt?.fill_opacity ?? 1,
2462
- cursor: (on_point_click || point_events?.onclick)
2463
- ? `pointer`
2464
- : undefined,
2465
- }}
2466
- hover={point.point_hover ?? {}}
2467
- label={final_label}
2468
- offset={point.point_offset ?? { x: 0, y: 0 }}
2469
- {point_tween}
2470
- origin={{ x: plot_center_x, y: plot_center_y }}
2471
- --point-fill-color={point.color_value != null
2472
- ? color_scale_fn(point.color_value)
2473
- : (tc(`point.color`) ? styles.point?.color : null) ?? pt?.fill ??
2474
- series_default_color}
2475
- {...point_events &&
2476
- Object.fromEntries(
2477
- point_event_names.map((name) => [name, (event: Event) =>
2478
- point_events?.[name]?.({ point, event })]
2479
- ),
2480
- )}
2481
- onclick={(event: MouseEvent) => {
2482
- // Call user-provided onclick handler first if it exists
2483
- point_events?.onclick?.({ point, event })
2484
- // then handle internal logic
2485
- const props = construct_handler_props(point)
2486
- tooltip_point = point
2487
- if (props) on_point_click?.({ ...props, event, point })
2488
- }}
2489
- />
2490
- {/each}
2491
- {/if}
2492
- </g>
2493
- {/each}
2494
- {/if}
2495
-
2496
- <!-- Fill regions: above all -->
2497
- {@render fill_regions_layer(fills_by_z.above_all)}
2498
- <!-- Reference lines: above all -->
2499
- {@render ref_lines_layer(ref_lines_by_z.above_all)}
2500
- </svg>
2501
-
2502
- <!-- Tooltip overlay above all plot overlays (legend, colorbar) -->
2503
- {#if handler_props && hovered && tooltip_point}
2504
- {@const { color_value, point_label, point_style, series_idx } = tooltip_point}
2505
- {@const hovered_series = series_with_ids[series_idx]}
2506
- {@const series_markers = hovered_series?.markers ?? DEFAULT_MARKERS}
2507
- {@const is_transparent_or_none = (color: string | undefined | null): boolean =>
2508
- !color ||
2509
- color === `none` ||
2510
- color === `transparent` ||
2511
- /rgba\([^)]+[,/]\s*0(\.0*)?\s*\)$/.test(color)}
2512
- {@const tooltip_bg_color = (() => {
2513
- const scale_color = color_value != null
2514
- ? color_scale_fn(color_value)
2515
- : undefined
2516
- if (!is_transparent_or_none(scale_color)) return scale_color
2517
- const fill_color = point_style?.fill
2518
- if (!is_transparent_or_none(fill_color)) return fill_color
2519
- if (series_markers?.includes(`points`)) {
2520
- const stroke_color = point_style?.stroke
2521
- if (!is_transparent_or_none(stroke_color)) return stroke_color
2522
- }
2523
- if (series_markers?.includes(`line`)) {
2524
- const line_style = hovered_series?.line_style ?? {}
2525
- const first_point_style = Array.isArray(hovered_series?.point_style)
2526
- ? hovered_series?.point_style[0]
2527
- : hovered_series?.point_style
2528
- const first_color_value = hovered_series?.color_values?.[0]
2529
- let line_color_candidate = line_style.stroke
2530
- if (is_transparent_or_none(line_color_candidate)) {line_color_candidate =
2531
- first_point_style?.fill}
2532
- if (
2533
- is_transparent_or_none(line_color_candidate) && first_color_value != null
2534
- ) line_color_candidate = color_scale_fn(first_color_value)
2535
- if (
2536
- is_transparent_or_none(line_color_candidate) &&
2537
- series_markers?.includes(`points`)
2538
- ) line_color_candidate = first_point_style?.stroke
2539
- if (!is_transparent_or_none(line_color_candidate)) return line_color_candidate
2540
- }
2541
- return `rgba(0, 0, 0, 0.7)`
2542
- })()}
2543
- {@const tooltip_pos = constrain_tooltip_position(
2544
- handler_props.cx,
2545
- handler_props.cy,
2546
- tooltip_el?.offsetWidth ?? 120,
2547
- tooltip_el?.offsetHeight ?? 50,
2548
- width,
2549
- height,
2550
- { offset_x: 10, offset_y: 5 },
2551
- )}
2552
- <PlotTooltip
2553
- x={tooltip_pos.x}
2554
- y={tooltip_pos.y}
2555
- offset={{ x: 0, y: 0 }}
2556
- bg_color={tooltip_bg_color}
2557
- bind:wrapper={tooltip_el}
2558
- >
2559
- {#if tooltip}
2560
- {@render tooltip(handler_props)}
2561
- {:else}
2562
- {@const hp = handler_props}
2563
- {#if has_multiple_series && hp.label}<strong>{hp.label}</strong><br />{/if}
2564
- {@html sanitize_html(point_label?.text ? `${point_label.text}<br />` : ``)}
2565
- {@html sanitize_html(hp.x_axis.label || `x`)}: {hp.x_formatted}<br />
2566
- {@html sanitize_html(hp.y_axis.label || `y`)}: {hp.y_formatted}
2567
- {#if hp.colorbar?.value != null}
2568
- <br />{@html sanitize_html(hp.colorbar.title || `Color`)}: {
2569
- format_value(hp.colorbar.value, hp.colorbar.tick_format || `.3~g`)
2570
- }
2571
- {/if}
2572
- {/if}
2573
- </PlotTooltip>
2574
- {/if}
2575
-
2576
- <!-- Control Pane -->
2577
- {#if controls.show}
2578
- <ScatterPlotControls
2579
- toggle_props={{
2580
- ...controls.toggle_props,
2581
- style:
2582
- `--ctrl-btn-right: var(--fullscreen-btn-offset, 30px); top: var(--ctrl-btn-top, 5pt); ${
2583
- controls.toggle_props?.style ?? ``
2584
- }`,
2585
- }}
2586
- pane_props={controls.pane_props}
2587
- bind:x_axis
2588
- bind:x2_axis
2589
- bind:y_axis
2590
- bind:y2_axis
2591
- bind:display
2592
- bind:styles
2593
- {auto_x_range}
2594
- {auto_x2_range}
2595
- {auto_y_range}
2596
- {auto_y2_range}
2597
- bind:selected_series_idx
2598
- series={series_with_ids}
2599
- has_x2_points={x2_points.length > 0}
2600
- has_y2_points={y2_points.length > 0}
2601
- children={controls_extra}
2602
- on_touch={(key) => touched.add(key)}
2603
- />
2604
- {/if}
2605
-
2606
- <!-- Color Bar -->
2607
- {#if color_bar && all_color_values.length > 0 && color_bar_placement}
2608
- {@const color_domain = [
2609
- (typeof color_scale === `string` ? undefined : color_scale.value_range)?.[0] ??
2610
- auto_color_range[0],
2611
- (typeof color_scale === `string` ? undefined : color_scale.value_range)?.[1] ??
2612
- auto_color_range[1],
2613
- ] as Vec2}
2614
- <div
2615
- bind:this={colorbar_element}
2616
- onmouseenter={() => colorbar_hover.set_locked(true)}
2617
- onmouseleave={() => colorbar_hover.set_locked(false)}
2618
- class="colorbar-wrapper"
2619
- role="img"
2620
- aria-label="Color scale legend"
2621
- style={`
2622
- position: absolute;
2623
- left: ${tweened_colorbar_coords.current.x}px;
2624
- top: ${tweened_colorbar_coords.current.y}px;
2625
- pointer-events: auto;
2626
- `}
2627
- >
2628
- <ColorBar
2629
- tick_labels={4}
2630
- tick_side="primary"
2631
- {color_scale_fn}
2632
- color_scale_domain={color_domain}
2633
- scale_type={typeof color_scale === `string` ? undefined : color_scale.type}
2634
- range={color_domain?.every((val) => val != null) ? color_domain : undefined}
2635
- wrapper_style={color_bar?.wrapper_style ?? ``}
2636
- bar_style="width: 220px; height: 20px; {color_bar?.style ?? ``}"
2637
- {...color_bar}
2638
- />
2639
- </div>
2640
- {/if}
2641
-
2642
- <!-- Legend -->
2643
- <!-- Only render if multiple series or if legend prop was explicitly provided by user (even if empty object) -->
2644
- {#if legend != null && legend_data.length > 0 &&
2645
- (legend_data.length > 1 || Object.keys(legend).length > 0)}
2646
- {@const default_x = pad.l + 10}
2647
- {@const default_y = pad.t + 10}
2648
- {@const current_x = legend_is_dragging && legend_manual_position
2649
- ? legend_manual_position.x
2650
- : legend_placement
2651
- ? tweened_legend_coords.current.x
2652
- : default_x}
2653
- {@const current_y = legend_is_dragging && legend_manual_position
2654
- ? legend_manual_position.y
2655
- : legend_placement
2656
- ? tweened_legend_coords.current.y
2657
- : default_y}
2658
- <PlotLegend
2659
- bind:root_element={legend_element}
2660
- series_data={legend_data}
2661
- on_drag_start={handle_legend_drag_start}
2662
- on_drag={handle_legend_drag}
2663
- on_drag_end={() => (legend_is_dragging = false)}
2664
- on_hover_change={legend_hover.set_locked}
2665
- draggable={legend?.draggable ?? true}
2666
- {...legend}
2667
- on_toggle={legend?.on_toggle ??
2668
- ((series_idx) => {
2669
- series = toggle_series_visibility(series, series_idx)
2670
- })}
2671
- on_double_click={legend?.on_double_click ??
2672
- ((double_clicked_idx) => {
2673
- const result = handle_legend_double_click(
2674
- series,
2675
- double_clicked_idx,
2676
- previous_series_visibility,
2677
- )
2678
- series = result.series
2679
- previous_series_visibility = result.previous_visibility
2680
- })}
2681
- on_group_toggle={legend?.on_group_toggle ??
2682
- ((_group_name, series_indices) => {
2683
- series = toggle_group_visibility(series, series_indices)
2684
- })}
2685
- on_fill_toggle={(source_type, source_idx) => {
2686
- // Only fill_regions can be toggled (error_bands are not bindable)
2687
- if (source_type === `fill_region`) {
2688
- fill_regions = fill_regions.map((region, idx) =>
2689
- idx === source_idx
2690
- ? { ...region, visible: !(region.visible !== false) }
2691
- : region
2692
- )
2693
- }
2694
- }}
2695
- on_fill_double_click={(source_type, source_idx) => {
2696
- // Only fill_regions can be toggled (error_bands are not bindable)
2697
- if (source_type !== `fill_region`) return
2698
- // Toggle: if only this fill is visible, show all; otherwise show only this one
2699
- const visible_count = fill_regions.filter((r) => r.visible !== false).length
2700
- const this_visible = fill_regions[source_idx]?.visible !== false
2701
- if (visible_count === 1 && this_visible) {
2702
- // Show all fills
2703
- fill_regions = fill_regions.map((region) => ({ ...region, visible: true }))
2704
- } else {
2705
- // Show only this fill
2706
- fill_regions = fill_regions.map((region, idx) => ({
2707
- ...region,
2708
- visible: idx === source_idx,
2709
- }))
2710
- }
2711
- }}
2712
- style={`
2713
- position: absolute;
2714
- left: ${current_x}px;
2715
- top: ${current_y}px;
2716
- pointer-events: auto;
2717
- ${legend?.style ?? ``}
2718
- `}
2719
- />
2720
- {/if}
2721
- {/if}
2722
-
2723
- <!-- User-provided children (e.g. for custom absolutely-positioned overlays) -->
2724
- {@render children?.({ height, width, fullscreen })}
2725
- </div>
2726
-
2727
- <style>
2728
- div.scatter {
2729
- position: relative; /* Needed for absolute positioning of children like ColorBar */
2730
- width: var(--scatter-width, 100%);
2731
- height: var(--scatter-height, auto);
2732
- min-height: var(--scatter-min-height, 350px);
2733
- container-type: size; /* enable cqh for panes */
2734
- container-name: scatter-plot;
2735
- z-index: var(--scatter-z-index);
2736
- flex: var(--scatter-flex, 1); /* Allow filling available space in flex containers */
2737
- display: var(--scatter-display, flex);
2738
- flex-direction: column;
2739
- background: var(--scatter-bg, var(--plot-bg));
2740
- border-radius: var(--scatter-border-radius, var(--border-radius, 3pt));
2741
- }
2742
- div.scatter.fullscreen {
2743
- position: fixed;
2744
- top: 0;
2745
- left: 0;
2746
- width: 100vw !important;
2747
- height: 100vh !important;
2748
- /* Must be higher than Structure.svelte's --struct-buttons-z-index (100000000) */
2749
- z-index: var(--scatter-fullscreen-z-index, 100000001);
2750
- margin: 0;
2751
- border-radius: 0;
2752
- background: var(--scatter-fullscreen-bg, var(--scatter-bg, var(--plot-bg)));
2753
- max-height: none !important;
2754
- overflow: hidden;
2755
- /* Add padding to prevent titles from being cropped at top */
2756
- padding-top: var(--plot-fullscreen-padding-top, 2em);
2757
- box-sizing: border-box;
2758
- }
2759
- .header-controls {
2760
- position: absolute;
2761
- top: var(--ctrl-btn-top, 5pt);
2762
- right: var(--fullscreen-btn-right, 4px);
2763
- z-index: var(--fullscreen-btn-z-index, 10);
2764
- display: flex;
2765
- align-items: center;
2766
- gap: 8px;
2767
- }
2768
- .header-controls :global(.fullscreen-toggle) {
2769
- position: static; /* Override absolute positioning since container handles it */
2770
- opacity: 1; /* Always visible when inside header-controls, container controls visibility */
2771
- }
2772
- /* Hide controls and fullscreen toggles by default, show on hover */
2773
- div.scatter :global(.pane-toggle),
2774
- div.scatter .header-controls {
2775
- opacity: 0;
2776
- transition: opacity 0.2s, background-color 0.2s;
2777
- }
2778
- div.scatter:hover :global(.pane-toggle),
2779
- div.scatter:hover .header-controls,
2780
- div.scatter :global(.pane-toggle:focus-visible),
2781
- div.scatter :global(.pane-toggle[aria-expanded='true']),
2782
- div.scatter .header-controls:focus-within {
2783
- opacity: 1;
2784
- }
2785
- svg {
2786
- width: var(--scatter-svg-width, 100%);
2787
- height: var(--scatter-svg-height, 100%);
2788
- flex: var(--scatter-svg-flex, 1);
2789
- overflow: var(--scatter-svg-overflow, visible);
2790
- fill: var(--text-color);
2791
- font-weight: var(--scatter-font-weight);
2792
- font-size: var(--scatter-font-size);
2793
- }
2794
- line {
2795
- stroke: var(--scatter-grid-stroke, gray);
2796
- stroke-dasharray: var(--scatter-grid-dash, 4);
2797
- stroke-width: var(--scatter-grid-width, 0.4);
2798
- }
2799
- g:is(.x-axis, .x2-axis) text {
2800
- text-anchor: middle;
2801
- dominant-baseline: top;
2802
- }
2803
- g:is(.y-axis, .y2-axis) text {
2804
- dominant-baseline: central;
2805
- }
2806
- g:is(.x-axis, .x2-axis, .y-axis, .y2-axis) .tick text {
2807
- font-size: var(--tick-font-size, 0.8em); /* shrink tick labels */
2808
- }
2809
- .scatter :global(.axis-label) {
2810
- text-align: center;
2811
- width: 100%;
2812
- height: 100%;
2813
- font-size: var(--scatter-font-size, inherit);
2814
- font-weight: var(--scatter-font-weight, normal);
2815
- color: var(--text-color);
2816
- white-space: nowrap;
2817
- /* Use line-height to center text vertically without flexbox */
2818
- line-height: var(
2819
- --scatter-axis-label-line-height,
2820
- 20px
2821
- ); /* Match foreignObject height */
2822
- display: block;
2823
- }
2824
- .current-frame-indicator {
2825
- filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2));
2826
- transition: opacity 0.2s ease;
2827
- }
2828
- .current-frame-indicator:hover {
2829
- opacity: 0.8;
2830
- }
2831
- </style>