matterviz 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (926) hide show
  1. package/dist/EmptyState.svelte.d.ts +9 -0
  2. package/dist/FilePicker.svelte +360 -0
  3. package/dist/FilePicker.svelte.d.ts +17 -0
  4. package/dist/Icon.svelte +44 -0
  5. package/dist/Icon.svelte.d.ts +13 -0
  6. package/dist/MillerIndexInput.svelte +66 -0
  7. package/dist/MillerIndexInput.svelte.d.ts +7 -0
  8. package/dist/api/mp.d.ts +6 -0
  9. package/dist/api/mp.js +22 -0
  10. package/dist/api/optimade.d.ts +45 -0
  11. package/dist/api/optimade.js +141 -0
  12. package/dist/app.css +244 -0
  13. package/dist/brillouin/BrillouinZone.svelte +554 -0
  14. package/dist/brillouin/BrillouinZone.svelte.d.ts +84 -0
  15. package/dist/brillouin/BrillouinZoneControls.svelte +144 -0
  16. package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +17 -0
  17. package/dist/brillouin/BrillouinZoneExportPane.svelte +146 -0
  18. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +15 -0
  19. package/dist/brillouin/BrillouinZoneInfoPane.svelte +146 -0
  20. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +13 -0
  21. package/dist/brillouin/BrillouinZoneScene.svelte +522 -0
  22. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +49 -0
  23. package/dist/brillouin/BrillouinZoneTooltip.svelte +83 -0
  24. package/dist/brillouin/BrillouinZoneTooltip.svelte.d.ts +8 -0
  25. package/dist/brillouin/compute.d.ts +17 -0
  26. package/dist/brillouin/compute.js +422 -0
  27. package/dist/brillouin/index.d.ts +8 -0
  28. package/dist/brillouin/index.js +7 -0
  29. package/dist/brillouin/types.d.ts +43 -0
  30. package/dist/brillouin/types.js +1 -0
  31. package/dist/chempot-diagram/ChemPotDiagram.svelte +328 -0
  32. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  33. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +843 -0
  34. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  35. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +3191 -0
  36. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  37. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  38. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  39. package/dist/chempot-diagram/async-compute.svelte.js +80 -0
  40. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  41. package/dist/chempot-diagram/chempot-worker.js +12 -0
  42. package/dist/chempot-diagram/color.d.ts +10 -0
  43. package/dist/chempot-diagram/color.js +32 -0
  44. package/dist/chempot-diagram/compute.d.ts +48 -0
  45. package/dist/chempot-diagram/compute.js +804 -0
  46. package/dist/chempot-diagram/index.d.ts +6 -0
  47. package/dist/chempot-diagram/index.js +6 -0
  48. package/dist/chempot-diagram/pointer.d.ts +16 -0
  49. package/dist/chempot-diagram/pointer.js +40 -0
  50. package/dist/chempot-diagram/temperature.d.ts +15 -0
  51. package/dist/chempot-diagram/temperature.js +34 -0
  52. package/dist/chempot-diagram/types.d.ts +81 -0
  53. package/dist/chempot-diagram/types.js +28 -0
  54. package/dist/colors/index.d.ts +47 -0
  55. package/dist/colors/index.js +204 -0
  56. package/dist/composition/BarChart.svelte +297 -0
  57. package/dist/composition/BarChart.svelte.d.ts +39 -0
  58. package/dist/composition/BubbleChart.svelte +218 -0
  59. package/dist/composition/BubbleChart.svelte.d.ts +28 -0
  60. package/dist/composition/Composition.svelte +165 -0
  61. package/dist/composition/Composition.svelte.d.ts +15 -0
  62. package/dist/composition/Formula.svelte +268 -0
  63. package/dist/composition/Formula.svelte.d.ts +19 -0
  64. package/dist/composition/FormulaFilter.svelte +1263 -0
  65. package/dist/composition/FormulaFilter.svelte.d.ts +51 -0
  66. package/dist/composition/PieChart.svelte +324 -0
  67. package/dist/composition/PieChart.svelte.d.ts +37 -0
  68. package/dist/composition/chem-sys.d.ts +8 -0
  69. package/dist/composition/chem-sys.js +85 -0
  70. package/dist/composition/format.d.ts +15 -0
  71. package/dist/composition/format.js +111 -0
  72. package/dist/composition/index.d.ts +21 -0
  73. package/dist/composition/index.js +15 -0
  74. package/dist/composition/parse.d.ts +56 -0
  75. package/dist/composition/parse.js +486 -0
  76. package/dist/constants.d.ts +29 -0
  77. package/dist/constants.js +99 -0
  78. package/dist/controls.d.ts +14 -0
  79. package/dist/controls.js +30 -0
  80. package/dist/convex-hull/ConvexHull.svelte +157 -0
  81. package/dist/convex-hull/ConvexHull.svelte.d.ts +13 -0
  82. package/dist/convex-hull/ConvexHull2D.svelte +827 -0
  83. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +11 -0
  84. package/dist/convex-hull/ConvexHull3D.svelte +1801 -0
  85. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +8 -0
  86. package/dist/convex-hull/ConvexHull4D.svelte +1394 -0
  87. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +8 -0
  88. package/dist/convex-hull/ConvexHullControls.svelte +535 -0
  89. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +48 -0
  90. package/dist/convex-hull/ConvexHullInfoPane.svelte +125 -0
  91. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +20 -0
  92. package/dist/convex-hull/ConvexHullStats.svelte +929 -0
  93. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +17 -0
  94. package/dist/convex-hull/ConvexHullTooltip.svelte +131 -0
  95. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +33 -0
  96. package/dist/convex-hull/GasPressureControls.svelte +247 -0
  97. package/dist/convex-hull/GasPressureControls.svelte.d.ts +11 -0
  98. package/dist/convex-hull/StructurePopup.svelte +151 -0
  99. package/dist/convex-hull/StructurePopup.svelte.d.ts +18 -0
  100. package/dist/convex-hull/TemperatureSlider.svelte +140 -0
  101. package/dist/convex-hull/TemperatureSlider.svelte.d.ts +8 -0
  102. package/dist/convex-hull/barycentric-coords.d.ts +18 -0
  103. package/dist/convex-hull/barycentric-coords.js +182 -0
  104. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  105. package/dist/convex-hull/demo-temperature.js +38 -0
  106. package/dist/convex-hull/gas-thermodynamics.d.ts +16 -0
  107. package/dist/convex-hull/gas-thermodynamics.js +306 -0
  108. package/dist/convex-hull/helpers.d.ts +117 -0
  109. package/dist/convex-hull/helpers.js +718 -0
  110. package/dist/convex-hull/index.d.ts +119 -0
  111. package/dist/convex-hull/index.js +58 -0
  112. package/dist/convex-hull/thermodynamics.d.ts +67 -0
  113. package/dist/convex-hull/thermodynamics.js +1757 -0
  114. package/dist/convex-hull/types.d.ts +162 -0
  115. package/dist/convex-hull/types.js +36 -0
  116. package/dist/coordination/CoordinationBarPlot.svelte +311 -0
  117. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +30 -0
  118. package/dist/coordination/calc-coordination.d.ts +15 -0
  119. package/dist/coordination/calc-coordination.js +63 -0
  120. package/dist/coordination/index.d.ts +8 -0
  121. package/dist/coordination/index.js +7 -0
  122. package/dist/effects.svelte.d.ts +12 -0
  123. package/dist/effects.svelte.js +37 -0
  124. package/dist/element/BohrAtom.svelte.d.ts +20 -0
  125. package/dist/element/ElementHeading.svelte +26 -0
  126. package/dist/element/ElementHeading.svelte.d.ts +8 -0
  127. package/dist/element/ElementPhoto.svelte +57 -0
  128. package/dist/element/ElementPhoto.svelte.d.ts +9 -0
  129. package/dist/element/ElementStats.svelte +80 -0
  130. package/dist/element/ElementStats.svelte.d.ts +8 -0
  131. package/dist/element/ElementTile.svelte +484 -0
  132. package/dist/element/ElementTile.svelte.d.ts +29 -0
  133. package/dist/element/Nucleus.svelte.d.ts +17 -0
  134. package/dist/element/data.d.ts +2 -0
  135. package/dist/element/data.js +2 -0
  136. package/dist/element/index.d.ts +8 -0
  137. package/dist/element/index.js +7 -0
  138. package/dist/element/types.d.ts +57 -0
  139. package/dist/element/types.js +1 -0
  140. package/dist/feedback/ClickFeedback.svelte +58 -0
  141. package/dist/feedback/ClickFeedback.svelte.d.ts +12 -0
  142. package/dist/feedback/DragOverlay.svelte +42 -0
  143. package/dist/feedback/DragOverlay.svelte.d.ts +7 -0
  144. package/dist/feedback/Spinner.svelte.d.ts +7 -0
  145. package/dist/feedback/StatusMessage.svelte.d.ts +9 -0
  146. package/dist/feedback/index.d.ts +4 -0
  147. package/dist/feedback/index.js +4 -0
  148. package/dist/fermi-surface/FermiSlice.svelte +197 -0
  149. package/dist/fermi-surface/FermiSlice.svelte.d.ts +24 -0
  150. package/dist/fermi-surface/FermiSurface.svelte +606 -0
  151. package/dist/fermi-surface/FermiSurface.svelte.d.ts +83 -0
  152. package/dist/fermi-surface/FermiSurfaceControls.svelte +448 -0
  153. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +35 -0
  154. package/dist/fermi-surface/FermiSurfaceScene.svelte +797 -0
  155. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +50 -0
  156. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +85 -0
  157. package/dist/fermi-surface/FermiSurfaceTooltip.svelte.d.ts +8 -0
  158. package/dist/fermi-surface/compute.d.ts +5 -0
  159. package/dist/fermi-surface/compute.js +538 -0
  160. package/dist/fermi-surface/constants.d.ts +9 -0
  161. package/dist/fermi-surface/constants.js +27 -0
  162. package/dist/fermi-surface/export.d.ts +5 -0
  163. package/dist/fermi-surface/export.js +51 -0
  164. package/dist/fermi-surface/index.d.ts +12 -0
  165. package/dist/fermi-surface/index.js +13 -0
  166. package/dist/fermi-surface/marching-cubes.d.ts +2 -0
  167. package/dist/fermi-surface/marching-cubes.js +2 -0
  168. package/dist/fermi-surface/parse.d.ts +2 -0
  169. package/dist/fermi-surface/parse.js +494 -0
  170. package/dist/fermi-surface/symmetry.d.ts +3 -0
  171. package/dist/fermi-surface/symmetry.js +46 -0
  172. package/dist/fermi-surface/types.d.ts +111 -0
  173. package/dist/fermi-surface/types.js +4 -0
  174. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1547 -0
  175. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  176. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
  177. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +30 -0
  178. package/dist/heatmap-matrix/index.d.ts +53 -0
  179. package/dist/heatmap-matrix/index.js +100 -0
  180. package/dist/heatmap-matrix/shared.d.ts +2 -0
  181. package/dist/heatmap-matrix/shared.js +4 -0
  182. package/dist/icons.d.ts +569 -0
  183. package/dist/icons.js +648 -0
  184. package/dist/index.d.ts +39 -0
  185. package/dist/index.js +39 -0
  186. package/dist/io/decompress.d.ts +11 -0
  187. package/dist/io/decompress.js +76 -0
  188. package/dist/io/export.d.ts +16 -0
  189. package/dist/io/export.js +338 -0
  190. package/dist/io/fetch.d.ts +5 -0
  191. package/dist/io/fetch.js +43 -0
  192. package/dist/io/file-drop.d.ts +7 -0
  193. package/dist/io/file-drop.js +42 -0
  194. package/dist/io/index.d.ts +7 -0
  195. package/dist/io/index.js +6 -0
  196. package/dist/io/is-binary.d.ts +1 -0
  197. package/dist/io/is-binary.js +20 -0
  198. package/dist/io/types.d.ts +8 -0
  199. package/dist/io/types.js +1 -0
  200. package/dist/io/url-drop.d.ts +2 -0
  201. package/dist/io/url-drop.js +154 -0
  202. package/dist/isosurface/Isosurface.svelte +285 -0
  203. package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
  204. package/dist/isosurface/IsosurfaceControls.svelte +277 -0
  205. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
  206. package/dist/isosurface/index.d.ts +5 -0
  207. package/dist/isosurface/index.js +6 -0
  208. package/dist/isosurface/parse.d.ts +6 -0
  209. package/dist/isosurface/parse.js +552 -0
  210. package/dist/isosurface/slice.d.ts +11 -0
  211. package/dist/isosurface/slice.js +141 -0
  212. package/dist/isosurface/types.d.ts +56 -0
  213. package/dist/isosurface/types.js +227 -0
  214. package/dist/keyboard.d.ts +3 -0
  215. package/dist/keyboard.js +23 -0
  216. package/dist/labels.d.ts +53 -0
  217. package/dist/labels.js +278 -0
  218. package/dist/layout/FullscreenToggle.svelte +50 -0
  219. package/dist/layout/FullscreenToggle.svelte.d.ts +7 -0
  220. package/dist/layout/InfoCard.svelte +120 -0
  221. package/dist/layout/InfoCard.svelte.d.ts +21 -0
  222. package/dist/layout/InfoTag.svelte +185 -0
  223. package/dist/layout/InfoTag.svelte.d.ts +19 -0
  224. package/dist/layout/PropertyFilter.svelte +247 -0
  225. package/dist/layout/PropertyFilter.svelte.d.ts +24 -0
  226. package/dist/layout/SettingsSection.svelte +148 -0
  227. package/dist/layout/SettingsSection.svelte.d.ts +17 -0
  228. package/dist/layout/SubpageGrid.svelte +82 -0
  229. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  230. package/dist/layout/fullscreen.d.ts +9 -0
  231. package/dist/layout/fullscreen.js +53 -0
  232. package/dist/layout/index.d.ts +10 -0
  233. package/dist/layout/index.js +8 -0
  234. package/dist/layout/json-tree/JsonNode.svelte +548 -0
  235. package/dist/layout/json-tree/JsonNode.svelte.d.ts +11 -0
  236. package/dist/layout/json-tree/JsonTree.svelte +1230 -0
  237. package/dist/layout/json-tree/JsonTree.svelte.d.ts +6 -0
  238. package/dist/layout/json-tree/JsonValue.svelte.d.ts +9 -0
  239. package/dist/layout/json-tree/index.d.ts +3 -0
  240. package/dist/layout/json-tree/index.js +3 -0
  241. package/dist/layout/json-tree/types.d.ts +74 -0
  242. package/dist/layout/json-tree/types.js +2 -0
  243. package/dist/layout/json-tree/utils.d.ts +29 -0
  244. package/dist/layout/json-tree/utils.js +642 -0
  245. package/dist/marching-cubes.d.ts +14 -0
  246. package/dist/marching-cubes.js +535 -0
  247. package/dist/math.d.ts +105 -0
  248. package/dist/math.js +920 -0
  249. package/dist/overlays/ContextMenu.svelte +162 -0
  250. package/dist/overlays/ContextMenu.svelte.d.ts +25 -0
  251. package/dist/overlays/CopyButton.svelte +45 -0
  252. package/dist/overlays/CopyButton.svelte.d.ts +8 -0
  253. package/dist/overlays/DragControlTab.svelte +98 -0
  254. package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
  255. package/dist/overlays/DraggablePane.svelte +487 -0
  256. package/dist/overlays/DraggablePane.svelte.d.ts +36 -0
  257. package/dist/overlays/InfoPaneCards.svelte +149 -0
  258. package/dist/overlays/InfoPaneCards.svelte.d.ts +22 -0
  259. package/dist/overlays/index.d.ts +3 -0
  260. package/dist/overlays/index.js +3 -0
  261. package/dist/periodic-table/PeriodicTable.svelte +480 -0
  262. package/dist/periodic-table/PeriodicTable.svelte.d.ts +55 -0
  263. package/dist/periodic-table/PeriodicTableControls.svelte +557 -0
  264. package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +24 -0
  265. package/dist/periodic-table/PropertySelect.svelte +38 -0
  266. package/dist/periodic-table/PropertySelect.svelte.d.ts +13 -0
  267. package/dist/periodic-table/TableInset.svelte.d.ts +9 -0
  268. package/dist/periodic-table/index.d.ts +10 -0
  269. package/dist/periodic-table/index.js +4 -0
  270. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +1092 -0
  271. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +44 -0
  272. package/dist/phase-diagram/PhaseDiagramControls.svelte +444 -0
  273. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +30 -0
  274. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +127 -0
  275. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  276. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +184 -0
  277. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +19 -0
  278. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +391 -0
  279. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +16 -0
  280. package/dist/phase-diagram/TdbInfoPanel.svelte +203 -0
  281. package/dist/phase-diagram/TdbInfoPanel.svelte.d.ts +12 -0
  282. package/dist/phase-diagram/build-diagram.d.ts +11 -0
  283. package/dist/phase-diagram/build-diagram.js +160 -0
  284. package/dist/phase-diagram/colors.d.ts +35 -0
  285. package/dist/phase-diagram/colors.js +51 -0
  286. package/dist/phase-diagram/diagram-input.d.ts +29 -0
  287. package/dist/phase-diagram/diagram-input.js +3 -0
  288. package/dist/phase-diagram/index.d.ts +13 -0
  289. package/dist/phase-diagram/index.js +11 -0
  290. package/dist/phase-diagram/parse.d.ts +55 -0
  291. package/dist/phase-diagram/parse.js +273 -0
  292. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  293. package/dist/phase-diagram/svg-to-diagram.js +867 -0
  294. package/dist/phase-diagram/types.d.ts +93 -0
  295. package/dist/phase-diagram/types.js +1 -0
  296. package/dist/phase-diagram/utils.d.ts +118 -0
  297. package/dist/phase-diagram/utils.js +600 -0
  298. package/dist/plot/bar/BarPlot.svelte +1755 -0
  299. package/dist/plot/bar/BarPlot.svelte.d.ts +84 -0
  300. package/dist/plot/bar/BarPlotControls.svelte +67 -0
  301. package/dist/plot/bar/BarPlotControls.svelte.d.ts +18 -0
  302. package/dist/plot/bar/SpacegroupBarPlot.svelte +293 -0
  303. package/dist/plot/bar/SpacegroupBarPlot.svelte.d.ts +9 -0
  304. package/dist/plot/bar/data.d.ts +40 -0
  305. package/dist/plot/bar/data.js +154 -0
  306. package/dist/plot/bar/geometry.d.ts +39 -0
  307. package/dist/plot/bar/geometry.js +60 -0
  308. package/dist/plot/bar/index.d.ts +3 -0
  309. package/dist/plot/bar/index.js +3 -0
  310. package/dist/plot/box/BoxPlot.svelte +1462 -0
  311. package/dist/plot/box/BoxPlot.svelte.d.ts +94 -0
  312. package/dist/plot/box/BoxPlotControls.svelte +109 -0
  313. package/dist/plot/box/BoxPlotControls.svelte.d.ts +19 -0
  314. package/dist/plot/box/Violin.svelte +14 -0
  315. package/dist/plot/box/Violin.svelte.d.ts +70 -0
  316. package/dist/plot/box/box-plot.d.ts +55 -0
  317. package/dist/plot/box/box-plot.js +126 -0
  318. package/dist/plot/box/index.d.ts +5 -0
  319. package/dist/plot/box/index.js +5 -0
  320. package/dist/plot/box/kde.d.ts +16 -0
  321. package/dist/plot/box/kde.js +160 -0
  322. package/dist/plot/box/quantile.d.ts +3 -0
  323. package/dist/plot/box/quantile.js +53 -0
  324. package/dist/plot/core/auto-place.d.ts +43 -0
  325. package/dist/plot/core/auto-place.js +122 -0
  326. package/dist/plot/core/axis-utils.d.ts +46 -0
  327. package/dist/plot/core/axis-utils.js +110 -0
  328. package/dist/plot/core/components/AxisLabel.svelte +51 -0
  329. package/dist/plot/core/components/AxisLabel.svelte.d.ts +16 -0
  330. package/dist/plot/core/components/ColorBar.svelte +724 -0
  331. package/dist/plot/core/components/ColorBar.svelte.d.ts +31 -0
  332. package/dist/plot/core/components/ColorScaleSelect.svelte +55 -0
  333. package/dist/plot/core/components/ColorScaleSelect.svelte.d.ts +15 -0
  334. package/dist/plot/core/components/ControlPane.svelte +46 -0
  335. package/dist/plot/core/components/ControlPane.svelte.d.ts +13 -0
  336. package/dist/plot/core/components/FillArea.svelte +234 -0
  337. package/dist/plot/core/components/FillArea.svelte.d.ts +21 -0
  338. package/dist/plot/core/components/InteractiveAxisLabel.svelte +96 -0
  339. package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +14 -0
  340. package/dist/plot/core/components/Line.svelte +101 -0
  341. package/dist/plot/core/components/Line.svelte.d.ts +15 -0
  342. package/dist/plot/core/components/PlotAxis.svelte +171 -0
  343. package/dist/plot/core/components/PlotAxis.svelte.d.ts +25 -0
  344. package/dist/plot/core/components/PlotControls.svelte +525 -0
  345. package/dist/plot/core/components/PlotControls.svelte.d.ts +4 -0
  346. package/dist/plot/core/components/PlotLegend.svelte +580 -0
  347. package/dist/plot/core/components/PlotLegend.svelte.d.ts +30 -0
  348. package/dist/plot/core/components/PlotTooltip.svelte +83 -0
  349. package/dist/plot/core/components/PlotTooltip.svelte.d.ts +25 -0
  350. package/dist/plot/core/components/PortalSelect.svelte +257 -0
  351. package/dist/plot/core/components/PortalSelect.svelte.d.ts +16 -0
  352. package/dist/plot/core/components/ReferenceLine.svelte +204 -0
  353. package/dist/plot/core/components/ReferenceLine.svelte.d.ts +20 -0
  354. package/dist/plot/core/components/ReferenceLine3D.svelte +156 -0
  355. package/dist/plot/core/components/ReferenceLine3D.svelte.d.ts +14 -0
  356. package/dist/plot/core/components/ReferencePlane.svelte +175 -0
  357. package/dist/plot/core/components/ReferencePlane.svelte.d.ts +14 -0
  358. package/dist/plot/core/components/ZeroLines.svelte +97 -0
  359. package/dist/plot/core/components/ZeroLines.svelte.d.ts +33 -0
  360. package/dist/plot/core/components/ZoomRect.svelte +23 -0
  361. package/dist/plot/core/components/ZoomRect.svelte.d.ts +8 -0
  362. package/dist/plot/core/components/index.d.ts +17 -0
  363. package/dist/plot/core/components/index.js +17 -0
  364. package/dist/plot/core/data-cleaning.d.ts +107 -0
  365. package/dist/plot/core/data-cleaning.js +853 -0
  366. package/dist/plot/core/data-transform.d.ts +16 -0
  367. package/dist/plot/core/data-transform.js +45 -0
  368. package/dist/plot/core/fill-utils.d.ts +33 -0
  369. package/dist/plot/core/fill-utils.js +388 -0
  370. package/dist/plot/core/hover-lock.svelte.d.ts +14 -0
  371. package/dist/plot/core/hover-lock.svelte.js +45 -0
  372. package/dist/plot/core/index.d.ts +10 -0
  373. package/dist/plot/core/index.js +11 -0
  374. package/dist/plot/core/interactions.d.ts +35 -0
  375. package/dist/plot/core/interactions.js +195 -0
  376. package/dist/plot/core/layout.d.ts +79 -0
  377. package/dist/plot/core/layout.js +281 -0
  378. package/dist/plot/core/reference-line.d.ts +60 -0
  379. package/dist/plot/core/reference-line.js +301 -0
  380. package/dist/plot/core/scales.d.ts +48 -0
  381. package/dist/plot/core/scales.js +480 -0
  382. package/dist/plot/core/svg.d.ts +2 -0
  383. package/dist/plot/core/svg.js +41 -0
  384. package/dist/plot/core/types.d.ts +771 -0
  385. package/dist/plot/core/types.js +99 -0
  386. package/dist/plot/core/utils/label-placement.d.ts +68 -0
  387. package/dist/plot/core/utils/label-placement.js +326 -0
  388. package/dist/plot/core/utils/series-visibility.d.ts +26 -0
  389. package/dist/plot/core/utils/series-visibility.js +112 -0
  390. package/dist/plot/core/utils.d.ts +11 -0
  391. package/dist/plot/core/utils.js +27 -0
  392. package/dist/plot/histogram/Histogram.svelte +1418 -0
  393. package/dist/plot/histogram/Histogram.svelte.d.ts +50 -0
  394. package/dist/plot/histogram/HistogramControls.svelte +212 -0
  395. package/dist/plot/histogram/HistogramControls.svelte.d.ts +22 -0
  396. package/dist/plot/histogram/index.d.ts +2 -0
  397. package/dist/plot/histogram/index.js +2 -0
  398. package/dist/plot/index.d.ts +8 -0
  399. package/dist/plot/index.js +10 -0
  400. package/dist/plot/sankey/Sankey.svelte +700 -0
  401. package/dist/plot/sankey/Sankey.svelte.d.ts +74 -0
  402. package/dist/plot/sankey/SankeyControls.svelte +98 -0
  403. package/dist/plot/sankey/SankeyControls.svelte.d.ts +19 -0
  404. package/dist/plot/sankey/index.d.ts +4 -0
  405. package/dist/plot/sankey/index.js +3 -0
  406. package/dist/plot/sankey/sankey-types.d.ts +42 -0
  407. package/dist/plot/sankey/sankey-types.js +4 -0
  408. package/dist/plot/sankey/sankey.d.ts +52 -0
  409. package/dist/plot/sankey/sankey.js +187 -0
  410. package/dist/plot/scatter/BinnedScatterPlot.svelte +1116 -0
  411. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +66 -0
  412. package/dist/plot/scatter/ElementScatter.svelte +63 -0
  413. package/dist/plot/scatter/ElementScatter.svelte.d.ts +14 -0
  414. package/dist/plot/scatter/ScatterPlot.svelte +2357 -0
  415. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +96 -0
  416. package/dist/plot/scatter/ScatterPlotControls.svelte +307 -0
  417. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +17 -0
  418. package/dist/plot/scatter/ScatterPoint.svelte +182 -0
  419. package/dist/plot/scatter/ScatterPoint.svelte.d.ts +22 -0
  420. package/dist/plot/scatter/adaptive-density.d.ts +79 -0
  421. package/dist/plot/scatter/adaptive-density.js +217 -0
  422. package/dist/plot/scatter/binned-scatter-types.d.ts +59 -0
  423. package/dist/plot/scatter/binned-scatter-types.js +1 -0
  424. package/dist/plot/scatter/index.d.ts +7 -0
  425. package/dist/plot/scatter/index.js +5 -0
  426. package/dist/plot/scatter/scatter-data.d.ts +19 -0
  427. package/dist/plot/scatter/scatter-data.js +212 -0
  428. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +531 -0
  429. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +95 -0
  430. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +438 -0
  431. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte.d.ts +20 -0
  432. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +912 -0
  433. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +74 -0
  434. package/dist/plot/scatter-3d/Surface3D.svelte +197 -0
  435. package/dist/plot/scatter-3d/Surface3D.svelte.d.ts +13 -0
  436. package/dist/plot/scatter-3d/index.d.ts +4 -0
  437. package/dist/plot/scatter-3d/index.js +4 -0
  438. package/dist/plot/sunburst/Sunburst.svelte +1045 -0
  439. package/dist/plot/sunburst/Sunburst.svelte.d.ts +96 -0
  440. package/dist/plot/sunburst/SunburstControls.svelte +200 -0
  441. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +26 -0
  442. package/dist/plot/sunburst/index.d.ts +4 -0
  443. package/dist/plot/sunburst/index.js +4 -0
  444. package/dist/plot/sunburst/render.d.ts +34 -0
  445. package/dist/plot/sunburst/render.js +122 -0
  446. package/dist/plot/sunburst/sunburst.d.ts +62 -0
  447. package/dist/plot/sunburst/sunburst.js +266 -0
  448. package/dist/rdf/RdfPlot.svelte +248 -0
  449. package/dist/rdf/RdfPlot.svelte.d.ts +27 -0
  450. package/dist/rdf/calc-rdf.d.ts +4 -0
  451. package/dist/rdf/calc-rdf.js +98 -0
  452. package/dist/rdf/index.d.ts +23 -0
  453. package/dist/rdf/index.js +2 -0
  454. package/dist/sanitize.d.ts +6 -0
  455. package/dist/sanitize.js +116 -0
  456. package/dist/settings.d.ts +319 -0
  457. package/dist/settings.js +1394 -0
  458. package/dist/spectral/Bands.svelte +1050 -0
  459. package/dist/spectral/Bands.svelte.d.ts +39 -0
  460. package/dist/spectral/BandsAndDos.svelte +134 -0
  461. package/dist/spectral/BandsAndDos.svelte.d.ts +18 -0
  462. package/dist/spectral/BrillouinBandsDos.svelte +264 -0
  463. package/dist/spectral/BrillouinBandsDos.svelte.d.ts +20 -0
  464. package/dist/spectral/Dos.svelte +688 -0
  465. package/dist/spectral/Dos.svelte.d.ts +29 -0
  466. package/dist/spectral/helpers.d.ts +121 -0
  467. package/dist/spectral/helpers.js +1098 -0
  468. package/dist/spectral/index.d.ts +6 -0
  469. package/dist/spectral/index.js +6 -0
  470. package/dist/spectral/types.d.ts +84 -0
  471. package/dist/spectral/types.js +2 -0
  472. package/dist/state.svelte.d.ts +25 -0
  473. package/dist/state.svelte.js +45 -0
  474. package/dist/structure/Arrow.svelte +72 -0
  475. package/dist/structure/Arrow.svelte.d.ts +15 -0
  476. package/dist/structure/AtomLegend.svelte +814 -0
  477. package/dist/structure/AtomLegend.svelte.d.ts +35 -0
  478. package/dist/structure/Bond.svelte +140 -0
  479. package/dist/structure/Bond.svelte.d.ts +9 -0
  480. package/dist/structure/CanvasTooltip.svelte +33 -0
  481. package/dist/structure/CanvasTooltip.svelte.d.ts +12 -0
  482. package/dist/structure/CellSelect.svelte +348 -0
  483. package/dist/structure/CellSelect.svelte.d.ts +13 -0
  484. package/dist/structure/Cylinder.svelte +49 -0
  485. package/dist/structure/Cylinder.svelte.d.ts +13 -0
  486. package/dist/structure/Lattice.svelte +196 -0
  487. package/dist/structure/Lattice.svelte.d.ts +17 -0
  488. package/dist/structure/Structure.svelte +2254 -0
  489. package/dist/structure/Structure.svelte.d.ts +89 -0
  490. package/dist/structure/StructureControls.svelte +1273 -0
  491. package/dist/structure/StructureControls.svelte.d.ts +31 -0
  492. package/dist/structure/StructureExportPane.svelte +252 -0
  493. package/dist/structure/StructureExportPane.svelte.d.ts +17 -0
  494. package/dist/structure/StructureInfoPane.svelte +736 -0
  495. package/dist/structure/StructureInfoPane.svelte.d.ts +19 -0
  496. package/dist/structure/StructureScene.svelte +2256 -0
  497. package/dist/structure/StructureScene.svelte.d.ts +111 -0
  498. package/dist/structure/atom-properties.d.ts +37 -0
  499. package/dist/structure/atom-properties.js +200 -0
  500. package/dist/structure/bond-order-perception.d.ts +13 -0
  501. package/dist/structure/bond-order-perception.js +384 -0
  502. package/dist/structure/bonding.d.ts +69 -0
  503. package/dist/structure/bonding.js +724 -0
  504. package/dist/structure/export.d.ts +20 -0
  505. package/dist/structure/export.js +731 -0
  506. package/dist/structure/index.d.ts +124 -0
  507. package/dist/structure/index.js +167 -0
  508. package/dist/structure/label-placement.d.ts +14 -0
  509. package/dist/structure/label-placement.js +72 -0
  510. package/dist/structure/measure.d.ts +7 -0
  511. package/dist/structure/measure.js +30 -0
  512. package/dist/structure/parse.d.ts +66 -0
  513. package/dist/structure/parse.js +1410 -0
  514. package/dist/structure/partial-occupancy.d.ts +25 -0
  515. package/dist/structure/partial-occupancy.js +99 -0
  516. package/dist/structure/pbc.d.ts +9 -0
  517. package/dist/structure/pbc.js +127 -0
  518. package/dist/structure/supercell.d.ts +8 -0
  519. package/dist/structure/supercell.js +170 -0
  520. package/dist/structure/validation.d.ts +2 -0
  521. package/dist/structure/validation.js +10 -0
  522. package/dist/symmetry/SymmetryStats.svelte +226 -0
  523. package/dist/symmetry/SymmetryStats.svelte.d.ts +21 -0
  524. package/dist/symmetry/WyckoffTable.svelte +120 -0
  525. package/dist/symmetry/WyckoffTable.svelte.d.ts +11 -0
  526. package/dist/symmetry/cell-transform.d.ts +12 -0
  527. package/dist/symmetry/cell-transform.js +91 -0
  528. package/dist/symmetry/index.d.ts +43 -0
  529. package/dist/symmetry/index.js +226 -0
  530. package/dist/symmetry/spacegroups.d.ts +16 -0
  531. package/dist/symmetry/spacegroups.js +429 -0
  532. package/dist/table/HeatmapTable.svelte +1885 -0
  533. package/dist/table/HeatmapTable.svelte.d.ts +49 -0
  534. package/dist/table/ToggleMenu.svelte +385 -0
  535. package/dist/table/ToggleMenu.svelte.d.ts +11 -0
  536. package/dist/table/index.d.ts +72 -0
  537. package/dist/table/index.js +38 -0
  538. package/dist/theme/ThemeControl.svelte +53 -0
  539. package/dist/theme/ThemeControl.svelte.d.ts +9 -0
  540. package/dist/theme/index.d.ts +29 -0
  541. package/dist/theme/index.js +79 -0
  542. package/dist/time.d.ts +4 -0
  543. package/dist/time.js +70 -0
  544. package/dist/tooltip/KCoords.svelte +45 -0
  545. package/dist/tooltip/KCoords.svelte.d.ts +8 -0
  546. package/dist/tooltip/TooltipContent.svelte +58 -0
  547. package/dist/tooltip/TooltipContent.svelte.d.ts +31 -0
  548. package/dist/tooltip/index.d.ts +3 -0
  549. package/dist/tooltip/index.js +2 -0
  550. package/dist/tooltip/types.d.ts +8 -0
  551. package/dist/tooltip/types.js +1 -0
  552. package/dist/trajectory/Trajectory.svelte +1571 -0
  553. package/dist/trajectory/Trajectory.svelte.d.ts +78 -0
  554. package/dist/trajectory/TrajectoryError.svelte +128 -0
  555. package/dist/trajectory/TrajectoryError.svelte.d.ts +13 -0
  556. package/dist/trajectory/TrajectoryExportPane.svelte +358 -0
  557. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +17 -0
  558. package/dist/trajectory/TrajectoryInfoPane.svelte +314 -0
  559. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +17 -0
  560. package/dist/trajectory/constants.d.ts +6 -0
  561. package/dist/trajectory/constants.js +7 -0
  562. package/dist/trajectory/extract.d.ts +5 -0
  563. package/dist/trajectory/extract.js +162 -0
  564. package/dist/trajectory/format-detect.d.ts +10 -0
  565. package/dist/trajectory/format-detect.js +90 -0
  566. package/dist/trajectory/frame-reader.d.ts +17 -0
  567. package/dist/trajectory/frame-reader.js +299 -0
  568. package/dist/trajectory/helpers.d.ts +15 -0
  569. package/dist/trajectory/helpers.js +164 -0
  570. package/dist/trajectory/index.d.ts +63 -0
  571. package/dist/trajectory/index.js +126 -0
  572. package/dist/trajectory/parse/ase.d.ts +2 -0
  573. package/dist/trajectory/parse/ase.js +73 -0
  574. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  575. package/dist/trajectory/parse/hdf5.js +127 -0
  576. package/dist/trajectory/parse/index.d.ts +12 -0
  577. package/dist/trajectory/parse/index.js +306 -0
  578. package/dist/trajectory/parse/lammps.d.ts +5 -0
  579. package/dist/trajectory/parse/lammps.js +179 -0
  580. package/dist/trajectory/parse/vasp.d.ts +2 -0
  581. package/dist/trajectory/parse/vasp.js +87 -0
  582. package/dist/trajectory/parse/xyz.d.ts +26 -0
  583. package/dist/trajectory/parse/xyz.js +123 -0
  584. package/dist/trajectory/plotting.d.ts +28 -0
  585. package/dist/trajectory/plotting.js +423 -0
  586. package/dist/trajectory/types.d.ts +11 -0
  587. package/dist/trajectory/types.js +1 -0
  588. package/dist/utils.d.ts +7 -0
  589. package/dist/utils.js +47 -0
  590. package/dist/xrd/XrdPlot.svelte +616 -0
  591. package/dist/xrd/XrdPlot.svelte.d.ts +28 -0
  592. package/dist/xrd/broadening.d.ts +20 -0
  593. package/dist/xrd/broadening.js +97 -0
  594. package/dist/xrd/calc-xrd.d.ts +37 -0
  595. package/dist/xrd/calc-xrd.js +339 -0
  596. package/dist/xrd/index.d.ts +37 -0
  597. package/dist/xrd/index.js +4 -0
  598. package/dist/xrd/parse.d.ts +13 -0
  599. package/dist/xrd/parse.js +749 -0
  600. package/license +1 -1
  601. package/package.json +237 -1458
  602. package/readme.md +98 -171
  603. package/.vscode/launch.json +0 -13
  604. package/.vscodeignore +0 -7
  605. package/dist/assets/STLExporter-BpTH3YHE.js +0 -8
  606. package/dist/assets/browser-DdDecX_W.js +0 -1
  607. package/dist/assets/export-qgn-H9y6.js +0 -2
  608. package/dist/assets/main-DiKYzti2.css +0 -1
  609. package/dist/assets/moyo_wasm_bg-0ocwg7xY.wasm +0 -0
  610. package/dist/extension.js +0 -31293
  611. package/dist/src/lib/FilePicker.svelte +0 -360
  612. package/dist/src/lib/Icon.svelte +0 -41
  613. package/dist/src/lib/MillerIndexInput.svelte +0 -66
  614. package/dist/src/lib/api/mp.ts +0 -26
  615. package/dist/src/lib/api/optimade.ts +0 -204
  616. package/dist/src/lib/app.css +0 -247
  617. package/dist/src/lib/brillouin/BrillouinZone.svelte +0 -549
  618. package/dist/src/lib/brillouin/BrillouinZoneControls.svelte +0 -144
  619. package/dist/src/lib/brillouin/BrillouinZoneExportPane.svelte +0 -146
  620. package/dist/src/lib/brillouin/BrillouinZoneInfoPane.svelte +0 -146
  621. package/dist/src/lib/brillouin/BrillouinZoneScene.svelte +0 -476
  622. package/dist/src/lib/brillouin/BrillouinZoneTooltip.svelte +0 -92
  623. package/dist/src/lib/brillouin/compute.ts +0 -529
  624. package/dist/src/lib/brillouin/index.ts +0 -8
  625. package/dist/src/lib/brillouin/types.ts +0 -51
  626. package/dist/src/lib/chempot-diagram/ChemPotDiagram.svelte +0 -327
  627. package/dist/src/lib/chempot-diagram/ChemPotDiagram2D.svelte +0 -846
  628. package/dist/src/lib/chempot-diagram/ChemPotDiagram3D.svelte +0 -3193
  629. package/dist/src/lib/chempot-diagram/async-compute.svelte.ts +0 -94
  630. package/dist/src/lib/chempot-diagram/chempot-worker.ts +0 -11
  631. package/dist/src/lib/chempot-diagram/color.ts +0 -42
  632. package/dist/src/lib/chempot-diagram/compute.ts +0 -1014
  633. package/dist/src/lib/chempot-diagram/index.ts +0 -6
  634. package/dist/src/lib/chempot-diagram/pointer.ts +0 -56
  635. package/dist/src/lib/chempot-diagram/temperature.ts +0 -77
  636. package/dist/src/lib/chempot-diagram/types.ts +0 -130
  637. package/dist/src/lib/colors/index.ts +0 -249
  638. package/dist/src/lib/composition/BarChart.svelte +0 -297
  639. package/dist/src/lib/composition/BubbleChart.svelte +0 -218
  640. package/dist/src/lib/composition/Composition.svelte +0 -165
  641. package/dist/src/lib/composition/Formula.svelte +0 -268
  642. package/dist/src/lib/composition/FormulaFilter.svelte +0 -1257
  643. package/dist/src/lib/composition/PieChart.svelte +0 -323
  644. package/dist/src/lib/composition/format.ts +0 -155
  645. package/dist/src/lib/composition/index.ts +0 -37
  646. package/dist/src/lib/composition/parse.ts +0 -605
  647. package/dist/src/lib/constants.ts +0 -134
  648. package/dist/src/lib/controls.ts +0 -42
  649. package/dist/src/lib/convex-hull/ConvexHull.svelte +0 -157
  650. package/dist/src/lib/convex-hull/ConvexHull2D.svelte +0 -825
  651. package/dist/src/lib/convex-hull/ConvexHull3D.svelte +0 -1801
  652. package/dist/src/lib/convex-hull/ConvexHull4D.svelte +0 -1398
  653. package/dist/src/lib/convex-hull/ConvexHullControls.svelte +0 -535
  654. package/dist/src/lib/convex-hull/ConvexHullInfoPane.svelte +0 -125
  655. package/dist/src/lib/convex-hull/ConvexHullStats.svelte +0 -929
  656. package/dist/src/lib/convex-hull/ConvexHullTooltip.svelte +0 -131
  657. package/dist/src/lib/convex-hull/GasPressureControls.svelte +0 -247
  658. package/dist/src/lib/convex-hull/StructurePopup.svelte +0 -151
  659. package/dist/src/lib/convex-hull/TemperatureSlider.svelte +0 -140
  660. package/dist/src/lib/convex-hull/barycentric-coords.ts +0 -246
  661. package/dist/src/lib/convex-hull/demo-temperature.ts +0 -63
  662. package/dist/src/lib/convex-hull/gas-thermodynamics.ts +0 -405
  663. package/dist/src/lib/convex-hull/helpers.ts +0 -932
  664. package/dist/src/lib/convex-hull/index.ts +0 -202
  665. package/dist/src/lib/convex-hull/thermodynamics.ts +0 -2192
  666. package/dist/src/lib/convex-hull/types.ts +0 -267
  667. package/dist/src/lib/coordination/CoordinationBarPlot.svelte +0 -311
  668. package/dist/src/lib/coordination/calc-coordination.ts +0 -93
  669. package/dist/src/lib/coordination/index.ts +0 -9
  670. package/dist/src/lib/effects.svelte.ts +0 -48
  671. package/dist/src/lib/element/ElementHeading.svelte +0 -26
  672. package/dist/src/lib/element/ElementPhoto.svelte +0 -57
  673. package/dist/src/lib/element/ElementStats.svelte +0 -80
  674. package/dist/src/lib/element/ElementTile.svelte +0 -484
  675. package/dist/src/lib/element/data.ts +0 -14
  676. package/dist/src/lib/element/index.ts +0 -8
  677. package/dist/src/lib/element/types.ts +0 -62
  678. package/dist/src/lib/feedback/ClickFeedback.svelte +0 -58
  679. package/dist/src/lib/feedback/DragOverlay.svelte +0 -42
  680. package/dist/src/lib/feedback/index.ts +0 -4
  681. package/dist/src/lib/fermi-surface/FermiSlice.svelte +0 -189
  682. package/dist/src/lib/fermi-surface/FermiSurface.svelte +0 -600
  683. package/dist/src/lib/fermi-surface/FermiSurfaceControls.svelte +0 -448
  684. package/dist/src/lib/fermi-surface/FermiSurfaceScene.svelte +0 -794
  685. package/dist/src/lib/fermi-surface/FermiSurfaceTooltip.svelte +0 -111
  686. package/dist/src/lib/fermi-surface/compute.ts +0 -728
  687. package/dist/src/lib/fermi-surface/constants.ts +0 -32
  688. package/dist/src/lib/fermi-surface/export.ts +0 -64
  689. package/dist/src/lib/fermi-surface/index.ts +0 -14
  690. package/dist/src/lib/fermi-surface/marching-cubes.ts +0 -3
  691. package/dist/src/lib/fermi-surface/parse.ts +0 -574
  692. package/dist/src/lib/fermi-surface/symmetry.ts +0 -56
  693. package/dist/src/lib/fermi-surface/types.ts +0 -159
  694. package/dist/src/lib/heatmap-matrix/HeatmapMatrix.svelte +0 -1545
  695. package/dist/src/lib/heatmap-matrix/HeatmapMatrixControls.svelte +0 -225
  696. package/dist/src/lib/heatmap-matrix/index.ts +0 -167
  697. package/dist/src/lib/heatmap-matrix/shared.ts +0 -7
  698. package/dist/src/lib/icons.ts +0 -650
  699. package/dist/src/lib/index.ts +0 -61
  700. package/dist/src/lib/io/decompress.ts +0 -92
  701. package/dist/src/lib/io/export.ts +0 -385
  702. package/dist/src/lib/io/fetch.ts +0 -46
  703. package/dist/src/lib/io/file-drop.ts +0 -51
  704. package/dist/src/lib/io/index.ts +0 -7
  705. package/dist/src/lib/io/is-binary.ts +0 -24
  706. package/dist/src/lib/io/types.ts +0 -8
  707. package/dist/src/lib/io/url-drop.ts +0 -141
  708. package/dist/src/lib/isosurface/Isosurface.svelte +0 -285
  709. package/dist/src/lib/isosurface/IsosurfaceControls.svelte +0 -277
  710. package/dist/src/lib/isosurface/index.ts +0 -7
  711. package/dist/src/lib/isosurface/parse.ts +0 -656
  712. package/dist/src/lib/isosurface/slice.ts +0 -175
  713. package/dist/src/lib/isosurface/types.ts +0 -309
  714. package/dist/src/lib/labels.ts +0 -320
  715. package/dist/src/lib/layout/FullscreenToggle.svelte +0 -50
  716. package/dist/src/lib/layout/InfoCard.svelte +0 -120
  717. package/dist/src/lib/layout/InfoTag.svelte +0 -185
  718. package/dist/src/lib/layout/PropertyFilter.svelte +0 -246
  719. package/dist/src/lib/layout/SettingsSection.svelte +0 -148
  720. package/dist/src/lib/layout/SubpageGrid.svelte +0 -82
  721. package/dist/src/lib/layout/fullscreen.ts +0 -65
  722. package/dist/src/lib/layout/index.ts +0 -11
  723. package/dist/src/lib/layout/json-tree/JsonNode.svelte +0 -548
  724. package/dist/src/lib/layout/json-tree/JsonTree.svelte +0 -1230
  725. package/dist/src/lib/layout/json-tree/index.ts +0 -3
  726. package/dist/src/lib/layout/json-tree/types.ts +0 -126
  727. package/dist/src/lib/layout/json-tree/utils.ts +0 -682
  728. package/dist/src/lib/marching-cubes.ts +0 -614
  729. package/dist/src/lib/math.ts +0 -1081
  730. package/dist/src/lib/overlays/ContextMenu.svelte +0 -162
  731. package/dist/src/lib/overlays/CopyButton.svelte +0 -45
  732. package/dist/src/lib/overlays/DragControlTab.svelte +0 -98
  733. package/dist/src/lib/overlays/DraggablePane.svelte +0 -487
  734. package/dist/src/lib/overlays/InfoPaneCards.svelte +0 -149
  735. package/dist/src/lib/overlays/index.ts +0 -3
  736. package/dist/src/lib/periodic-table/PeriodicTable.svelte +0 -469
  737. package/dist/src/lib/periodic-table/PeriodicTableControls.svelte +0 -557
  738. package/dist/src/lib/periodic-table/PropertySelect.svelte +0 -37
  739. package/dist/src/lib/periodic-table/index.ts +0 -12
  740. package/dist/src/lib/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +0 -1086
  741. package/dist/src/lib/phase-diagram/PhaseDiagramControls.svelte +0 -444
  742. package/dist/src/lib/phase-diagram/PhaseDiagramEditorPane.svelte +0 -126
  743. package/dist/src/lib/phase-diagram/PhaseDiagramExportPane.svelte +0 -184
  744. package/dist/src/lib/phase-diagram/PhaseDiagramTooltip.svelte +0 -391
  745. package/dist/src/lib/phase-diagram/TdbInfoPanel.svelte +0 -203
  746. package/dist/src/lib/phase-diagram/build-diagram.ts +0 -186
  747. package/dist/src/lib/phase-diagram/colors.ts +0 -58
  748. package/dist/src/lib/phase-diagram/diagram-input.ts +0 -40
  749. package/dist/src/lib/phase-diagram/index.ts +0 -13
  750. package/dist/src/lib/phase-diagram/parse.ts +0 -348
  751. package/dist/src/lib/phase-diagram/svg-to-diagram.ts +0 -1023
  752. package/dist/src/lib/phase-diagram/types.ts +0 -144
  753. package/dist/src/lib/phase-diagram/utils.ts +0 -775
  754. package/dist/src/lib/plot/AxisLabel.svelte +0 -51
  755. package/dist/src/lib/plot/BarPlot.svelte +0 -2113
  756. package/dist/src/lib/plot/BarPlotControls.svelte +0 -66
  757. package/dist/src/lib/plot/BinnedScatterPlot.svelte +0 -1114
  758. package/dist/src/lib/plot/ColorBar.svelte +0 -721
  759. package/dist/src/lib/plot/ColorScaleSelect.svelte +0 -54
  760. package/dist/src/lib/plot/ElementScatter.svelte +0 -63
  761. package/dist/src/lib/plot/FillArea.svelte +0 -223
  762. package/dist/src/lib/plot/Histogram.svelte +0 -1558
  763. package/dist/src/lib/plot/HistogramControls.svelte +0 -212
  764. package/dist/src/lib/plot/InteractiveAxisLabel.svelte +0 -96
  765. package/dist/src/lib/plot/Line.svelte +0 -84
  766. package/dist/src/lib/plot/PlotAxis.svelte +0 -169
  767. package/dist/src/lib/plot/PlotControls.svelte +0 -537
  768. package/dist/src/lib/plot/PlotLegend.svelte +0 -569
  769. package/dist/src/lib/plot/PlotTooltip.svelte +0 -67
  770. package/dist/src/lib/plot/PortalSelect.svelte +0 -253
  771. package/dist/src/lib/plot/ReferenceLine.svelte +0 -204
  772. package/dist/src/lib/plot/ReferenceLine3D.svelte +0 -156
  773. package/dist/src/lib/plot/ReferencePlane.svelte +0 -175
  774. package/dist/src/lib/plot/ScatterPlot.svelte +0 -2778
  775. package/dist/src/lib/plot/ScatterPlot3D.svelte +0 -529
  776. package/dist/src/lib/plot/ScatterPlot3DControls.svelte +0 -437
  777. package/dist/src/lib/plot/ScatterPlot3DScene.svelte +0 -912
  778. package/dist/src/lib/plot/ScatterPlotControls.svelte +0 -306
  779. package/dist/src/lib/plot/ScatterPoint.svelte +0 -182
  780. package/dist/src/lib/plot/SpacegroupBarPlot.svelte +0 -293
  781. package/dist/src/lib/plot/Surface3D.svelte +0 -197
  782. package/dist/src/lib/plot/ZeroLines.svelte +0 -97
  783. package/dist/src/lib/plot/ZoomRect.svelte +0 -23
  784. package/dist/src/lib/plot/adaptive-density.ts +0 -316
  785. package/dist/src/lib/plot/auto-place.ts +0 -184
  786. package/dist/src/lib/plot/axis-utils.ts +0 -122
  787. package/dist/src/lib/plot/binned-scatter-types.ts +0 -83
  788. package/dist/src/lib/plot/data-cleaning.ts +0 -1069
  789. package/dist/src/lib/plot/data-transform.ts +0 -69
  790. package/dist/src/lib/plot/defaults.ts +0 -9
  791. package/dist/src/lib/plot/fill-utils.ts +0 -494
  792. package/dist/src/lib/plot/hover-lock.svelte.ts +0 -60
  793. package/dist/src/lib/plot/index.ts +0 -53
  794. package/dist/src/lib/plot/interactions.ts +0 -119
  795. package/dist/src/lib/plot/layout.ts +0 -425
  796. package/dist/src/lib/plot/reference-line.ts +0 -426
  797. package/dist/src/lib/plot/scales.ts +0 -654
  798. package/dist/src/lib/plot/svg.ts +0 -23
  799. package/dist/src/lib/plot/types.ts +0 -1144
  800. package/dist/src/lib/plot/utils/label-placement.ts +0 -541
  801. package/dist/src/lib/plot/utils/series-visibility.ts +0 -140
  802. package/dist/src/lib/plot/utils.ts +0 -11
  803. package/dist/src/lib/rdf/RdfPlot.svelte +0 -247
  804. package/dist/src/lib/rdf/calc-rdf.ts +0 -167
  805. package/dist/src/lib/rdf/index.ts +0 -27
  806. package/dist/src/lib/sanitize.ts +0 -126
  807. package/dist/src/lib/settings.ts +0 -1479
  808. package/dist/src/lib/spectral/Bands.svelte +0 -1040
  809. package/dist/src/lib/spectral/BandsAndDos.svelte +0 -134
  810. package/dist/src/lib/spectral/BrillouinBandsDos.svelte +0 -252
  811. package/dist/src/lib/spectral/Dos.svelte +0 -697
  812. package/dist/src/lib/spectral/helpers.ts +0 -1381
  813. package/dist/src/lib/spectral/index.ts +0 -8
  814. package/dist/src/lib/spectral/types.ts +0 -112
  815. package/dist/src/lib/state.svelte.ts +0 -64
  816. package/dist/src/lib/structure/Arrow.svelte +0 -72
  817. package/dist/src/lib/structure/AtomLegend.svelte +0 -815
  818. package/dist/src/lib/structure/Bond.svelte +0 -140
  819. package/dist/src/lib/structure/CanvasTooltip.svelte +0 -33
  820. package/dist/src/lib/structure/CellSelect.svelte +0 -349
  821. package/dist/src/lib/structure/Cylinder.svelte +0 -45
  822. package/dist/src/lib/structure/Lattice.svelte +0 -196
  823. package/dist/src/lib/structure/Structure.svelte +0 -2248
  824. package/dist/src/lib/structure/StructureControls.svelte +0 -1273
  825. package/dist/src/lib/structure/StructureExportPane.svelte +0 -252
  826. package/dist/src/lib/structure/StructureInfoPane.svelte +0 -737
  827. package/dist/src/lib/structure/StructureScene.svelte +0 -2255
  828. package/dist/src/lib/structure/atom-properties.ts +0 -316
  829. package/dist/src/lib/structure/bond-order-perception.ts +0 -447
  830. package/dist/src/lib/structure/bonding.ts +0 -944
  831. package/dist/src/lib/structure/export.ts +0 -861
  832. package/dist/src/lib/structure/index.ts +0 -291
  833. package/dist/src/lib/structure/label-placement.ts +0 -130
  834. package/dist/src/lib/structure/measure.ts +0 -45
  835. package/dist/src/lib/structure/parse.ts +0 -1705
  836. package/dist/src/lib/structure/partial-occupancy.ts +0 -183
  837. package/dist/src/lib/structure/pbc.ts +0 -164
  838. package/dist/src/lib/structure/supercell.ts +0 -226
  839. package/dist/src/lib/structure/validation.ts +0 -11
  840. package/dist/src/lib/symmetry/SymmetryStats.svelte +0 -226
  841. package/dist/src/lib/symmetry/WyckoffTable.svelte +0 -120
  842. package/dist/src/lib/symmetry/cell-transform.ts +0 -118
  843. package/dist/src/lib/symmetry/index.ts +0 -348
  844. package/dist/src/lib/symmetry/spacegroups.ts +0 -404
  845. package/dist/src/lib/table/HeatmapTable.svelte +0 -1833
  846. package/dist/src/lib/table/ToggleMenu.svelte +0 -385
  847. package/dist/src/lib/table/index.ts +0 -139
  848. package/dist/src/lib/theme/ThemeControl.svelte +0 -53
  849. package/dist/src/lib/theme/index.ts +0 -107
  850. package/dist/src/lib/time.ts +0 -71
  851. package/dist/src/lib/tooltip/TooltipContent.svelte +0 -58
  852. package/dist/src/lib/tooltip/index.ts +0 -2
  853. package/dist/src/lib/tooltip/types.ts +0 -13
  854. package/dist/src/lib/trajectory/Trajectory.svelte +0 -1545
  855. package/dist/src/lib/trajectory/TrajectoryError.svelte +0 -128
  856. package/dist/src/lib/trajectory/TrajectoryExportPane.svelte +0 -357
  857. package/dist/src/lib/trajectory/TrajectoryInfoPane.svelte +0 -313
  858. package/dist/src/lib/trajectory/constants.ts +0 -7
  859. package/dist/src/lib/trajectory/extract.ts +0 -196
  860. package/dist/src/lib/trajectory/format-detect.ts +0 -96
  861. package/dist/src/lib/trajectory/frame-reader.ts +0 -456
  862. package/dist/src/lib/trajectory/helpers.ts +0 -217
  863. package/dist/src/lib/trajectory/index.ts +0 -218
  864. package/dist/src/lib/trajectory/parse/ase.ts +0 -109
  865. package/dist/src/lib/trajectory/parse/hdf5.ts +0 -173
  866. package/dist/src/lib/trajectory/parse/index.ts +0 -411
  867. package/dist/src/lib/trajectory/parse/lammps.ts +0 -215
  868. package/dist/src/lib/trajectory/parse/vasp.ts +0 -102
  869. package/dist/src/lib/trajectory/parse/xyz.ts +0 -143
  870. package/dist/src/lib/trajectory/plotting.ts +0 -599
  871. package/dist/src/lib/trajectory/types.ts +0 -13
  872. package/dist/src/lib/utils.ts +0 -56
  873. package/dist/src/lib/xrd/XrdPlot.svelte +0 -615
  874. package/dist/src/lib/xrd/broadening.ts +0 -130
  875. package/dist/src/lib/xrd/calc-xrd.ts +0 -397
  876. package/dist/src/lib/xrd/index.ts +0 -38
  877. package/dist/src/lib/xrd/parse.ts +0 -858
  878. package/dist/webview.js +0 -29421
  879. package/icon.png +0 -0
  880. package/matterviz-0.3.2.vsix +0 -0
  881. package/matterviz-0.3.4.vsix +0 -0
  882. package/matterviz-0.3.5.vsix +0 -0
  883. package/scripts/sync-config.ts +0 -101
  884. package/src/declarations.d.ts +0 -2
  885. package/src/extension.ts +0 -972
  886. package/src/node-io.ts +0 -65
  887. package/src/types.ts +0 -17
  888. package/src/webview/JsonBrowser.svelte +0 -1079
  889. package/src/webview/PlotPanel.svelte +0 -346
  890. package/src/webview/detect.ts +0 -444
  891. package/src/webview/main.ts +0 -764
  892. package/src/webview/plot-utils.ts +0 -250
  893. package/test-fixtures/all-viz-types.json.gz +0 -0
  894. package/test-fixtures/plot-demo-data.json.gz +0 -0
  895. package/tests/detect.test.ts +0 -604
  896. package/tests/extension.test.ts +0 -2041
  897. package/tests/node-io.test.ts +0 -39
  898. package/tests/plot-utils.test.ts +0 -302
  899. package/tests/vite-plugin-json-gz.test.ts +0 -114
  900. package/tests/vscode-mock.ts +0 -18
  901. package/tests/webview.test.ts +0 -231
  902. package/tsconfig.json +0 -20
  903. package/vite-plugin-json-gz.ts +0 -29
  904. package/vite.config.ts +0 -34
  905. package/vite.extension.config.ts +0 -34
  906. /package/dist/{src/lib/EmptyState.svelte → EmptyState.svelte} +0 -0
  907. /package/dist/{src/lib/chempot-diagram → chempot-diagram}/ChemPotScene3D.svelte +0 -0
  908. /package/dist/{src/lib/colors → colors}/alloy-colors.json +0 -0
  909. /package/dist/{src/lib/colors → colors}/dark-mode-colors.json +0 -0
  910. /package/dist/{src/lib/colors → colors}/jmol-colors.json +0 -0
  911. /package/dist/{src/lib/colors → colors}/muted-colors.json +0 -0
  912. /package/dist/{src/lib/colors → colors}/pastel-colors.json +0 -0
  913. /package/dist/{src/lib/colors → colors}/vesta-colors.json +0 -0
  914. /package/dist/{src/lib/element → element}/BohrAtom.svelte +0 -0
  915. /package/dist/{src/lib/element → element}/Nucleus.svelte +0 -0
  916. /package/dist/{src/lib/element → element}/data.json +0 -0
  917. /package/dist/{src/lib/element → element}/data.json.gz +0 -0
  918. /package/dist/{src/lib/element → element}/data.json.gz.d.ts +0 -0
  919. /package/dist/{src/lib/element → element}/data.schema.json +0 -0
  920. /package/dist/{src/lib/element-image-urls.json → element-image-urls.json} +0 -0
  921. /package/dist/{src/lib/feedback → feedback}/Spinner.svelte +0 -0
  922. /package/dist/{src/lib/feedback → feedback}/StatusMessage.svelte +0 -0
  923. /package/dist/{src/lib/layout → layout}/json-tree/JsonValue.svelte +0 -0
  924. /package/dist/{src/lib/periodic-table → periodic-table}/TableInset.svelte +0 -0
  925. /package/dist/{src/lib/theme → theme}/themes.mjs +0 -0
  926. /package/dist/{src/lib/xrd → xrd}/atomic_scattering_params.json +0 -0
@@ -0,0 +1,2357 @@
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 { format_value } from '../../labels'
7
+ import { sanitize_html } from '../../sanitize'
8
+ import { FullscreenToggle, set_fullscreen_bg } from '../../layout'
9
+ import type { Point2D, Vec2 } from '../../math'
10
+ import type {
11
+ AxisLoadError,
12
+ BasePlotProps,
13
+ ControlsConfig,
14
+ DataLoaderFn,
15
+ DataSeries,
16
+ ErrorBand,
17
+ FillHandlerEvent,
18
+ FillRegion,
19
+ HoverConfig,
20
+ InitialRanges,
21
+ InternalPoint,
22
+ LabelPlacementConfig,
23
+ LegendConfig,
24
+ PanConfig,
25
+ PlotConfig,
26
+ Point,
27
+ RefLine,
28
+ RefLineEvent,
29
+ ScaleType,
30
+ ScatterHandlerEvent,
31
+ ScatterHandlerProps,
32
+ StyleOverrides,
33
+ UserContentProps,
34
+ } from '..'
35
+ import {
36
+ ColorBar,
37
+ compute_element_placement,
38
+ FillArea,
39
+ get_tick_label,
40
+ Line,
41
+ PlotAxis,
42
+ PlotLegend,
43
+ PlotTooltip,
44
+ ReferenceLine,
45
+ ScatterPlotControls,
46
+ ScatterPoint,
47
+ ZeroLines,
48
+ ZoomRect,
49
+ } from '..'
50
+ import {
51
+ build_obstacles_norm,
52
+ has_explicit_position,
53
+ measured_footprint,
54
+ place_decorations,
55
+ } from '../core/auto-place'
56
+ import type { AxisChangeState } from '../core/axis-utils'
57
+ import { AXIS_DEFAULTS, create_axis_loader } from '../core/axis-utils'
58
+ import { get_series_color, get_series_symbol } from '../core/data-transform'
59
+ import {
60
+ create_dimension_tracker,
61
+ create_hover_lock,
62
+ } from '../core/hover-lock.svelte'
63
+ import {
64
+ DEFAULT_MARKERS,
65
+ get_scale_type_name,
66
+ is_time_scale,
67
+ } from '../core/types'
68
+ import { compute_label_positions } from '../core/utils/label-placement'
69
+ import { create_legend_visibility } from '../core/utils/series-visibility'
70
+ import { DEFAULTS } from '../../settings'
71
+ import { extent } from 'd3-array'
72
+ import { scaleTime } from 'd3-scale'
73
+ import type { ComponentProps, Snippet } from 'svelte'
74
+ import { onDestroy, untrack } from 'svelte'
75
+ import type { HTMLAttributes } from 'svelte/elements'
76
+ import { Tween, type TweenOptions } from 'svelte/motion'
77
+ import { SvelteSet } from 'svelte/reactivity'
78
+ import type { Pt } from '../core/fill-utils'
79
+ import {
80
+ compute_fill_segments,
81
+ convert_error_band_to_fill_region,
82
+ generate_fill_path,
83
+ } from '../core/fill-utils'
84
+ import {
85
+ expand_range_if_needed,
86
+ get_relative_coords,
87
+ MIN_TOUCH_DISTANCE_PIXELS,
88
+ normalize_y2_sync,
89
+ pan_range_by_pixels,
90
+ PINCH_ZOOM_THRESHOLD,
91
+ remove_drag_listeners,
92
+ sorted_range,
93
+ sync_y2_range,
94
+ to_epoch_num,
95
+ zoom_range_by_factor,
96
+ } from '../core/interactions'
97
+ import type { Rect, Sides } from '../core/layout'
98
+ import {
99
+ calc_auto_padding,
100
+ filter_padding,
101
+ LABEL_GAP_DEFAULT,
102
+ y2_axis_label_x,
103
+ measure_full_footprint,
104
+ measure_max_tick_width,
105
+ sample_series_obstacle_points,
106
+ } from '../core/layout'
107
+ import type { IndexedRefLine } from '../core/reference-line'
108
+ import { group_ref_lines_by_z, index_ref_lines } from '../core/reference-line'
109
+ import {
110
+ create_color_scale,
111
+ create_scale,
112
+ create_size_scale,
113
+ generate_ticks,
114
+ get_nice_data_range,
115
+ } from '../core/scales'
116
+ import { resolve_line_tween, unique_id } from '../core/utils'
117
+ import {
118
+ build_legend_data,
119
+ filter_series_to_ranges,
120
+ pick_tooltip_bg,
121
+ } from './scatter-data'
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?: TweenOptions<Point2D>
210
+ responsive?: boolean // Allow colorbar to reposition if density changes (default: false)
211
+ axis_clearance?: number // Min distance kept from plot edges/axes (default: 15)
212
+ })
213
+ | null
214
+ label_placement_config?: Partial<LabelPlacementConfig>
215
+ hover_config?: Partial<HoverConfig>
216
+ legend?: LegendConfig | null
217
+ point_tween?: TweenOptions<Point2D>
218
+ line_tween?: TweenOptions<string>
219
+ point_events?: Record<
220
+ string,
221
+ (payload: { point: InternalPoint<Metadata>; event: Event }) => void
222
+ >
223
+ on_point_click?: (data: ScatterHandlerEvent<Metadata>) => void
224
+ on_point_hover?: (data: ScatterHandlerEvent<Metadata> | null) => void
225
+ fill_regions?: FillRegion[] // Bindable for legend toggle support
226
+ error_bands?: ErrorBand[]
227
+ on_fill_click?: (event: FillHandlerEvent) => void
228
+ on_fill_hover?: (event: FillHandlerEvent | null) => void
229
+ ref_lines?: RefLine[] // Bindable for legend toggle support
230
+ on_ref_line_click?: (event: RefLineEvent) => void
231
+ on_ref_line_hover?: (event: RefLineEvent | null) => void
232
+ selected_series_idx?: number
233
+ wrapper?: HTMLDivElement
234
+ // Interactive axis props
235
+ data_loader?: DataLoaderFn<Metadata>
236
+ on_axis_change?: (
237
+ axis: `x` | `x2` | `y` | `y2`,
238
+ key: string,
239
+ new_series: DataSeries<Metadata>[],
240
+ ) => void
241
+ on_error?: (error: AxisLoadError) => void
242
+ pan?: PanConfig
243
+ } = $props()
244
+
245
+ // Merged axis/display values with defaults (use $derived to avoid breaking $bindable)
246
+ const final_x_axis = $derived({
247
+ ...AXIS_DEFAULTS,
248
+ label_shift: { x: 0, y: -40 }, // x-axis needs different label position
249
+ ...x_axis,
250
+ })
251
+ const final_y_axis = $derived({ ...AXIS_DEFAULTS, ...y_axis })
252
+ const final_x2_axis = $derived({
253
+ ...AXIS_DEFAULTS,
254
+ label_shift: { x: 0, y: 40 }, // x2-axis label above top edge
255
+ ...x2_axis,
256
+ })
257
+ const final_y2_axis = $derived({ ...AXIS_DEFAULTS, ...y2_axis })
258
+ // Cache time-axis check — used in ~10 places for scale/tick/tooltip logic
259
+ let is_time_x = $derived(
260
+ is_time_scale(final_x_axis.scale_type, final_x_axis.format),
261
+ )
262
+ let is_time_x2 = $derived(
263
+ is_time_scale(final_x2_axis.scale_type, final_x2_axis.format),
264
+ )
265
+ const final_display = $derived({ ...DEFAULTS.scatter.display, ...display })
266
+ // Local state for styles (initialized from prop, owned by this component for controls)
267
+ // Using $state because styles has bindings in ScatterPlotControls
268
+ // untrack() explicitly captures initial prop value (intentional - props provide initial config)
269
+ let styles = $state(untrack(() => ({
270
+ show_points: DEFAULTS.scatter.show_points,
271
+ show_lines: DEFAULTS.scatter.show_lines,
272
+ point: { ...DEFAULTS.scatter.point, ...styles_init?.point },
273
+ line: { ...DEFAULTS.scatter.line, ...styles_init?.line },
274
+ ...styles_init,
275
+ })))
276
+ let controls = $derived({ show: true, open: false, ...controls_init })
277
+
278
+ let [width, height] = $state([0, 0])
279
+ let svg_element: SVGElement | null = $state(null) // Bind the SVG element
280
+ let svg_bounding_box: DOMRect | null = $state(null) // Store SVG bounds during drag
281
+
282
+ // Track which specific control properties user has modified
283
+ let touched = new SvelteSet<string>()
284
+
285
+ // Unique component ID to avoid clipPath conflicts between multiple instances
286
+ let component_id = $state(unique_id(`scatter`))
287
+ let clip_path_id = $derived(`plot-area-clip-${component_id}`)
288
+
289
+ // Assign stable IDs to series for keying
290
+ let series_with_ids = $derived(
291
+ series.map((srs: DataSeries<Metadata>, idx: number) => {
292
+ if (!srs || typeof srs !== `object`) return srs
293
+ // Use series.id if provided, otherwise fall back to index
294
+ // prevents re-mounts when series are reordered if stable IDs are provided
295
+ return { ...srs, _id: srs.id ?? idx }
296
+ }),
297
+ )
298
+
299
+ // State for rectangle zoom selection
300
+ let drag_start_coords = $state<Point2D | null>(null)
301
+ let drag_current_coords = $state<Point2D | null>(null)
302
+
303
+ // Zoom/pan state - track both initial (data-driven) and current (after pan/zoom) ranges
304
+ let initial_x_range = $state<[number, number]>([0, 1])
305
+ let initial_x2_range = $state<[number, number]>([0, 1])
306
+ let initial_y_range = $state<[number, number]>([0, 1])
307
+ let initial_y2_range = $state<[number, number]>([0, 1])
308
+ let zoom_x_range = $state<[number, number]>([0, 1])
309
+ let zoom_x2_range = $state<[number, number]>([0, 1])
310
+ let zoom_y_range = $state<[number, number]>([0, 1])
311
+ let zoom_y2_range = $state<[number, number]>([0, 1])
312
+ const legend_vis = create_legend_visibility(() => series, (next) => (series = next))
313
+
314
+ // Y2 axis sync configuration
315
+ let y2_sync_config = $derived(normalize_y2_sync(y2_axis?.sync))
316
+ // Track previous sync mode to detect changes (updated in $effect.pre to avoid race conditions)
317
+ let prev_sync_mode = $state<string>(`none`)
318
+
319
+ // Helper to compute synced y2 range or return fallback when sync disabled
320
+ const get_synced_y2 = (y1_range: Vec2, fallback: Vec2): Vec2 =>
321
+ y2_sync_config.mode !== `none`
322
+ ? sync_y2_range(y1_range, initial_y2_range, y2_sync_config)
323
+ : fallback
324
+
325
+ // Effect to update y2 range when sync mode changes - use $effect.pre to capture
326
+ // mode change before the main range-update effect runs, ensuring sync is applied
327
+ // immediately when toggled (not delayed until next data change)
328
+ $effect.pre(() => {
329
+ const mode = y2_sync_config.mode
330
+ if (mode !== prev_sync_mode) {
331
+ // When sync mode becomes enabled (or changes), apply sync immediately
332
+ if (mode !== `none`) {
333
+ zoom_y2_range = sync_y2_range(zoom_y_range, initial_y2_range, y2_sync_config)
334
+ } else {
335
+ // When switching to independent mode, reset Y2 to its data range
336
+ zoom_y2_range = [...initial_y2_range] as [number, number]
337
+ }
338
+ prev_sync_mode = mode
339
+ }
340
+ })
341
+
342
+ // Pan state
343
+ let is_focused = $state(false)
344
+ let shift_held = $state(false)
345
+ let pan_drag_state = $state<
346
+ InitialRanges & { start: { x: number; y: number } } | null
347
+ >(null)
348
+ let touch_state = $state<
349
+ InitialRanges & { start_touches: { x: number; y: number }[] } | null
350
+ >(null)
351
+
352
+ // Fill region hover state
353
+ let hovered_fill_key = $state<string | null>(null)
354
+
355
+ // Reference line hover state
356
+ let hovered_ref_line_idx = $state<number | null>(null)
357
+
358
+ // Interactive axis loading state
359
+ let axis_loading = $state<`x` | `x2` | `y` | `y2` | null>(null)
360
+
361
+ // State to hold the calculated label positions after simulation
362
+ let label_positions = $state<Record<string, Point2D>>({})
363
+
364
+ // State for legend dragging
365
+ let legend_is_dragging = $state(false)
366
+ let legend_drag_offset = $state<{ x: number; y: number }>({ x: 0, y: 0 })
367
+ let legend_manual_position = $state<{ x: number; y: number } | null>(null)
368
+ let hovered_legend_series_idx = $state<number | null>(null)
369
+
370
+ // State for legend/colorbar placement stability
371
+ let legend_element = $state<HTMLDivElement | undefined>()
372
+ let colorbar_element = $state<HTMLDivElement | undefined>()
373
+ const legend_hover = create_hover_lock()
374
+ const colorbar_hover = create_hover_lock()
375
+ const dim_tracker = create_dimension_tracker()
376
+ let has_initial_legend_placement = $state(false)
377
+ let has_initial_colorbar_placement = $state(false)
378
+
379
+ // Clear pending hover lock timeouts on unmount
380
+ $effect(() => () => {
381
+ legend_hover.cleanup()
382
+ colorbar_hover.cleanup()
383
+ })
384
+
385
+ // Module-level constants to avoid repeated allocations
386
+ // Create and categorize points in a single pass (instead of 3 separate iterations)
387
+ type SimplePoint = { x: number; y: number }
388
+ let points_by_axis = $derived.by(() => {
389
+ const all: SimplePoint[] = []
390
+ const y1: SimplePoint[] = []
391
+ const y2: SimplePoint[] = []
392
+ const x2: SimplePoint[] = []
393
+
394
+ for (const srs of series_with_ids) {
395
+ if (!srs) continue
396
+ const { x: xs, y: ys, visible = true, y_axis: series_y_axis = `y1`, x_axis: x_ax = `x1` } =
397
+ srs as DataSeries
398
+ for (let idx = 0; idx < xs.length; idx++) {
399
+ const point = { x: xs[idx], y: ys[idx] }
400
+ all.push(point)
401
+ if (visible) {
402
+ if (series_y_axis === `y2`) y2.push(point)
403
+ else y1.push(point)
404
+ if (x_ax === `x2`) x2.push(point)
405
+ }
406
+ }
407
+ }
408
+ return { all, y1, y2, x2 }
409
+ })
410
+
411
+ let all_points = $derived(points_by_axis.all)
412
+ let y1_points = $derived(points_by_axis.y1)
413
+ let y2_points = $derived(points_by_axis.y2)
414
+ let x2_points = $derived(points_by_axis.x2)
415
+
416
+ // Layout: tick-label padding (decoration reservations are added in `pad` below)
417
+ const default_padding = { t: 5, b: 50, l: 50, r: 20 }
418
+ let base_pad = $state(untrack(() => filter_padding(padding, default_padding)))
419
+
420
+ // Update padding when format or ticks change
421
+ $effect(() => {
422
+ const new_pad = width && height &&
423
+ (y_tick_values.length > 0 || y2_tick_values.length > 0 || x2_tick_values.length > 0)
424
+ ? calc_auto_padding({
425
+ padding,
426
+ default_padding,
427
+ x2_axis: { ...final_x2_axis, tick_values: x2_tick_values },
428
+ y_axis: { ...final_y_axis, tick_values: y_tick_values },
429
+ y2_axis: { ...final_y2_axis, tick_values: y2_tick_values },
430
+ })
431
+ : filter_padding(padding, default_padding)
432
+
433
+ if (
434
+ base_pad.t !== new_pad.t ||
435
+ base_pad.b !== new_pad.b ||
436
+ base_pad.l !== new_pad.l ||
437
+ base_pad.r !== new_pad.r
438
+ ) base_pad = new_pad
439
+ })
440
+
441
+ // === Auto-move legend/colorbar outside the plot when interior overlap is unavoidable ===
442
+ // (shared logic lives in auto-place.ts so every 2D plot reuses it)
443
+ // ColorBar's orientation prop defaults to horizontal, so treat unset as horizontal too
444
+ const colorbar_is_horizontal = $derived((color_bar?.orientation ?? `horizontal`) === `horizontal`)
445
+ const colorbar_footprint = $derived(
446
+ colorbar_element?.offsetWidth && colorbar_element?.offsetHeight
447
+ ? measure_full_footprint(colorbar_element)
448
+ : colorbar_is_horizontal
449
+ ? { width: 220, height: 56 }
450
+ : { width: 56, height: 100 },
451
+ )
452
+ const legend_footprint = $derived(measured_footprint(legend_element, { width: 120, height: 80 }))
453
+ const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
454
+
455
+ // Plot-specific obstacle field: series points/lines normalized to [0,1] (y=0 at top)
456
+ const obstacles_norm = $derived.by(() => {
457
+ if (!width || !height || !filtered_series) return []
458
+ const base_w = width - base_pad.l - base_pad.r
459
+ const base_h = height - base_pad.t - base_pad.b
460
+ if (base_w <= 0 || base_h <= 0) return []
461
+ const norm_x = is_time_x
462
+ ? scaleTime().domain([new Date(x_min), new Date(x_max)]).range([0, 1])
463
+ : create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [0, 1])
464
+ const norm_y = create_scale(final_y_axis.scale_type ?? `linear`, [y_min, y_max], [0, 1])
465
+ return build_obstacles_norm(
466
+ filtered_series
467
+ .filter((srs) => srs?.filtered_data)
468
+ .map((srs) => ({
469
+ points: srs.filtered_data.map((pt) => ({
470
+ x: is_time_x ? norm_x(new Date(pt.x)) : norm_x(pt.x),
471
+ y: 1 - norm_y(pt.y), // norm_y is 0 at bottom; invert so 0 = top
472
+ })),
473
+ draws_line: styles.show_lines && (srs.markers ?? DEFAULT_MARKERS).includes(`line`),
474
+ })),
475
+ base_w,
476
+ base_h,
477
+ )
478
+ })
479
+
480
+ const decor = $derived.by(() =>
481
+ place_decorations({
482
+ base_pad,
483
+ width,
484
+ height,
485
+ obstacles_norm,
486
+ // gate on legend_element (the actual render signal) not legend_data, whose fill entries read
487
+ // computed_fills -> pad and would make this derived reference itself
488
+ legend: legend != null && legend_element != null &&
489
+ !legend_has_explicit_pos && !legend_is_dragging && !legend_manual_position
490
+ ? { footprint: legend_footprint, clearance: legend?.axis_clearance }
491
+ : null,
492
+ // gate on a measured colorbar: its outside style stretches it to full width, so deciding from
493
+ // the (wide) pre-measure fallback would flip-flop placement between interior and outside
494
+ colorbar: Boolean(color_bar) && all_color_values.length > 0 && !color_bar?.wrapper_style &&
495
+ (colorbar_element?.offsetWidth ?? 0) > 0 && (colorbar_element?.offsetHeight ?? 0) > 0
496
+ ? {
497
+ footprint: colorbar_footprint,
498
+ horizontal: colorbar_is_horizontal,
499
+ clearance: color_bar?.axis_clearance,
500
+ }
501
+ : null,
502
+ })
503
+ )
504
+ const pad = $derived(decor.pad)
505
+ const legend_auto_outside = $derived(decor.legend_outside)
506
+ const legend_outside_x = $derived(decor.legend_pos.x)
507
+ const legend_outside_y = $derived(decor.legend_pos.y)
508
+ const effective_cbar_wrapper_style = $derived(
509
+ color_bar?.wrapper_style ?? (decor.colorbar_outside ? decor.colorbar_style : undefined),
510
+ )
511
+
512
+ // Reactive clip area dimensions to ensure proper responsiveness
513
+ let clip_area = $derived({
514
+ x: pad.l || 0,
515
+ y: pad.t || 0,
516
+ width: isFinite(width - pad.l - pad.r) ? Math.max(1, width - pad.l - pad.r) : 1,
517
+ height: isFinite(height - pad.t - pad.b)
518
+ ? Math.max(1, height - pad.t - pad.b)
519
+ : 1,
520
+ })
521
+
522
+ // Calculate plot area center coordinates
523
+ let plot_center_x = $derived(pad.l + (width - pad.r - pad.l) / 2)
524
+ let plot_center_y = $derived(pad.t + (height - pad.b - pad.t) / 2)
525
+
526
+ // Extract color and size values in single pass (used for scale computations)
527
+ let series_value_arrays = $derived.by(() => {
528
+ const color_values: number[] = []
529
+ const size_values: number[] = []
530
+ for (const srs of series_with_ids) {
531
+ if (!srs) continue
532
+ const { color_values: cvs, size_values: svs } = srs as DataSeries
533
+ if (cvs) { for (const val of cvs) if (val != null) color_values.push(val) }
534
+ if (svs) { for (const val of svs) if (val != null) size_values.push(val) }
535
+ }
536
+ return { color_values, size_values }
537
+ })
538
+ let all_color_values = $derived(series_value_arrays.color_values)
539
+
540
+ // Compute auto ranges based on data and limits
541
+ let auto_x_range = $derived(
542
+ get_nice_data_range(
543
+ all_points,
544
+ ({ x }) => x,
545
+ final_x_axis.range ?? [null, null],
546
+ final_x_axis.scale_type ?? `linear`,
547
+ range_padding,
548
+ is_time_x,
549
+ ),
550
+ )
551
+
552
+ let auto_y_range = $derived(
553
+ get_nice_data_range(
554
+ y1_points,
555
+ ({ y }) => y,
556
+ final_y_axis.range ?? [null, null],
557
+ final_y_axis.scale_type ?? `linear`,
558
+ range_padding,
559
+ false,
560
+ ),
561
+ )
562
+
563
+ let auto_x2_range = $derived(
564
+ get_nice_data_range(
565
+ x2_points,
566
+ ({ x }) => x,
567
+ final_x2_axis.range ?? [null, null],
568
+ final_x2_axis.scale_type ?? `linear`,
569
+ range_padding,
570
+ is_time_x2,
571
+ ),
572
+ )
573
+
574
+ let auto_y2_range = $derived(
575
+ get_nice_data_range(
576
+ y2_points,
577
+ ({ y }) => y,
578
+ final_y2_axis.range ?? [null, null],
579
+ final_y2_axis.scale_type ?? `linear`,
580
+ range_padding,
581
+ false,
582
+ ),
583
+ )
584
+
585
+ // Update zoom ranges when auto ranges or explicit ranges change
586
+ // - Explicit ranges (from zoom/pan): apply directly
587
+ // - Auto ranges (from data changes): use lazy expansion to preserve view context
588
+ $effect(() => {
589
+ // Helper to get effective range (explicit ?? auto) and check if explicit
590
+ const get_range = (
591
+ axis: { range?: [number | null, number | null] },
592
+ auto: Vec2,
593
+ ): { explicit: boolean; range: Vec2 } => {
594
+ const explicit = axis.range?.[0] != null && axis.range?.[1] != null
595
+ const range = [axis.range?.[0] ?? auto[0], axis.range?.[1] ?? auto[1]] as Vec2
596
+ return { explicit, range }
597
+ }
598
+
599
+ const x = get_range(final_x_axis, auto_x_range)
600
+ const x2 = get_range(final_x2_axis, auto_x2_range)
601
+ const y = get_range(final_y_axis, auto_y_range)
602
+ const y2 = get_range(final_y2_axis, auto_y2_range)
603
+
604
+ // X axis: explicit → direct, auto → lazy expand
605
+ if (x.explicit) {
606
+ zoom_x_range = x.range
607
+ } else {
608
+ const result = expand_range_if_needed(initial_x_range, x.range)
609
+ if (result.changed) {
610
+ ;[initial_x_range, zoom_x_range] = [result.range, result.range]
611
+ }
612
+ }
613
+
614
+ // X2 axis: explicit → direct, auto → lazy expand
615
+ if (x2.explicit) {
616
+ zoom_x2_range = x2.range
617
+ } else {
618
+ const result = expand_range_if_needed(initial_x2_range, x2.range)
619
+ if (result.changed) {
620
+ ;[initial_x2_range, zoom_x2_range] = [result.range, result.range]
621
+ }
622
+ }
623
+
624
+ // Y axis: explicit → direct, auto → lazy expand
625
+ if (y.explicit) {
626
+ zoom_y_range = y.range
627
+ } else {
628
+ const result = expand_range_if_needed(initial_y_range, y.range)
629
+ if (result.changed) {
630
+ ;[initial_y_range, zoom_y_range] = [result.range, result.range]
631
+ }
632
+ }
633
+
634
+ // Y2 axis: explicit → direct, else expand initial range then optionally sync
635
+ if (y2.explicit) {
636
+ zoom_y2_range = y2.range
637
+ } else {
638
+ const result = expand_range_if_needed(initial_y2_range, y2.range)
639
+ if (result.changed) initial_y2_range = result.range
640
+ // Apply sync if enabled, otherwise use expanded range (or keep current if unchanged)
641
+ if (y2_sync_config.mode !== `none`) {
642
+ // untrack the read of zoom_y_range: this effect also writes it (fresh array per
643
+ // run when y.explicit), so a tracked read would loop until
644
+ // effect_update_depth_exceeded. Pan/zoom handlers sync y2 themselves.
645
+ zoom_y2_range = sync_y2_range(
646
+ untrack(() => zoom_y_range),
647
+ initial_y2_range,
648
+ y2_sync_config,
649
+ )
650
+ } else if (result.changed) {
651
+ zoom_y2_range = result.range
652
+ }
653
+ }
654
+ })
655
+
656
+ let [x_min, x_max] = $derived(zoom_x_range)
657
+ let [x2_min, x2_max] = $derived(zoom_x2_range)
658
+ let [y_min, y_max] = $derived(zoom_y_range)
659
+ let [y2_min, y2_max] = $derived(zoom_y2_range)
660
+
661
+ // Create auto color range
662
+ let auto_color_range = $derived(
663
+ // Ensure we only calculate extent on actual numbers, filtering out nulls/undefined
664
+ all_color_values.length > 0
665
+ ? extent(
666
+ all_color_values.filter((color_val: number | null): color_val is number =>
667
+ typeof color_val === `number`
668
+ ),
669
+ )
670
+ : [0, 1],
671
+ ) as Vec2
672
+
673
+ // Create scale functions
674
+ // For time scales, use scaleTime directly; otherwise use create_scale (supports linear/log/arcsinh)
675
+ let x_scale_fn = $derived(
676
+ is_time_x
677
+ ? scaleTime()
678
+ .domain([new Date(x_min), new Date(x_max)])
679
+ .range([pad.l, width - pad.r])
680
+ : create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [
681
+ pad.l,
682
+ width - pad.r,
683
+ ]),
684
+ )
685
+
686
+ let x2_scale_fn = $derived(
687
+ is_time_x2
688
+ ? scaleTime()
689
+ .domain([new Date(x2_min), new Date(x2_max)])
690
+ .range([pad.l, width - pad.r])
691
+ : create_scale(final_x2_axis.scale_type ?? `linear`, [x2_min, x2_max], [
692
+ pad.l,
693
+ width - pad.r,
694
+ ]),
695
+ )
696
+
697
+ let y_scale_fn = $derived(
698
+ create_scale(final_y_axis.scale_type ?? `linear`, [y_min, y_max], [
699
+ height - pad.b,
700
+ pad.t,
701
+ ]),
702
+ )
703
+
704
+ let y2_scale_fn = $derived(
705
+ create_scale(final_y2_axis.scale_type ?? `linear`, [y2_min, y2_max], [
706
+ height - pad.b,
707
+ pad.t,
708
+ ]),
709
+ )
710
+
711
+ // All size values from series (for size scale) - extracted in series_value_arrays
712
+ let all_size_values = $derived(series_value_arrays.size_values)
713
+
714
+ // Size scale function (using shared utility)
715
+ let size_scale_fn = $derived(create_size_scale(size_scale, all_size_values))
716
+
717
+ // Color scale function (using shared utility)
718
+ let color_scale_fn = $derived(create_color_scale(color_scale, auto_color_range))
719
+
720
+ // Filter series data to only include points within bounds and augment with internal data
721
+ let filtered_series = $derived(
722
+ filter_series_to_ranges(series_with_ids, {
723
+ x: [x_min, x_max],
724
+ x2: [x2_min, x2_max],
725
+ y: [y_min, y_max],
726
+ y2: [y2_min, y2_max],
727
+ }),
728
+ )
729
+
730
+ // Tally line series/points to budget path-morph tweens (see resolve_line_tween).
731
+ // Disabling the morph for high-cardinality plots (e.g. phonon bands) keeps them
732
+ // snappy; Line.svelte short-circuits the Tween when duration <= 0.
733
+ let line_tween_load = $derived.by(() => {
734
+ if (!styles.show_lines) return { series: 0, points: 0 }
735
+ let [n_series, n_points] = [0, 0]
736
+ for (const srs of filtered_series ?? []) {
737
+ if (!(srs.markers ?? DEFAULT_MARKERS).includes(`line`)) continue
738
+ n_series += 1
739
+ n_points += srs.x.length
740
+ }
741
+ return { series: n_series, points: n_points }
742
+ })
743
+ let effective_line_tween = $derived(resolve_line_tween(line_tween, line_tween_load))
744
+
745
+ // Obstacle field for legend/colorbar auto-placement. Sampling only data points lets the
746
+ // legend land on top of a steep connecting line whose markers are sparse (e.g. y=x^2), so
747
+ // sample_series_obstacle_points also walks each drawn segment at a fixed pixel cadence.
748
+ const SEGMENT_SAMPLE_STEP = 12 // px between samples taken along a connecting line
749
+ let plot_points_for_placement = $derived.by(() => {
750
+ if (!width || !height || !filtered_series) return []
751
+
752
+ const points: { x: number; y: number }[] = []
753
+
754
+ for (const series_data of filtered_series) {
755
+ if (!series_data?.filtered_data) continue
756
+ const use_x2_scale = series_data.x_axis === `x2`
757
+ const active_x_scale = use_x2_scale ? x2_scale_fn : x_scale_fn
758
+ const active_is_time_x = use_x2_scale ? is_time_x2 : is_time_x
759
+ const active_y_scale = series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn
760
+ const draws_line = styles.show_lines &&
761
+ (series_data.markers ?? DEFAULT_MARKERS).includes(`line`)
762
+
763
+ const pixel_points = series_data.filtered_data.map((point) => ({
764
+ x: active_is_time_x ? active_x_scale(new Date(point.x)) : active_x_scale(point.x),
765
+ y: active_y_scale(point.y),
766
+ }))
767
+ points.push(...sample_series_obstacle_points(pixel_points, draws_line, SEGMENT_SAMPLE_STEP))
768
+ }
769
+ return points
770
+ })
771
+
772
+ const fill_hover_key = (
773
+ source_type: `fill_region` | `error_band`,
774
+ source_idx: number,
775
+ id?: string | number,
776
+ is_duplicate_id = false,
777
+ ): string => {
778
+ if (id == null) return `${source_type}:idx:${source_idx}`
779
+ if (is_duplicate_id) return `${source_type}:id:${id}:idx:${source_idx}`
780
+ return `${source_type}:id:${id}`
781
+ }
782
+ const has_duplicate_id = <T extends { id?: string | number }>(
783
+ items: readonly T[] | undefined,
784
+ source_idx: number,
785
+ id?: string | number,
786
+ ): boolean =>
787
+ id != null && (items?.some((item, idx) => idx !== source_idx && item.id === id) ?? false)
788
+
789
+ // Computed fill regions: merge fill_regions and converted error_bands, resolve boundaries
790
+ type ComputedFill = FillRegion & {
791
+ idx: number
792
+ source_type: `fill_region` | `error_band`
793
+ source_idx: number
794
+ hover_key: string
795
+ path_segments: string[]
796
+ }
797
+ let computed_fills = $derived.by((): ComputedFill[] => {
798
+ // Early exit: skip expensive computation if no fills to render
799
+ const has_fill_regions = fill_regions && fill_regions.length > 0
800
+ const has_error_bands = error_bands && error_bands.length > 0
801
+ if (!has_fill_regions && !has_error_bands) return []
802
+
803
+ // Merge fill_regions and converted error_bands, tracking source
804
+ const all_regions: {
805
+ region: FillRegion | null
806
+ source_type: `fill_region` | `error_band`
807
+ source_idx: number
808
+ hover_key: string
809
+ }[] = [
810
+ ...(fill_regions ?? []).map((region, source_idx) => ({
811
+ region,
812
+ source_type: `fill_region` as const,
813
+ source_idx,
814
+ hover_key: fill_hover_key(
815
+ `fill_region`,
816
+ source_idx,
817
+ region.id,
818
+ has_duplicate_id(fill_regions, source_idx, region.id),
819
+ ),
820
+ })),
821
+ ...(error_bands ?? []).map((band, source_idx) => ({
822
+ region: convert_error_band_to_fill_region(band, series_with_ids),
823
+ source_type: `error_band` as const,
824
+ source_idx,
825
+ hover_key: fill_hover_key(
826
+ `error_band`,
827
+ source_idx,
828
+ band.id,
829
+ has_duplicate_id(error_bands, source_idx, band.id),
830
+ ),
831
+ })),
832
+ ]
833
+
834
+ // On log axes, clamp non-positive coords to the scale's domain floor (x_min/y_min) before
835
+ // scaling. A fixed tiny epsilon can sit far below the domain and map to extreme pixel coords.
836
+ const x_scale_type = final_x_axis.scale_type ?? `linear`
837
+ const y_scale_type = final_y_axis.scale_type ?? `linear`
838
+ const to_px = (pt: Pt): Pt => ({
839
+ x: x_scale_fn(x_scale_type === `log` && pt.x <= 0 ? x_min : pt.x),
840
+ y: y_scale_fn(y_scale_type === `log` && pt.y <= 0 ? y_min : pt.y),
841
+ })
842
+
843
+ // Each boundary is traced through its own points with the same curve the series line uses,
844
+ // so fill edges coincide exactly with the lines they border (x_domain anchors flat boundaries).
845
+ const domains = {
846
+ x_domain: [x_min, x_max] as Vec2,
847
+ y_domain: [y_min, y_max] as Vec2,
848
+ y2_domain: [y2_min, y2_max] as Vec2,
849
+ }
850
+
851
+ return all_regions
852
+ .filter((
853
+ entry,
854
+ ): entry is {
855
+ region: FillRegion
856
+ source_type: `fill_region` | `error_band`
857
+ source_idx: number
858
+ hover_key: string
859
+ } => entry.region !== null)
860
+ .map(({ region, source_type, source_idx, hover_key }, idx) => {
861
+ // Hidden fills keep their entry (with empty path_segments -> nothing renders) so the
862
+ // legend item persists greyed-out and can be toggled back on.
863
+ const hidden = region.visible === false
864
+ const path_segments = hidden
865
+ ? []
866
+ : compute_fill_segments(region, series_with_ids, domains)
867
+ .map((seg) =>
868
+ generate_fill_path(
869
+ seg.upper.map(to_px),
870
+ seg.lower.map(to_px),
871
+ seg.upper_curve,
872
+ seg.lower_curve,
873
+ )
874
+ )
875
+ .filter((path) => path.length > 0)
876
+
877
+ // Drop only visible fills with no geometry; keep hidden ones for the legend
878
+ if (!hidden && path_segments.length === 0) return null
879
+
880
+ return { ...region, idx, source_type, source_idx, hover_key, path_segments }
881
+ })
882
+ .filter((fill): fill is ComputedFill => fill !== null)
883
+ })
884
+
885
+ // Prepare data needed for the legend component
886
+ let legend_data = $derived(
887
+ build_legend_data(series_with_ids, computed_fills, color_scale_fn),
888
+ )
889
+
890
+ // Group fills by z-index for ordered rendering (single pass instead of 4 filters)
891
+ let fills_by_z = $derived.by(() => {
892
+ const groups: {
893
+ below_grid: typeof computed_fills
894
+ below_lines: typeof computed_fills
895
+ below_points: typeof computed_fills
896
+ above_all: typeof computed_fills
897
+ } = { below_grid: [], below_lines: [], below_points: [], above_all: [] }
898
+
899
+ for (const fill of computed_fills) {
900
+ if (fill.z_index === `below-grid`) groups.below_grid.push(fill)
901
+ else if (fill.z_index === `below-points`) groups.below_points.push(fill)
902
+ else if (fill.z_index === `above-all`) groups.above_all.push(fill)
903
+ else groups.below_lines.push(fill) // default: no z_index or 'below-lines'
904
+ }
905
+ return groups
906
+ })
907
+
908
+ // Compute ref_lines with index and group by z-index (using shared utilities)
909
+ let indexed_ref_lines = $derived(index_ref_lines(ref_lines))
910
+ let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines))
911
+
912
+ // Calculate best legend placement using continuous grid sampling
913
+ let legend_placement = $derived.by(() => {
914
+ const should_place = legend != null &&
915
+ (legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)
916
+
917
+ if (!should_place || !width || !height) return null
918
+
919
+ const plot_width = width - pad.l - pad.r
920
+ const plot_height = height - pad.t - pad.b
921
+
922
+ const placement_config = {
923
+ plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
924
+ element: legend_element,
925
+ element_size: { width: 120, height: 80 }, // fallback before first render
926
+ axis_clearance: legend?.axis_clearance,
927
+ exclude_rects: [],
928
+ points: plot_points_for_placement,
929
+ }
930
+
931
+ return compute_element_placement(placement_config)
932
+ })
933
+
934
+ // Calculate color bar placement (coordinates with legend to avoid overlap)
935
+ let color_bar_placement = $derived.by(() => {
936
+ if (!color_bar || all_color_values.length === 0 || !width || !height) return null
937
+
938
+ const plot_width = width - pad.l - pad.r
939
+ const plot_height = height - pad.t - pad.b
940
+
941
+ // Fallback estimate (with room for tick labels) used before the colorbar first
942
+ // renders; compute_element_placement measures the real footprint once it's laid out
943
+ const is_horizontal = (color_bar.orientation ?? `horizontal`) === `horizontal`
944
+ const colorbar_size = is_horizontal
945
+ ? { width: 220, height: 56 }
946
+ : { width: 56, height: 100 }
947
+
948
+ // Build exclusion rects (avoid legend if it's placed)
949
+ const exclude_rects: Rect[] = []
950
+ if (legend_element && legend_placement) {
951
+ exclude_rects.push({
952
+ x: legend_placement.x,
953
+ y: legend_placement.y,
954
+ width: legend_element.offsetWidth || 120,
955
+ height: legend_element.offsetHeight || 80,
956
+ })
957
+ }
958
+
959
+ return compute_element_placement({
960
+ plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
961
+ element: colorbar_element,
962
+ element_size: colorbar_size,
963
+ // Small gap from the corner; the full-footprint measurement reserves the tick
964
+ // labels, so this alone keeps the colorbar off the axes
965
+ axis_clearance: color_bar?.axis_clearance ?? 15,
966
+ exclude_rects,
967
+ points: plot_points_for_placement,
968
+ })
969
+ })
970
+
971
+ // Active legend placement (null if user set explicit position)
972
+ let active_legend_placement = $derived.by(() => {
973
+ if (!legend_placement) return null
974
+
975
+ // Skip auto-placement if user set explicit position in style
976
+ const legend_style = legend?.style ?? ``
977
+ if (
978
+ /(^|[;{]\s*)(top|bottom|left|right)\s*:|position\s*:\s*absolute/.test(
979
+ legend_style,
980
+ )
981
+ ) return null
982
+
983
+ return legend_placement
984
+ })
985
+
986
+ // Initialize tweened values for color bar position - create once, update target via effect
987
+ // untrack() explicitly captures initial tween config (intentional - config set once at mount)
988
+ const tweened_colorbar_coords = new Tween(
989
+ { x: 0, y: 0 },
990
+ untrack(() => ({ duration: 400, ...color_bar?.tween })),
991
+ )
992
+ // Initialize tweened values for legend position - create once, update target via effect
993
+ const tweened_legend_coords = new Tween(
994
+ { x: 0, y: 0 },
995
+ untrack(() => ({ duration: 400, ...legend?.tween })),
996
+ )
997
+
998
+ // Update placement positions (with animation and stability checks)
999
+ $effect(() => {
1000
+ if (!width || !height) return
1001
+
1002
+ // Track dimensions for resize detection
1003
+ const dims_changed = dim_tracker.has_changed(width, height)
1004
+ if (dims_changed) dim_tracker.update(width, height)
1005
+
1006
+ // Update colorbar position (stable after initial placement unless responsive)
1007
+ if (color_bar_placement) {
1008
+ const is_responsive = color_bar?.responsive ?? false
1009
+ const should_update = dims_changed || (!colorbar_hover.is_locked.current &&
1010
+ (is_responsive || !has_initial_colorbar_placement))
1011
+
1012
+ if (should_update) {
1013
+ tweened_colorbar_coords.set(
1014
+ { x: color_bar_placement.x, y: color_bar_placement.y },
1015
+ has_initial_colorbar_placement ? undefined : { duration: 0 },
1016
+ )
1017
+ if (colorbar_element && !has_initial_colorbar_placement) {
1018
+ has_initial_colorbar_placement = true
1019
+ }
1020
+ }
1021
+ }
1022
+
1023
+ // Update legend position (stable after initial placement unless responsive)
1024
+ if (legend_manual_position && !legend_is_dragging) {
1025
+ // Immediate update (no animation) for manually dragged positions
1026
+ tweened_legend_coords.set(legend_manual_position, { duration: 0 })
1027
+ } else if (active_legend_placement && !legend_is_dragging) {
1028
+ const is_responsive = legend?.responsive ?? false
1029
+ const should_update = dims_changed || (!legend_hover.is_locked.current &&
1030
+ (is_responsive || !has_initial_legend_placement))
1031
+
1032
+ if (should_update) {
1033
+ tweened_legend_coords.set(
1034
+ { x: active_legend_placement.x, y: active_legend_placement.y },
1035
+ has_initial_legend_placement ? undefined : { duration: 0 },
1036
+ )
1037
+ if (legend_element) has_initial_legend_placement = true
1038
+ }
1039
+ }
1040
+ })
1041
+
1042
+ // Generate axis ticks - consolidated into single derived for efficiency
1043
+ let axis_ticks = $derived.by(() => {
1044
+ if (!width || !height) return { x: [], x2: [], y: [], y2: [] }
1045
+
1046
+ // X-axis ticks: choose appropriate scale for tick generation
1047
+ // Time scales (format starts with %) use scaleTime for better tick placement
1048
+ const x_scale_for_ticks = is_time_x
1049
+ ? scaleTime().domain([new Date(x_min), new Date(x_max)])
1050
+ : create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [0, 1])
1051
+
1052
+ const x2_scale_for_ticks = is_time_x2
1053
+ ? scaleTime().domain([new Date(x2_min), new Date(x2_max)])
1054
+ : create_scale(final_x2_axis.scale_type ?? `linear`, [x2_min, x2_max], [0, 1])
1055
+
1056
+ return {
1057
+ x: generate_ticks(
1058
+ [x_min, x_max],
1059
+ final_x_axis.scale_type ?? `linear`,
1060
+ final_x_axis.ticks,
1061
+ x_scale_for_ticks,
1062
+ { format: final_x_axis.format },
1063
+ ),
1064
+ x2: x2_points.length > 0
1065
+ ? generate_ticks(
1066
+ [x2_min, x2_max],
1067
+ final_x2_axis.scale_type ?? `linear`,
1068
+ final_x2_axis.ticks,
1069
+ x2_scale_for_ticks,
1070
+ { format: final_x2_axis.format },
1071
+ )
1072
+ : [],
1073
+ y: generate_ticks(
1074
+ [y_min, y_max],
1075
+ final_y_axis.scale_type ?? `linear`,
1076
+ final_y_axis.ticks,
1077
+ y_scale_fn,
1078
+ { default_count: 5 },
1079
+ ),
1080
+ y2: y2_points.length > 0
1081
+ ? generate_ticks(
1082
+ [y2_min, y2_max],
1083
+ final_y2_axis.scale_type ?? `linear`,
1084
+ final_y2_axis.ticks,
1085
+ y2_scale_fn,
1086
+ { default_count: 5 },
1087
+ )
1088
+ : [],
1089
+ }
1090
+ })
1091
+
1092
+ let x_tick_values = $derived(axis_ticks.x)
1093
+ let x2_tick_values = $derived(axis_ticks.x2)
1094
+ let y_tick_values = $derived(axis_ticks.y)
1095
+ let y2_tick_values = $derived(axis_ticks.y2)
1096
+
1097
+ // Cache measured tick-label widths so expensive text measurement only runs
1098
+ // when tick values/format change, not on every template rerender.
1099
+ let tick_label_widths = $derived({
1100
+ x2_max: measure_max_tick_width(x2_tick_values, final_x2_axis.format ?? ``),
1101
+ y_max: measure_max_tick_width(y_tick_values, final_y_axis.format ?? ``),
1102
+ y2_max: measure_max_tick_width(y2_tick_values, final_y2_axis.format ?? ``),
1103
+ })
1104
+
1105
+ // Define global handlers reference for adding/removing listeners
1106
+ const on_window_mouse_move = (evt: MouseEvent) => {
1107
+ if (!drag_start_coords || !svg_bounding_box) return // Exit if not dragging or no bounds
1108
+
1109
+ // Calculate mouse position relative to the stored SVG bounding box
1110
+ const current_x = evt.clientX - svg_bounding_box.left
1111
+ const current_y = evt.clientY - svg_bounding_box.top
1112
+ drag_current_coords = { x: current_x, y: current_y }
1113
+
1114
+ // Optional: update tooltip only if inside SVG bounds
1115
+ const is_inside_svg = current_x >= 0 &&
1116
+ current_x <= svg_bounding_box.width &&
1117
+ current_y >= 0 &&
1118
+ current_y <= svg_bounding_box.height
1119
+
1120
+ if (is_inside_svg) {
1121
+ // Use the already calculated relative coordinates
1122
+ update_tooltip_point(current_x, current_y)
1123
+ } else tooltip_point = null // Clear tooltip if outside
1124
+ }
1125
+
1126
+ const on_window_mouse_up = (_evt: MouseEvent) => {
1127
+ if (drag_start_coords && drag_current_coords) {
1128
+ // Use current scales to invert screen coords to data coords
1129
+ const start_data_x_val = x_scale_fn.invert(drag_start_coords.x)
1130
+ const end_data_x_val = x_scale_fn.invert(drag_current_coords.x)
1131
+ const start_data_y_val = y_scale_fn.invert(drag_start_coords.y)
1132
+ const end_data_y_val = y_scale_fn.invert(drag_current_coords.y)
1133
+
1134
+ // Same scale inverts both coords, so both are numbers or both are Dates
1135
+ const [x1, x2] = [to_epoch_num(start_data_x_val), to_epoch_num(end_data_x_val)]
1136
+ const next_x_range = sorted_range(x1, x2)
1137
+ // Y axis is always number
1138
+ const next_y_range = sorted_range(start_data_y_val, end_data_y_val)
1139
+
1140
+ // Check for minuscule zoom box (e.g. accidental click)
1141
+ const min_zoom_size = 5 // Minimum pixels to trigger zoom
1142
+ const dx = Math.abs(drag_start_coords.x - drag_current_coords.x)
1143
+ const dy = Math.abs(drag_start_coords.y - drag_current_coords.y)
1144
+
1145
+ if (
1146
+ dx > min_zoom_size &&
1147
+ dy > min_zoom_size &&
1148
+ next_x_range[0] !== next_x_range[1] &&
1149
+ next_y_range[0] !== next_y_range[1]
1150
+ ) {
1151
+ // Update axis ranges to trigger reactivity (like BarPlot/Histogram do)
1152
+ x_axis = { ...x_axis, range: next_x_range }
1153
+ y_axis = { ...y_axis, range: next_y_range }
1154
+
1155
+ // X2 axis: invert screen coords using x2 scale
1156
+ if (x2_points.length > 0) {
1157
+ const x2_a = to_epoch_num(x2_scale_fn.invert(drag_start_coords.x))
1158
+ const x2_b = to_epoch_num(x2_scale_fn.invert(drag_current_coords.x))
1159
+ x2_axis = { ...x2_axis, range: sorted_range(x2_a, x2_b) }
1160
+ }
1161
+
1162
+ // Y2 axis: when sync is enabled the y_axis effect derives y2; with sync 'none'
1163
+ // y2 must zoom from the rect directly (parity with BarPlot/Histogram/BoxPlot)
1164
+ if (y2_points.length > 0 && y2_sync_config.mode === `none`) {
1165
+ const y2_a = y2_scale_fn.invert(drag_start_coords.y)
1166
+ const y2_b = y2_scale_fn.invert(drag_current_coords.y)
1167
+ y2_axis = { ...y2_axis, range: sorted_range(y2_a, y2_b) }
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ // Reset states and remove listeners
1173
+ drag_start_coords = null
1174
+ drag_current_coords = null
1175
+ svg_bounding_box = null
1176
+ window.removeEventListener(`mousemove`, on_window_mouse_move)
1177
+ window.removeEventListener(`mouseup`, on_window_mouse_up)
1178
+ document.body.style.cursor = `default`
1179
+ }
1180
+
1181
+ // Pan/zoom all four axes from an interaction-start snapshot, each in its own
1182
+ // scale's transform space (log axes pan by a constant factor, linear by a shift).
1183
+ // Plot dims clamped to 1px so degenerate containers can't produce Infinity deltas.
1184
+ const pan_all_axes = (init: InitialRanges, dx_px: number, dy_px: number) => {
1185
+ const plot_width = Math.max(1, width - pad.l - pad.r)
1186
+ const plot_height = Math.max(1, height - pad.t - pad.b)
1187
+ zoom_x_range = pan_range_by_pixels(init.initial_x_range, dx_px, plot_width, final_x_axis.scale_type)
1188
+ zoom_x2_range = pan_range_by_pixels(init.initial_x2_range, dx_px, plot_width, final_x2_axis.scale_type)
1189
+ zoom_y_range = pan_range_by_pixels(init.initial_y_range, dy_px, plot_height, final_y_axis.scale_type)
1190
+ zoom_y2_range = get_synced_y2(
1191
+ zoom_y_range,
1192
+ pan_range_by_pixels(init.initial_y2_range, dy_px, plot_height, final_y2_axis.scale_type),
1193
+ )
1194
+ }
1195
+ const zoom_all_axes = (init: InitialRanges, factor: number) => {
1196
+ zoom_x_range = zoom_range_by_factor(init.initial_x_range, factor, final_x_axis.scale_type)
1197
+ zoom_x2_range = zoom_range_by_factor(init.initial_x2_range, factor, final_x2_axis.scale_type)
1198
+ zoom_y_range = zoom_range_by_factor(init.initial_y_range, factor, final_y_axis.scale_type)
1199
+ zoom_y2_range = get_synced_y2(
1200
+ zoom_y_range,
1201
+ zoom_range_by_factor(init.initial_y2_range, factor, final_y2_axis.scale_type),
1202
+ )
1203
+ }
1204
+
1205
+ // Pan drag handler (drag direction inverted on x for natural pan feel)
1206
+ const on_pan_move = (evt: MouseEvent) => {
1207
+ if (!pan_drag_state) return
1208
+ const sensitivity = pan?.drag_sensitivity ?? 1
1209
+ pan_all_axes(
1210
+ pan_drag_state,
1211
+ -(evt.clientX - pan_drag_state.start.x) * sensitivity,
1212
+ (evt.clientY - pan_drag_state.start.y) * sensitivity,
1213
+ )
1214
+ }
1215
+
1216
+ const on_pan_end = () => {
1217
+ pan_drag_state = null
1218
+ document.body.style.cursor = ``
1219
+ window.removeEventListener(`mousemove`, on_pan_move)
1220
+ window.removeEventListener(`mouseup`, on_pan_end)
1221
+ }
1222
+
1223
+ // Tear down any window listeners + cursor override if the component unmounts mid-drag
1224
+ // (mouseup/panend would otherwise never fire, leaking listeners and a stuck cursor).
1225
+ // onDestroy also runs during SSR teardown, where window/document don't exist.
1226
+ onDestroy(() => {
1227
+ remove_drag_listeners([on_window_mouse_move, on_pan_move], [on_window_mouse_up, on_pan_end])
1228
+ drag_start_coords = null
1229
+ drag_current_coords = null
1230
+ svg_bounding_box = null
1231
+ pan_drag_state = null
1232
+ })
1233
+
1234
+ function handle_mouse_down(evt: MouseEvent) {
1235
+ if (!svg_element) return
1236
+
1237
+ // Check if pan is enabled and shift is held for pan mode
1238
+ const pan_enabled = pan?.enabled !== false
1239
+ if (pan_enabled && evt.shiftKey) {
1240
+ evt.preventDefault()
1241
+ pan_drag_state = {
1242
+ start: { x: evt.clientX, y: evt.clientY },
1243
+ initial_x_range: [...zoom_x_range] as [number, number],
1244
+ initial_x2_range: [...zoom_x2_range] as [number, number],
1245
+ initial_y_range: [...zoom_y_range] as [number, number],
1246
+ initial_y2_range: [...zoom_y2_range] as [number, number],
1247
+ }
1248
+ document.body.style.cursor = `grabbing`
1249
+ window.addEventListener(`mousemove`, on_pan_move)
1250
+ window.addEventListener(`mouseup`, on_pan_end)
1251
+ return
1252
+ }
1253
+
1254
+ // Store bounding box first, then calculate coords using it
1255
+ svg_bounding_box = svg_element.getBoundingClientRect()
1256
+
1257
+ // Calculate initial coords using the same bounding box that will be used during drag
1258
+ const initial_x = evt.clientX - svg_bounding_box.left
1259
+ const initial_y = evt.clientY - svg_bounding_box.top
1260
+ const coords = { x: initial_x, y: initial_y }
1261
+
1262
+ drag_start_coords = coords
1263
+ drag_current_coords = coords
1264
+
1265
+ window.addEventListener(`mousemove`, on_window_mouse_move)
1266
+ window.addEventListener(`mouseup`, on_window_mouse_up)
1267
+ document.body.style.cursor = `crosshair`
1268
+ evt.preventDefault()
1269
+ }
1270
+
1271
+ // Wheel handler for pan (requires focus and shift)
1272
+ function handle_wheel(evt: WheelEvent) {
1273
+ const pan_enabled = pan?.enabled !== false
1274
+ // Only capture wheel when focused AND Shift is held
1275
+ // Use shift_held state (tracked via keydown/keyup) for compatibility with synthetic events
1276
+ if (!pan_enabled || !is_focused || !shift_held) return
1277
+
1278
+ evt.preventDefault()
1279
+
1280
+ // Clamp to at least 1 to avoid Infinity deltas when padding equals container size
1281
+ const plot_width = Math.max(1, width - pad.l - pad.r)
1282
+ const plot_height = Math.max(1, height - pad.t - pad.b)
1283
+ const sensitivity = pan?.wheel_sensitivity ?? 1
1284
+
1285
+ // Pan along the dominant wheel direction
1286
+ // (deltaX for horizontal scroll on trackpads, deltaY for vertical)
1287
+ if (Math.abs(evt.deltaX) > Math.abs(evt.deltaY)) {
1288
+ const dx = evt.deltaX * sensitivity
1289
+ zoom_x_range = pan_range_by_pixels(zoom_x_range, dx, plot_width, final_x_axis.scale_type)
1290
+ zoom_x2_range = pan_range_by_pixels(zoom_x2_range, dx, plot_width, final_x2_axis.scale_type)
1291
+ } else {
1292
+ const dy = evt.deltaY * sensitivity
1293
+ zoom_y_range = pan_range_by_pixels(zoom_y_range, dy, plot_height, final_y_axis.scale_type)
1294
+ zoom_y2_range = get_synced_y2(
1295
+ zoom_y_range,
1296
+ pan_range_by_pixels(zoom_y2_range, dy, plot_height, final_y2_axis.scale_type),
1297
+ )
1298
+ }
1299
+ }
1300
+
1301
+ // Touch handlers for pinch-zoom and two-finger pan
1302
+ function handle_touch_start(evt: TouchEvent) {
1303
+ const touch_enabled = pan?.enabled !== false && pan?.touch_enabled !== false
1304
+ if (!touch_enabled || evt.touches.length !== 2) return
1305
+
1306
+ evt.preventDefault()
1307
+ const touches = Array.from(evt.touches)
1308
+ touch_state = {
1309
+ start_touches: touches.map((touch) => ({ x: touch.clientX, y: touch.clientY })),
1310
+ initial_x_range: [...zoom_x_range] as [number, number],
1311
+ initial_x2_range: [...zoom_x2_range] as [number, number],
1312
+ initial_y_range: [...zoom_y_range] as [number, number],
1313
+ initial_y2_range: [...zoom_y2_range] as [number, number],
1314
+ }
1315
+ }
1316
+
1317
+ function handle_touch_move(evt: TouchEvent) {
1318
+ if (!touch_state || evt.touches.length !== 2) return
1319
+ evt.preventDefault()
1320
+
1321
+ const [t1, t2] = Array.from(evt.touches)
1322
+ const [s1, s2] = touch_state.start_touches
1323
+
1324
+ // Calculate center movement for pan
1325
+ const start_center = { x: (s1.x + s2.x) / 2, y: (s1.y + s2.y) / 2 }
1326
+ const curr_center = {
1327
+ x: (t1.clientX + t2.clientX) / 2,
1328
+ y: (t1.clientY + t2.clientY) / 2,
1329
+ }
1330
+ const dx = curr_center.x - start_center.x
1331
+ const dy = curr_center.y - start_center.y
1332
+
1333
+ // Calculate pinch scale (curr/start so spread = zoom out, pinch = zoom in)
1334
+ const start_dist = Math.hypot(s2.x - s1.x, s2.y - s1.y)
1335
+ // ignore near-coincident touches so curr_dist / start_dist can't blow up the scale
1336
+ if (start_dist < MIN_TOUCH_DISTANCE_PIXELS) return
1337
+ const curr_dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY)
1338
+ const scale = curr_dist / start_dist
1339
+
1340
+ // Pinch zoom about the view center (spread = zoom in, pinch = zoom out)
1341
+ if (Math.abs(scale - 1) > PINCH_ZOOM_THRESHOLD && scale > Number.EPSILON) {
1342
+ zoom_all_axes(touch_state, scale)
1343
+ } else pan_all_axes(touch_state, -dx, dy)
1344
+ }
1345
+
1346
+ function handle_touch_end() {
1347
+ touch_state = null
1348
+ }
1349
+
1350
+ // tooltip logic: find closest point and update tooltip state
1351
+ function update_tooltip_point(x_rel: number, y_rel: number, evt?: MouseEvent) {
1352
+ if (!width || !height) return
1353
+
1354
+ let closest_point: InternalPoint<Metadata> | null = null
1355
+ let closest_series: DataSeries<Metadata> | null = null
1356
+ let min_screen_dist_sq = Infinity
1357
+ const { threshold_px = 20 } = hover_config // Use configured threshold
1358
+ const hover_threshold_px_sq = threshold_px * threshold_px
1359
+
1360
+ // Iterate through points to find the closest one in screen coordinates
1361
+ for (const series_data of filtered_series) {
1362
+ if (!series_data?.filtered_data) continue
1363
+
1364
+ const tooltip_use_x2 = series_data.x_axis === `x2`
1365
+ const tooltip_x_scale = tooltip_use_x2 ? x2_scale_fn : x_scale_fn
1366
+ const tooltip_is_time_x = tooltip_use_x2 ? is_time_x2 : is_time_x
1367
+ for (const point of series_data.filtered_data) {
1368
+ // Calculate screen coordinates of the point
1369
+ const point_cx = tooltip_is_time_x
1370
+ ? tooltip_x_scale(new Date(point.x))
1371
+ : tooltip_x_scale(point.x)
1372
+ const point_cy = (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(
1373
+ point.y,
1374
+ )
1375
+
1376
+ // Calculate squared screen distance between mouse and point
1377
+ const screen_dx = x_rel - point_cx
1378
+ const screen_dy = y_rel - point_cy
1379
+ const screen_distance_sq = screen_dx * screen_dx + screen_dy * screen_dy
1380
+
1381
+ // Update if this point is closer
1382
+ if (screen_distance_sq < min_screen_dist_sq) {
1383
+ min_screen_dist_sq = screen_distance_sq
1384
+ closest_point = point
1385
+ closest_series = series_data
1386
+ }
1387
+ }
1388
+ }
1389
+
1390
+ // Check if the closest point is within the hover threshold
1391
+ if (
1392
+ closest_point &&
1393
+ closest_series &&
1394
+ min_screen_dist_sq <= hover_threshold_px_sq
1395
+ ) {
1396
+ // Construct handler props synchronously to avoid stale derived reads
1397
+ const props = construct_handler_props(closest_point)
1398
+ tooltip_point = closest_point
1399
+ // Construct object matching change signature
1400
+ const { x, y, metadata } = closest_point
1401
+ change({ x, y, metadata, series: closest_series })
1402
+ // Call hover handler with synchronously constructed props
1403
+ if (evt && props) {
1404
+ on_point_hover?.({ ...props, event: evt, point: closest_point })
1405
+ }
1406
+ } else {
1407
+ tooltip_point = null
1408
+ change(null)
1409
+ on_point_hover?.(null)
1410
+ }
1411
+ }
1412
+
1413
+ function on_mouse_move(evt: MouseEvent) {
1414
+ hovered = true
1415
+
1416
+ const coords = get_relative_coords(evt)
1417
+ if (!coords) return
1418
+
1419
+ update_tooltip_point(coords.x, coords.y, evt)
1420
+ }
1421
+
1422
+ // Merge user config with defaults before the effect that uses it
1423
+ let actual_label_config = $derived({
1424
+ sa_iterations: 2000,
1425
+ max_labels: 300,
1426
+ leader_line_threshold: 15,
1427
+ ...label_placement_config,
1428
+ })
1429
+
1430
+ $effect(() => {
1431
+ if (!width || !height) {
1432
+ label_positions = {}
1433
+ return
1434
+ }
1435
+
1436
+ label_positions = compute_label_positions(
1437
+ filtered_series,
1438
+ actual_label_config,
1439
+ { x_scale_fn, y_scale_fn, y2_scale_fn, x_axis: final_x_axis },
1440
+ { width, height, pad },
1441
+ )
1442
+ })
1443
+
1444
+ // Legend drag handlers
1445
+ function handle_legend_drag_start(event: MouseEvent) {
1446
+ if (!svg_element) return
1447
+
1448
+ legend_is_dragging = true
1449
+
1450
+ // Get the actual rendered position of the legend element (accounts for transforms)
1451
+ const legend_el = event.currentTarget
1452
+ if (!(legend_el instanceof HTMLElement)) return
1453
+ const legend_rect = legend_el.getBoundingClientRect()
1454
+
1455
+ // Calculate offset from mouse to legend's actual rendered position relative to SVG
1456
+ const [x, y] = [event.clientX - legend_rect.left, event.clientY - legend_rect.top]
1457
+ legend_drag_offset = { x, y }
1458
+ }
1459
+
1460
+ function handle_legend_drag(event: MouseEvent) {
1461
+ if (!legend_is_dragging || !svg_element || !legend_element) return
1462
+
1463
+ const svg_rect = svg_element.getBoundingClientRect()
1464
+
1465
+ // Calculate new position: mouse position relative to SVG, minus the offset within the legend
1466
+ const new_x = event.clientX - svg_rect.left - legend_drag_offset.x
1467
+ const new_y = event.clientY - svg_rect.top - legend_drag_offset.y
1468
+
1469
+ // Get actual legend dimensions for accurate bounds checking using the bound element reference
1470
+ const { width: legend_width, height: legend_height } = legend_element
1471
+ .getBoundingClientRect()
1472
+
1473
+ // Constrain to plot bounds using measured legend size
1474
+ const constrained_x = Math.max(0, Math.min(width - legend_width, new_x))
1475
+ const constrained_y = Math.max(0, Math.min(height - legend_height, new_y))
1476
+
1477
+ legend_manual_position = { x: constrained_x, y: constrained_y }
1478
+ }
1479
+
1480
+ function get_screen_coords(point: Point, data_series?: DataSeries): [number, number] {
1481
+ // convert data coordinates to potentially non-finite screen coordinates
1482
+ const use_x2 = data_series?.x_axis === `x2`
1483
+ const active_x_scale = use_x2 ? x2_scale_fn : x_scale_fn
1484
+ const active_is_time_x = use_x2 ? is_time_x2 : is_time_x
1485
+ const screen_x = active_is_time_x
1486
+ ? active_x_scale(new Date(point.x))
1487
+ : active_x_scale(point.x)
1488
+
1489
+ const y_val = point.y
1490
+ // Determine which y-scale to use based on series y_axis property
1491
+ const use_y2 = data_series?.y_axis === `y2`
1492
+ const y_scale = use_y2 ? y2_scale_fn : y_scale_fn
1493
+ const y_scale_type = use_y2
1494
+ ? get_scale_type_name(final_y2_axis.scale_type)
1495
+ : get_scale_type_name(final_y_axis.scale_type)
1496
+ // Only log scale needs domain clamping; linear and arcsinh can handle any value
1497
+ const min_domain_y = y_scale_type === `log` ? y_scale.domain()[0] : -Infinity
1498
+ const safe_y_val = y_scale_type === `log` ? Math.max(y_val, min_domain_y) : y_val
1499
+ const screen_y = y_scale(safe_y_val) // This might be non-finite
1500
+
1501
+ return [screen_x, screen_y]
1502
+ }
1503
+
1504
+ // Helper function to construct ScatterHandlerProps synchronously from InternalPoint
1505
+ function construct_handler_props(
1506
+ point: InternalPoint<Metadata>,
1507
+ ): ScatterHandlerProps<Metadata> | null {
1508
+ const hovered_series = series_with_ids[point.series_idx]
1509
+ if (!hovered_series) return null
1510
+ const { x, y, color_value, metadata, series_idx } = point
1511
+ const handler_use_x2 = hovered_series.x_axis === `x2`
1512
+ const handler_x_scale = handler_use_x2 ? x2_scale_fn : x_scale_fn
1513
+ const handler_is_time_x = handler_use_x2 ? is_time_x2 : is_time_x
1514
+ const cx = handler_is_time_x ? handler_x_scale(new Date(x)) : handler_x_scale(x)
1515
+ const cy = (hovered_series.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(y)
1516
+ const active_x_config = handler_use_x2 ? final_x2_axis : final_x_axis
1517
+ const active_y_config = hovered_series.y_axis === `y2`
1518
+ ? final_y2_axis
1519
+ : final_y_axis
1520
+ const coords = {
1521
+ x,
1522
+ y,
1523
+ cx,
1524
+ cy,
1525
+ x_axis: active_x_config,
1526
+ x2_axis: final_x2_axis,
1527
+ y_axis: active_y_config,
1528
+ y2_axis: final_y2_axis,
1529
+ }
1530
+ return {
1531
+ ...coords,
1532
+ fullscreen,
1533
+ metadata,
1534
+ label: hovered_series.label ?? null,
1535
+ series_idx,
1536
+ x_formatted: format_value(x, active_x_config.format || `.3~s`),
1537
+ y_formatted: format_value(y, active_y_config.format || `.3~s`),
1538
+ color_value: color_value ?? null,
1539
+ colorbar: {
1540
+ value: color_value ?? null,
1541
+ title: color_bar?.title ?? null,
1542
+ scale: color_scale,
1543
+ tick_format: color_bar?.tick_format ?? null,
1544
+ },
1545
+ }
1546
+ }
1547
+
1548
+ // Derive handler props from hovered point for both tooltip and event handlers
1549
+ let handler_props = $derived.by((): ScatterHandlerProps<Metadata> | null => {
1550
+ if (!tooltip_point) return null
1551
+ return construct_handler_props(tooltip_point)
1552
+ })
1553
+
1554
+ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1)
1555
+
1556
+ // Precompute non-click event names from point_events so we don't rebuild
1557
+ // the entries array on every point render.
1558
+ let point_event_names = $derived(
1559
+ point_events
1560
+ ? Object.keys(point_events).filter((name) => name !== `onclick`)
1561
+ : [],
1562
+ )
1563
+
1564
+ // Set theme-aware background when entering fullscreen
1565
+ $effect(() => {
1566
+ set_fullscreen_bg(wrapper, fullscreen, `--scatter-fullscreen-bg`)
1567
+ })
1568
+
1569
+ // State accessors for shared axis change handler
1570
+ const axis_state: AxisChangeState<DataSeries<Metadata>> = {
1571
+ get_axis: (axis) => {
1572
+ if (axis === `x`) return x_axis
1573
+ if (axis === `x2`) return x2_axis
1574
+ if (axis === `y`) return y_axis
1575
+ return y2_axis
1576
+ },
1577
+ set_axis: (axis, config) => {
1578
+ // Spread into existing state to preserve merged type structure
1579
+ if (axis === `x`) x_axis = { ...x_axis, ...config }
1580
+ else if (axis === `x2`) x2_axis = { ...x2_axis, ...config }
1581
+ else if (axis === `y`) y_axis = { ...y_axis, ...config }
1582
+ else y2_axis = { ...y2_axis, ...config }
1583
+ },
1584
+ get_series: () => series,
1585
+ set_series: (new_series) => (series = new_series),
1586
+ get_loading: () => axis_loading,
1587
+ set_loading: (axis) => (axis_loading = axis),
1588
+ }
1589
+
1590
+ // Shared handler + one-shot auto-load bound to this component's state
1591
+ const { handle_axis_change, try_auto_load } = create_axis_loader(
1592
+ axis_state,
1593
+ () => ({ data_loader, on_axis_change, on_error }),
1594
+ )
1595
+ $effect(try_auto_load)
1596
+ </script>
1597
+
1598
+ {#snippet fill_regions_layer(fills: typeof computed_fills)}
1599
+ {#each fills as fill (fill.hover_key)}
1600
+ {#each fill.path_segments as
1601
+ path_d,
1602
+ segment_idx
1603
+ (`${fill.id ?? fill.idx}-${segment_idx}`)
1604
+ }
1605
+ <FillArea
1606
+ region={fill}
1607
+ region_idx={fill.idx}
1608
+ path={path_d}
1609
+ {clip_path_id}
1610
+ {x_scale_fn}
1611
+ {y_scale_fn}
1612
+ is_hovered={hovered_fill_key === fill.hover_key}
1613
+ on_click={(event: FillHandlerEvent) => {
1614
+ fill.on_click?.(event)
1615
+ on_fill_click?.(event)
1616
+ }}
1617
+ on_hover={(event: FillHandlerEvent | null) => {
1618
+ hovered_fill_key = event ? fill.hover_key : null
1619
+ fill.on_hover?.(event)
1620
+ on_fill_hover?.(event)
1621
+ }}
1622
+ />
1623
+ {/each}
1624
+ {/each}
1625
+ {/snippet}
1626
+
1627
+ {#snippet ref_lines_layer(lines: IndexedRefLine[])}
1628
+ {#each lines as line (line.id ?? line.idx)}
1629
+ <ReferenceLine
1630
+ ref_line={line}
1631
+ line_idx={line.idx}
1632
+ x_min={line.x_axis === `x2` ? x2_min : x_min}
1633
+ x_max={line.x_axis === `x2` ? x2_max : x_max}
1634
+ y_min={line.y_axis === `y2` ? y2_min : y_min}
1635
+ y_max={line.y_axis === `y2` ? y2_max : y_max}
1636
+ x_scale={x_scale_fn}
1637
+ x2_scale={x2_scale_fn}
1638
+ y_scale={y_scale_fn}
1639
+ y2_scale={y2_scale_fn}
1640
+ {clip_path_id}
1641
+ hovered_line_idx={hovered_ref_line_idx}
1642
+ on_click={(event: RefLineEvent) => {
1643
+ line.on_click?.(event)
1644
+ on_ref_line_click?.(event)
1645
+ }}
1646
+ on_hover={(event: RefLineEvent | null) => {
1647
+ hovered_ref_line_idx = event?.line_idx ?? null
1648
+ line.on_hover?.(event)
1649
+ on_ref_line_hover?.(event)
1650
+ }}
1651
+ />
1652
+ {/each}
1653
+ {/snippet}
1654
+
1655
+ <svelte:window
1656
+ onkeydown={(evt) => {
1657
+ if (evt.key === `Escape` && fullscreen) {
1658
+ evt.preventDefault()
1659
+ fullscreen = false
1660
+ }
1661
+ if (evt.key === `Shift`) shift_held = true
1662
+ }}
1663
+ onkeyup={(evt) => {
1664
+ if (evt.key === `Shift`) shift_held = false
1665
+ }}
1666
+ />
1667
+
1668
+ <div
1669
+ bind:this={wrapper}
1670
+ bind:clientWidth={width}
1671
+ bind:clientHeight={height}
1672
+ {...rest}
1673
+ class="scatter {rest.class ?? ``}"
1674
+ class:fullscreen
1675
+ >
1676
+ {#if width && height}
1677
+ <div class="header-controls">
1678
+ {@render header_controls?.({ height, width, fullscreen })}
1679
+ {#if fullscreen_toggle}
1680
+ <FullscreenToggle bind:fullscreen />
1681
+ {/if}
1682
+ </div>
1683
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
1684
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
1685
+ <svg
1686
+ bind:this={svg_element}
1687
+ role="application"
1688
+ aria-label={rest[`aria-label`] ??
1689
+ ([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) ||
1690
+ `Scatter plot`)}
1691
+ tabindex="0"
1692
+ onfocusin={() => (is_focused = true)}
1693
+ onfocusout={() => (is_focused = false)}
1694
+ onmouseenter={() => (hovered = true)}
1695
+ onmousedown={handle_mouse_down}
1696
+ onmousemove={(evt: MouseEvent) => {
1697
+ // Only find closest point if not actively dragging
1698
+ if (!drag_start_coords && !pan_drag_state) on_mouse_move(evt)
1699
+ }}
1700
+ onmouseleave={() => {
1701
+ hovered = false
1702
+ tooltip_point = null
1703
+ on_point_hover?.(null)
1704
+ }}
1705
+ ondblclick={() => {
1706
+ // Reset to current auto ranges (not stale initial_*_range which may have expanded)
1707
+ // This ensures lazy expansion restarts fresh from current data bounds
1708
+ initial_x_range = [...auto_x_range] as [number, number]
1709
+ initial_x2_range = [...auto_x2_range] as [number, number]
1710
+ initial_y_range = [...auto_y_range] as [number, number]
1711
+ initial_y2_range = [...auto_y2_range] as [number, number]
1712
+ zoom_x_range = [...auto_x_range] as [number, number]
1713
+ zoom_x2_range = [...auto_x2_range] as [number, number]
1714
+ zoom_y_range = [...auto_y_range] as [number, number]
1715
+ zoom_y2_range = get_synced_y2(auto_y_range, [...auto_y2_range] as Vec2)
1716
+ // Also reset axis props so future data changes recalculate auto ranges
1717
+ x_axis = { ...x_axis, range: [null, null] }
1718
+ x2_axis = { ...x2_axis, range: [null, null] }
1719
+ y_axis = { ...y_axis, range: [null, null] }
1720
+ y2_axis = { ...y2_axis, range: [null, null] }
1721
+ }}
1722
+ onwheel={handle_wheel}
1723
+ ontouchstart={handle_touch_start}
1724
+ ontouchmove={handle_touch_move}
1725
+ ontouchend={handle_touch_end}
1726
+ ontouchcancel={handle_touch_end}
1727
+ style:cursor={pan_drag_state
1728
+ ? `grabbing`
1729
+ : shift_held && pan?.enabled !== false
1730
+ ? `grab`
1731
+ : `crosshair`}
1732
+ >
1733
+ {@render user_content?.({
1734
+ height,
1735
+ width,
1736
+ x_scale_fn,
1737
+ x2_scale_fn,
1738
+ y_scale_fn,
1739
+ y2_scale_fn,
1740
+ pad,
1741
+ x_range: [x_min, x_max],
1742
+ x2_range: [x2_min, x2_max],
1743
+ y_range: [y_min, y_max],
1744
+ y2_range: [y2_min, y2_max],
1745
+ fullscreen,
1746
+ })}
1747
+
1748
+ <!-- Fill regions: below grid -->
1749
+ {@render fill_regions_layer(fills_by_z.below_grid)}
1750
+ <!-- Reference lines: below grid -->
1751
+ {@render ref_lines_layer(ref_lines_by_z.below_grid)}
1752
+
1753
+ <PlotAxis
1754
+ side="x"
1755
+ ticks={x_tick_values}
1756
+ place={(tick) => (is_time_x ? x_scale_fn(new Date(tick)) : x_scale_fn(tick))}
1757
+ axis={final_x_axis}
1758
+ {pad}
1759
+ {width}
1760
+ {height}
1761
+ show_grid={final_display.x_grid}
1762
+ show_baseline={false}
1763
+ domain={[x_min, x_max]}
1764
+ tick_label={(tick) => get_tick_label(tick, final_x_axis.ticks)}
1765
+ label_x={width / 2 + (final_x_axis.label_shift?.x ?? 0)}
1766
+ label_y={height - pad.b - (final_x_axis.label_shift?.y ?? -40)}
1767
+ axis_loading={axis_loading === `x`}
1768
+ on_axis_change={(key) => handle_axis_change(`x`, key)}
1769
+ />
1770
+
1771
+ <!-- Current frame indicator -->
1772
+ {#if current_x_value != null}
1773
+ {@const current_pos_raw = is_time_x
1774
+ ? x_scale_fn(new Date(current_x_value))
1775
+ : x_scale_fn(current_x_value)}
1776
+ {#if isFinite(current_pos_raw)}
1777
+ {@const current_pos = current_pos_raw}
1778
+ {#if current_pos >= pad.l && current_pos <= width - pad.r}
1779
+ {@const active_tick_height = 7}
1780
+ <rect
1781
+ x={current_pos - 1.5}
1782
+ y={height - pad.b - active_tick_height / 2}
1783
+ width="3"
1784
+ height={active_tick_height}
1785
+ fill="var(--scatter-current-frame-color, #ff6b35)"
1786
+ stroke="white"
1787
+ stroke-width="1"
1788
+ class="current-frame-indicator"
1789
+ />
1790
+ {/if}
1791
+ {/if}
1792
+ {/if}
1793
+
1794
+ <PlotAxis
1795
+ side="y"
1796
+ ticks={y_tick_values}
1797
+ place={y_scale_fn}
1798
+ axis={final_y_axis}
1799
+ {pad}
1800
+ {width}
1801
+ {height}
1802
+ show_grid={final_display.y_grid}
1803
+ show_baseline={false}
1804
+ domain={[y_min, y_max]}
1805
+ unit_on_first_tick
1806
+ tick_label={(tick) => get_tick_label(tick, final_y_axis.ticks)}
1807
+ label_x={Math.max(
1808
+ 12,
1809
+ pad.l - (final_y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
1810
+ LABEL_GAP_DEFAULT,
1811
+ ) + (final_y_axis.label_shift?.x ?? 0)}
1812
+ label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y_axis.label_shift?.y ?? 0)}
1813
+ axis_loading={axis_loading === `y`}
1814
+ on_axis_change={(key) => handle_axis_change(`y`, key)}
1815
+ />
1816
+
1817
+ <!-- Y2-axis (Right) -->
1818
+ {#if y2_points.length > 0}
1819
+ <PlotAxis
1820
+ side="y2"
1821
+ ticks={y2_tick_values}
1822
+ place={y2_scale_fn}
1823
+ axis={final_y2_axis}
1824
+ {pad}
1825
+ {width}
1826
+ {height}
1827
+ show_grid={final_display.y2_grid}
1828
+ show_baseline={false}
1829
+ domain={[y2_min, y2_max]}
1830
+ unit_on_first_tick
1831
+ tick_label={(tick) => get_tick_label(tick, final_y2_axis.ticks)}
1832
+ label_x={y2_axis_label_x(final_y2_axis, width, pad.r, tick_label_widths.y2_max)}
1833
+ label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y2_axis.label_shift?.y ?? 0)}
1834
+ axis_loading={axis_loading === `y2`}
1835
+ on_axis_change={(key) => handle_axis_change(`y2`, key)}
1836
+ />
1837
+ {/if}
1838
+
1839
+ <!-- X2-axis (Top) -->
1840
+ {#if x2_points.length > 0}
1841
+ <PlotAxis
1842
+ side="x2"
1843
+ ticks={x2_tick_values}
1844
+ place={(tick) => (is_time_x2 ? x2_scale_fn(new Date(tick)) : x2_scale_fn(tick))}
1845
+ axis={final_x2_axis}
1846
+ {pad}
1847
+ {width}
1848
+ {height}
1849
+ show_grid={final_display.x2_grid}
1850
+ show_baseline={false}
1851
+ domain={[x2_min, x2_max]}
1852
+ tick_label={(tick) => get_tick_label(tick, final_x2_axis.ticks)}
1853
+ label_x={width / 2 + (final_x2_axis.label_shift?.x ?? 0)}
1854
+ label_y={Math.max(12, pad.t - (final_x2_axis.label_shift?.y ?? 40))}
1855
+ axis_loading={axis_loading === `x2`}
1856
+ on_axis_change={(key) => handle_axis_change(`x2`, key)}
1857
+ />
1858
+ {/if}
1859
+
1860
+ <!-- Tooltip rendered inside overlay (moved outside SVG for stacking above colorbar) -->
1861
+
1862
+ <ZoomRect start={drag_start_coords} current={drag_current_coords} />
1863
+
1864
+ <ZeroLines
1865
+ display={final_display}
1866
+ {x_scale_fn}
1867
+ {x2_scale_fn}
1868
+ {y_scale_fn}
1869
+ {y2_scale_fn}
1870
+ x_range={zoom_x_range}
1871
+ x2_range={zoom_x2_range}
1872
+ y_range={zoom_y_range}
1873
+ y2_range={zoom_y2_range}
1874
+ x_scale_type={final_x_axis.scale_type}
1875
+ x2_scale_type={final_x2_axis.scale_type}
1876
+ y_scale_type={final_y_axis.scale_type}
1877
+ y2_scale_type={final_y2_axis.scale_type}
1878
+ x_is_time={is_time_x}
1879
+ x2_is_time={is_time_x2}
1880
+ has_x2={x2_points.length > 0}
1881
+ has_y2={y2_points.length > 0}
1882
+ {width}
1883
+ {height}
1884
+ {pad}
1885
+ />
1886
+
1887
+ <defs>
1888
+ <clipPath id={clip_path_id}>
1889
+ <rect
1890
+ x={clip_area.x}
1891
+ y={clip_area.y}
1892
+ width={clip_area.width}
1893
+ height={clip_area.height}
1894
+ />
1895
+ </clipPath>
1896
+ </defs>
1897
+
1898
+ <!-- Fill regions: below lines (default z-index) -->
1899
+ {@render fill_regions_layer(fills_by_z.below_lines)}
1900
+ <!-- Reference lines: below lines (default z-index) -->
1901
+ {@render ref_lines_layer(ref_lines_by_z.below_lines)}
1902
+
1903
+ <!-- Lines -->
1904
+ {#if styles.show_lines}
1905
+ {#each filtered_series ?? [] as series_data (series_data._id)}
1906
+ {@const series_markers = series_data.markers ?? DEFAULT_MARKERS}
1907
+ {@const series_default_color = get_series_color(series_data.orig_series_idx ?? 0)}
1908
+ <g
1909
+ data-series-id={series_data._id}
1910
+ clip-path="url(#{clip_path_id})"
1911
+ opacity={hovered_legend_series_idx !== null &&
1912
+ hovered_legend_series_idx !== series_data.orig_series_idx
1913
+ ? 0.25
1914
+ : 1}
1915
+ >
1916
+ {#if series_markers?.includes(`line`)}
1917
+ {@const all_line_points = series_data.x.map((x, idx) => ({
1918
+ x,
1919
+ y: series_data.y[idx],
1920
+ }))}
1921
+ {@const finite_screen_points = all_line_points
1922
+ .map((point) => get_screen_coords(point, series_data))
1923
+ .filter(([sx, sy]) => isFinite(sx) && isFinite(sy))}
1924
+ {@const apply_line_controls = controls.show &&
1925
+ (!has_multiple_series ||
1926
+ series_data._id === series_with_ids[selected_series_idx]?._id)}
1927
+ {@const ls = series_data.line_style}
1928
+ {@const tc = (key: string) => apply_line_controls && touched.has(key)}
1929
+ {@const color_fallback = ls?.stroke ??
1930
+ (Array.isArray(series_data.point_style)
1931
+ ? series_data.point_style[0]?.fill
1932
+ : series_data.point_style?.fill) ??
1933
+ (series_data.color_values?.[0] != null
1934
+ ? color_scale_fn(series_data.color_values[0])
1935
+ : series_default_color)}
1936
+ <Line
1937
+ points={finite_screen_points}
1938
+ origin={[
1939
+ is_time_x ? x_scale_fn(new Date(x_min)) : x_scale_fn(x_min),
1940
+ series_data.y_axis === `y2` ? y2_scale_fn(y2_min) : y_scale_fn(y_min),
1941
+ ]}
1942
+ line_color={(tc(`line.color`) ? styles.line?.color : null) ?? color_fallback}
1943
+ line_width={(tc(`line.width`) ? styles.line?.width : null) ?? ls?.stroke_width ?? 2}
1944
+ line_dash={(tc(`line.dash`) ? styles.line?.dash : null) ?? ls?.line_dash}
1945
+ area_color="transparent"
1946
+ line_tween={effective_line_tween}
1947
+ />
1948
+ {/if}
1949
+ </g>
1950
+ {/each}
1951
+ {/if}
1952
+
1953
+ <!-- Fill regions: below points -->
1954
+ {@render fill_regions_layer(fills_by_z.below_points)}
1955
+ <!-- Reference lines: below points -->
1956
+ {@render ref_lines_layer(ref_lines_by_z.below_points)}
1957
+
1958
+ <!-- Points -->
1959
+ {#if styles.show_points}
1960
+ {#each filtered_series ?? [] as series_data (series_data._id)}
1961
+ {@const series_markers = series_data.markers ?? DEFAULT_MARKERS}
1962
+ {@const series_default_color = get_series_color(series_data.orig_series_idx ?? 0)}
1963
+ {@const series_default_symbol = get_series_symbol(series_data.orig_series_idx ?? 0)}
1964
+ <g data-series-id={series_data._id}>
1965
+ {#if series_markers?.includes(`points`)}
1966
+ {#each series_data.filtered_data as
1967
+ point
1968
+ (`${point.series_idx}-${point.point_idx}`)
1969
+ }
1970
+ {@const label_id = `${point.series_idx}-${point.point_idx}`}
1971
+ {@const calculated_label_pos = label_positions[label_id]}
1972
+ {@const point_label = point.point_label ?? {}}
1973
+ {@const label_style = point_label.auto_placement &&
1974
+ actual_label_config.max_neighbors &&
1975
+ !calculated_label_pos
1976
+ ? {}
1977
+ : point_label}
1978
+ {@const final_label = calculated_label_pos
1979
+ ? {
1980
+ ...label_style,
1981
+ offset: {
1982
+ x: calculated_label_pos.x -
1983
+ (is_time_x ? x_scale_fn(new Date(point.x)) : x_scale_fn(point.x)),
1984
+ y: calculated_label_pos.y - (series_data.y_axis === `y2`
1985
+ ? y2_scale_fn(point.y)
1986
+ : y_scale_fn(point.y)),
1987
+ },
1988
+ }
1989
+ : label_style}
1990
+ {@const [raw_screen_x, raw_screen_y] = get_screen_coords(point, series_data)}
1991
+ {@const screen_x = isFinite(raw_screen_x) ? raw_screen_x : x_scale_fn.range()[0]}
1992
+ {@const screen_y = isFinite(raw_screen_y)
1993
+ ? raw_screen_y
1994
+ : (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn).range()[0]}
1995
+ {@const apply_controls = controls.show &&
1996
+ (!has_multiple_series ||
1997
+ series_data._id === series_with_ids[selected_series_idx]?._id)}
1998
+ {@const pt = point.point_style}
1999
+ {@const tc = (key: string) => apply_controls && touched.has(key)}
2000
+ {@const computed_radius = point.size_value != null
2001
+ ? size_scale_fn(point.size_value)
2002
+ : (tc(`point.size`) ? styles.point?.size : null) ?? pt?.radius ?? 4}
2003
+ <ScatterPoint
2004
+ x={screen_x}
2005
+ y={screen_y}
2006
+ is_dimmed={hovered_legend_series_idx !== null &&
2007
+ hovered_legend_series_idx !== point.series_idx}
2008
+ is_hovered={tooltip_point?.series_idx === point.series_idx &&
2009
+ tooltip_point?.point_idx === point.point_idx}
2010
+ is_selected={selected_point?.series_idx === point.series_idx &&
2011
+ selected_point?.point_idx === point.point_idx}
2012
+ leader_line_threshold={actual_label_config.leader_line_threshold}
2013
+ style={{
2014
+ symbol_type: pt?.symbol_type ?? series_default_symbol,
2015
+ ...pt,
2016
+ radius: computed_radius,
2017
+ stroke_width:
2018
+ (tc(`point.stroke_width`) ? styles.point?.stroke_width : null) ??
2019
+ pt?.stroke_width ?? 1,
2020
+ stroke:
2021
+ (tc(`point.stroke_color`) ? styles.point?.stroke_color : null) ??
2022
+ pt?.stroke ?? `#000`,
2023
+ stroke_opacity:
2024
+ (tc(`point.stroke_opacity`) ? styles.point?.stroke_opacity : null) ??
2025
+ pt?.stroke_opacity ?? 1,
2026
+ fill_opacity: (tc(`point.opacity`) ? styles.point?.opacity : null) ??
2027
+ pt?.fill_opacity ?? 1,
2028
+ cursor: (on_point_click || point_events?.onclick)
2029
+ ? `pointer`
2030
+ : undefined,
2031
+ }}
2032
+ hover={point.point_hover ?? {}}
2033
+ label={final_label}
2034
+ offset={point.point_offset ?? { x: 0, y: 0 }}
2035
+ {point_tween}
2036
+ origin={{ x: plot_center_x, y: plot_center_y }}
2037
+ --point-fill-color={point.color_value != null
2038
+ ? color_scale_fn(point.color_value)
2039
+ : (tc(`point.color`) ? styles.point?.color : null) ?? pt?.fill ??
2040
+ series_default_color}
2041
+ {...point_events &&
2042
+ Object.fromEntries(
2043
+ point_event_names.map((name) => [name, (event: Event) =>
2044
+ point_events?.[name]?.({ point, event })]
2045
+ ),
2046
+ )}
2047
+ onclick={(event: MouseEvent) => {
2048
+ // Call user-provided onclick handler first if it exists
2049
+ point_events?.onclick?.({ point, event })
2050
+ // then handle internal logic
2051
+ const props = construct_handler_props(point)
2052
+ tooltip_point = point
2053
+ if (props) on_point_click?.({ ...props, event, point })
2054
+ }}
2055
+ />
2056
+ {/each}
2057
+ {/if}
2058
+ </g>
2059
+ {/each}
2060
+ {/if}
2061
+
2062
+ <!-- Fill regions: above all -->
2063
+ {@render fill_regions_layer(fills_by_z.above_all)}
2064
+ <!-- Reference lines: above all -->
2065
+ {@render ref_lines_layer(ref_lines_by_z.above_all)}
2066
+ </svg>
2067
+
2068
+ <!-- Tooltip overlay above all plot overlays (legend, colorbar) -->
2069
+ {#if handler_props && hovered && tooltip_point}
2070
+ {@const { point_label, series_idx } = tooltip_point}
2071
+ {@const tooltip_bg_color = pick_tooltip_bg(
2072
+ tooltip_point,
2073
+ series_with_ids[series_idx],
2074
+ color_scale_fn,
2075
+ )}
2076
+ <PlotTooltip
2077
+ x={handler_props.cx}
2078
+ y={handler_props.cy}
2079
+ offset={{ x: 10, y: 5 }}
2080
+ constrain_to={{ width, height }}
2081
+ fallback_size={{ width: 120, height: 50 }}
2082
+ bg_color={tooltip_bg_color}
2083
+ >
2084
+ {#if tooltip}
2085
+ {@render tooltip(handler_props)}
2086
+ {:else}
2087
+ {@const hp = handler_props}
2088
+ {#if has_multiple_series && hp.label}<strong>{hp.label}</strong><br />{/if}
2089
+ {@html sanitize_html(point_label?.text ? `${point_label.text}<br />` : ``)}
2090
+ {@html sanitize_html(hp.x_axis.label || `x`)}: {hp.x_formatted}<br />
2091
+ {@html sanitize_html(hp.y_axis.label || `y`)}: {hp.y_formatted}
2092
+ {#if hp.colorbar?.value != null}
2093
+ <br />{@html sanitize_html(hp.colorbar.title || `Color`)}: {
2094
+ format_value(hp.colorbar.value, hp.colorbar.tick_format || `.3~g`)
2095
+ }
2096
+ {/if}
2097
+ {/if}
2098
+ </PlotTooltip>
2099
+ {/if}
2100
+
2101
+ <!-- Control Pane -->
2102
+ {#if controls.show}
2103
+ <ScatterPlotControls
2104
+ toggle_props={{
2105
+ ...controls.toggle_props,
2106
+ style:
2107
+ `--ctrl-btn-right: var(--fullscreen-btn-offset, 30px); top: var(--ctrl-btn-top, 5pt); ${
2108
+ controls.toggle_props?.style ?? ``
2109
+ }`,
2110
+ }}
2111
+ pane_props={controls.pane_props}
2112
+ bind:x_axis
2113
+ bind:x2_axis
2114
+ bind:y_axis
2115
+ bind:y2_axis
2116
+ bind:display
2117
+ bind:styles
2118
+ {auto_x_range}
2119
+ {auto_x2_range}
2120
+ {auto_y_range}
2121
+ {auto_y2_range}
2122
+ bind:selected_series_idx
2123
+ series={series_with_ids}
2124
+ has_x2_points={x2_points.length > 0}
2125
+ has_y2_points={y2_points.length > 0}
2126
+ children={controls_extra}
2127
+ on_touch={(key: string) => touched.add(key)}
2128
+ />
2129
+ {/if}
2130
+
2131
+ <!-- Color Bar -->
2132
+ {#if color_bar && all_color_values.length > 0 && color_bar_placement}
2133
+ {@const color_domain = [
2134
+ (typeof color_scale === `string` ? undefined : color_scale.value_range)?.[0] ??
2135
+ auto_color_range[0],
2136
+ (typeof color_scale === `string` ? undefined : color_scale.value_range)?.[1] ??
2137
+ auto_color_range[1],
2138
+ ] as Vec2}
2139
+ <div
2140
+ bind:this={colorbar_element}
2141
+ onmouseenter={() => colorbar_hover.set_locked(true)}
2142
+ onmouseleave={() => colorbar_hover.set_locked(false)}
2143
+ class="colorbar-wrapper"
2144
+ role="img"
2145
+ aria-label="Color scale legend"
2146
+ style={`${
2147
+ // explicit wrapper_style or auto-outside places the colorbar; else auto-placement coords
2148
+ effective_cbar_wrapper_style ??
2149
+ `position: absolute; left: ${tweened_colorbar_coords.current.x}px; top: ${tweened_colorbar_coords.current.y}px`}; pointer-events: auto;`}
2150
+ >
2151
+ <ColorBar
2152
+ tick_labels={4}
2153
+ tick_side="primary"
2154
+ {color_scale_fn}
2155
+ color_scale_domain={color_domain}
2156
+ scale_type={typeof color_scale === `string` ? undefined : color_scale.type}
2157
+ range={color_domain?.every((val) => val != null) ? color_domain : undefined}
2158
+ bar_style="width: 220px; height: 16px; {color_bar?.style ?? ``}"
2159
+ {...color_bar}
2160
+ wrapper_style={effective_cbar_wrapper_style ? `height: 100%; width: 100%;` : ``}
2161
+ />
2162
+ </div>
2163
+ {/if}
2164
+
2165
+ <!-- Legend -->
2166
+ <!-- Only render if multiple series or if legend prop was explicitly provided by user (even if empty object) -->
2167
+ {#if legend != null && legend_data.length > 0 &&
2168
+ (legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)}
2169
+ {@const default_x = pad.l + 10}
2170
+ {@const default_y = pad.t + 10}
2171
+ {@const current_x = legend_is_dragging && legend_manual_position
2172
+ ? legend_manual_position.x
2173
+ : legend_auto_outside
2174
+ ? legend_outside_x
2175
+ : legend_placement
2176
+ ? tweened_legend_coords.current.x
2177
+ : default_x}
2178
+ {@const current_y = legend_is_dragging && legend_manual_position
2179
+ ? legend_manual_position.y
2180
+ : legend_auto_outside
2181
+ ? legend_outside_y
2182
+ : legend_placement
2183
+ ? tweened_legend_coords.current.y
2184
+ : default_y}
2185
+ <PlotLegend
2186
+ bind:root_element={legend_element}
2187
+ series_data={legend_data}
2188
+ on_drag_start={handle_legend_drag_start}
2189
+ on_drag={handle_legend_drag}
2190
+ on_drag_end={() => (legend_is_dragging = false)}
2191
+ on_hover_change={legend_hover.set_locked}
2192
+ on_item_hover={(item) => {
2193
+ if (item?.item_type === `fill`) {
2194
+ // highlight the matching fill in the plot (same state plot fill-hover uses), but skip
2195
+ // hidden fills since they render nothing and would mark the legend item active for naught
2196
+ const fill = computed_fills.find((entry) => entry.idx === item.fill_idx)
2197
+ hovered_fill_key = fill && fill.visible !== false ? fill.hover_key : null
2198
+ hovered_legend_series_idx = null
2199
+ } else {
2200
+ hovered_legend_series_idx = item != null && item.series_idx >= 0
2201
+ ? item.series_idx
2202
+ : null
2203
+ hovered_fill_key = null
2204
+ }
2205
+ }}
2206
+ active_series_idx={tooltip_point?.series_idx ?? hovered_legend_series_idx}
2207
+ active_fill_idx={computed_fills.find((fill) => fill.hover_key === hovered_fill_key)?.idx ??
2208
+ null}
2209
+ draggable={legend?.draggable ?? true}
2210
+ {...legend}
2211
+ on_toggle={legend?.on_toggle ?? legend_vis.on_toggle}
2212
+ on_double_click={legend?.on_double_click ?? legend_vis.on_double_click}
2213
+ on_group_toggle={legend?.on_group_toggle ?? legend_vis.on_group_toggle}
2214
+ on_fill_toggle={(source_type: `fill_region` | `error_band`, source_idx: number) => {
2215
+ // Only fill_regions can be toggled (error_bands are not bindable)
2216
+ if (source_type === `fill_region`) {
2217
+ fill_regions = fill_regions.map((region, idx) =>
2218
+ idx === source_idx
2219
+ ? { ...region, visible: !(region.visible !== false) }
2220
+ : region
2221
+ )
2222
+ }
2223
+ }}
2224
+ on_fill_double_click={(
2225
+ source_type: `fill_region` | `error_band`,
2226
+ source_idx: number,
2227
+ ) => {
2228
+ // Only fill_regions can be toggled (error_bands are not bindable)
2229
+ if (source_type !== `fill_region`) return
2230
+ // Toggle: if only this fill is visible, show all; otherwise show only this one
2231
+ const visible_count = fill_regions.filter((region) => region.visible !== false).length
2232
+ const this_visible = fill_regions[source_idx]?.visible !== false
2233
+ if (visible_count === 1 && this_visible) {
2234
+ // Show all fills
2235
+ fill_regions = fill_regions.map((region) => ({ ...region, visible: true }))
2236
+ } else {
2237
+ // Show only this fill
2238
+ fill_regions = fill_regions.map((region, idx) => ({
2239
+ ...region,
2240
+ visible: idx === source_idx,
2241
+ }))
2242
+ }
2243
+ }}
2244
+ style={`
2245
+ position: absolute;
2246
+ left: ${current_x}px;
2247
+ top: ${current_y}px;
2248
+ pointer-events: auto;
2249
+ ${legend?.style ?? ``}
2250
+ `}
2251
+ />
2252
+ {/if}
2253
+ {/if}
2254
+
2255
+ <!-- User-provided children (e.g. for custom absolutely-positioned overlays) -->
2256
+ {@render children?.({ height, width, fullscreen })}
2257
+ </div>
2258
+
2259
+ <style>
2260
+ div.scatter {
2261
+ position: relative; /* Needed for absolute positioning of children like ColorBar */
2262
+ width: var(--scatter-width, 100%);
2263
+ height: var(--scatter-height, auto);
2264
+ min-height: var(--scatter-min-height, 350px);
2265
+ container-type: size; /* enable cqh for panes */
2266
+ container-name: scatter-plot;
2267
+ z-index: var(--scatter-z-index);
2268
+ flex: var(--scatter-flex, 1); /* Allow filling available space in flex containers */
2269
+ display: var(--scatter-display, flex);
2270
+ flex-direction: column;
2271
+ background: var(--scatter-bg, var(--plot-bg));
2272
+ border-radius: var(--scatter-border-radius, var(--border-radius, 3pt));
2273
+ }
2274
+ div.scatter.fullscreen {
2275
+ position: fixed;
2276
+ top: 0;
2277
+ left: 0;
2278
+ width: 100vw !important;
2279
+ height: 100vh !important;
2280
+ /* Must be higher than Structure.svelte's --struct-buttons-z-index. */
2281
+ z-index: var(--scatter-fullscreen-z-index, var(--z-index-overlay-nav, 100000001));
2282
+ margin: 0;
2283
+ border-radius: 0;
2284
+ background: var(--scatter-fullscreen-bg, var(--scatter-bg, var(--plot-bg)));
2285
+ max-height: none !important;
2286
+ overflow: hidden;
2287
+ /* border-top (not padding-top): bind:clientHeight includes padding but excludes
2288
+ borders - padding made the chart overflow + clip its bottom 2em (x-axis title) */
2289
+ border-top: var(--plot-fullscreen-padding-top, 2em) solid
2290
+ var(--scatter-fullscreen-bg, var(--scatter-bg, var(--plot-bg, transparent)));
2291
+ box-sizing: border-box;
2292
+ }
2293
+ /* Center the colorbar within its wrapper when shorter than it (e.g. capped by --cbar-max-height
2294
+ in fullscreen). Users can override via wrapper_style (inline wins). */
2295
+ .colorbar-wrapper {
2296
+ display: flex;
2297
+ align-items: center;
2298
+ justify-content: center;
2299
+ }
2300
+ .header-controls {
2301
+ position: absolute;
2302
+ top: var(--ctrl-btn-top, 5pt);
2303
+ right: var(--fullscreen-btn-right, 4px);
2304
+ z-index: var(--fullscreen-btn-z-index, 10);
2305
+ display: flex;
2306
+ align-items: center;
2307
+ gap: 8px;
2308
+ }
2309
+ .header-controls :global(.fullscreen-toggle) {
2310
+ position: static; /* Override absolute positioning since container handles it */
2311
+ opacity: 1; /* Always visible when inside header-controls, container controls visibility */
2312
+ }
2313
+ /* Hide controls and fullscreen toggles by default, show on hover */
2314
+ div.scatter :global(.pane-toggle),
2315
+ div.scatter .header-controls {
2316
+ opacity: 0;
2317
+ transition: opacity 0.2s, background-color 0.2s;
2318
+ }
2319
+ div.scatter:hover :global(.pane-toggle),
2320
+ div.scatter:hover .header-controls,
2321
+ div.scatter :global(.pane-toggle:focus-visible),
2322
+ div.scatter :global(.pane-toggle[aria-expanded='true']),
2323
+ div.scatter .header-controls:focus-within {
2324
+ opacity: 1;
2325
+ }
2326
+ svg {
2327
+ width: var(--scatter-svg-width, 100%);
2328
+ height: var(--scatter-svg-height, 100%);
2329
+ flex: var(--scatter-svg-flex, 1);
2330
+ overflow: var(--scatter-svg-overflow, visible);
2331
+ fill: var(--text-color);
2332
+ font-weight: var(--scatter-font-weight);
2333
+ font-size: var(--scatter-font-size);
2334
+ }
2335
+ .scatter :global(.axis-label) {
2336
+ text-align: center;
2337
+ width: 100%;
2338
+ height: 100%;
2339
+ font-size: var(--scatter-font-size, inherit);
2340
+ font-weight: var(--scatter-font-weight, normal);
2341
+ color: var(--text-color);
2342
+ white-space: nowrap;
2343
+ /* Use line-height to center text vertically without flexbox */
2344
+ line-height: var(
2345
+ --scatter-axis-label-line-height,
2346
+ 20px
2347
+ ); /* Match foreignObject height */
2348
+ display: block;
2349
+ }
2350
+ .current-frame-indicator {
2351
+ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2));
2352
+ transition: opacity 0.2s ease;
2353
+ }
2354
+ .current-frame-indicator:hover {
2355
+ opacity: 0.8;
2356
+ }
2357
+ </style>