matterviz 0.3.7 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) hide show
  1. package/dist/Icon.svelte +7 -4
  2. package/dist/MillerIndexInput.svelte +1 -1
  3. package/dist/api/optimade.js +32 -26
  4. package/dist/app.css +0 -3
  5. package/dist/brillouin/BrillouinZone.svelte +76 -148
  6. package/dist/brillouin/BrillouinZone.svelte.d.ts +6 -14
  7. package/dist/brillouin/BrillouinZoneExportPane.svelte +43 -96
  8. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneInfoPane.svelte +9 -32
  10. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
  11. package/dist/brillouin/BrillouinZoneScene.svelte +97 -205
  12. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +4 -23
  13. package/dist/brillouin/BrillouinZoneTooltip.svelte +16 -25
  14. package/dist/brillouin/ReciprocalVectors.svelte +39 -0
  15. package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
  16. package/dist/brillouin/compute.d.ts +2 -0
  17. package/dist/brillouin/compute.js +89 -90
  18. package/dist/brillouin/geometry.d.ts +8 -0
  19. package/dist/brillouin/geometry.js +57 -0
  20. package/dist/brillouin/index.d.ts +2 -0
  21. package/dist/brillouin/index.js +2 -0
  22. package/dist/brillouin/types.d.ts +2 -2
  23. package/dist/chempot-diagram/ChemPotDiagram.svelte +14 -13
  24. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
  25. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +109 -203
  26. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
  27. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +180 -470
  28. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
  29. package/dist/chempot-diagram/async-compute.svelte.js +3 -1
  30. package/dist/chempot-diagram/chempot-worker.js +2 -1
  31. package/dist/chempot-diagram/color.d.ts +3 -6
  32. package/dist/chempot-diagram/color.js +5 -5
  33. package/dist/chempot-diagram/compute.d.ts +4 -4
  34. package/dist/chempot-diagram/compute.js +20 -20
  35. package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
  36. package/dist/chempot-diagram/controls-state.svelte.js +42 -0
  37. package/dist/chempot-diagram/export.d.ts +47 -0
  38. package/dist/chempot-diagram/export.js +133 -0
  39. package/dist/chempot-diagram/index.d.ts +1 -0
  40. package/dist/chempot-diagram/index.js +1 -0
  41. package/dist/chempot-diagram/pointer.d.ts +0 -10
  42. package/dist/chempot-diagram/pointer.js +4 -4
  43. package/dist/chempot-diagram/types.d.ts +3 -3
  44. package/dist/colors/index.js +8 -7
  45. package/dist/composition/FormulaFilter.svelte +18 -11
  46. package/dist/composition/PieChart.svelte +11 -10
  47. package/dist/composition/chem-sys.d.ts +8 -0
  48. package/dist/composition/chem-sys.js +86 -0
  49. package/dist/composition/format.js +7 -4
  50. package/dist/composition/index.d.ts +1 -0
  51. package/dist/composition/index.js +1 -0
  52. package/dist/composition/parse.d.ts +0 -1
  53. package/dist/composition/parse.js +41 -31
  54. package/dist/controls.d.ts +1 -0
  55. package/dist/controls.js +0 -1
  56. package/dist/convex-hull/ConvexHull.svelte +8 -10
  57. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
  58. package/dist/convex-hull/ConvexHull2D.svelte +106 -185
  59. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  60. package/dist/convex-hull/ConvexHull3D.svelte +179 -683
  61. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  62. package/dist/convex-hull/ConvexHull4D.svelte +183 -687
  63. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  64. package/dist/convex-hull/ConvexHullChrome.svelte +268 -0
  65. package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
  66. package/dist/convex-hull/ConvexHullControls.svelte +88 -7
  67. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
  68. package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
  69. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
  70. package/dist/convex-hull/ConvexHullStats.svelte +36 -175
  71. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
  72. package/dist/convex-hull/ConvexHullTooltip.svelte +11 -2
  73. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
  74. package/dist/convex-hull/GasPressureControls.svelte +4 -4
  75. package/dist/convex-hull/TemperatureSlider.svelte +2 -2
  76. package/dist/convex-hull/barycentric-coords.d.ts +2 -4
  77. package/dist/convex-hull/barycentric-coords.js +6 -33
  78. package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
  79. package/dist/convex-hull/canvas-interactions.svelte.js +278 -0
  80. package/dist/convex-hull/demo-temperature.d.ts +1 -1
  81. package/dist/convex-hull/demo-temperature.js +20 -22
  82. package/dist/convex-hull/gas-thermodynamics.d.ts +2 -2
  83. package/dist/convex-hull/gas-thermodynamics.js +22 -30
  84. package/dist/convex-hull/helpers.d.ts +42 -7
  85. package/dist/convex-hull/helpers.js +171 -78
  86. package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
  87. package/dist/convex-hull/hull-state.svelte.js +124 -0
  88. package/dist/convex-hull/index.d.ts +10 -8
  89. package/dist/convex-hull/index.js +7 -2
  90. package/dist/convex-hull/thermodynamics.js +136 -960
  91. package/dist/convex-hull/types.d.ts +13 -5
  92. package/dist/convex-hull/types.js +12 -0
  93. package/dist/coordination/CoordinationBarPlot.svelte +27 -34
  94. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  95. package/dist/element/BohrAtom.svelte +2 -1
  96. package/dist/element/index.d.ts +4 -0
  97. package/dist/element/index.js +18 -0
  98. package/dist/feedback/DragOverlay.svelte +3 -1
  99. package/dist/feedback/DragOverlay.svelte.d.ts +1 -0
  100. package/dist/feedback/StatusMessage.svelte +13 -3
  101. package/dist/fermi-surface/FermiSlice.svelte +13 -5
  102. package/dist/fermi-surface/FermiSurface.svelte +78 -151
  103. package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -14
  104. package/dist/fermi-surface/FermiSurfaceControls.svelte +1 -1
  105. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  106. package/dist/fermi-surface/FermiSurfaceScene.svelte +72 -221
  107. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
  108. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +8 -34
  109. package/dist/fermi-surface/compute.js +67 -66
  110. package/dist/fermi-surface/export.js +6 -16
  111. package/dist/fermi-surface/index.d.ts +0 -1
  112. package/dist/fermi-surface/index.js +0 -1
  113. package/dist/fermi-surface/parse.d.ts +1 -1
  114. package/dist/fermi-surface/parse.js +71 -79
  115. package/dist/fermi-surface/types.d.ts +3 -2
  116. package/dist/heatmap-matrix/HeatmapMatrix.svelte +69 -52
  117. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +4 -3
  118. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +3 -2
  119. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
  120. package/dist/heatmap-matrix/index.d.ts +3 -2
  121. package/dist/heatmap-matrix/index.js +1 -1
  122. package/dist/index.d.ts +1 -0
  123. package/dist/index.js +1 -0
  124. package/dist/io/ExportPane.svelte +166 -0
  125. package/dist/io/ExportPane.svelte.d.ts +17 -0
  126. package/dist/io/decompress.js +5 -4
  127. package/dist/io/export.d.ts +9 -5
  128. package/dist/io/export.js +77 -51
  129. package/dist/io/fetch.d.ts +2 -1
  130. package/dist/io/fetch.js +5 -1
  131. package/dist/io/file-drop.d.ts +8 -1
  132. package/dist/io/file-drop.js +48 -36
  133. package/dist/io/index.d.ts +2 -0
  134. package/dist/io/index.js +10 -0
  135. package/dist/io/types.d.ts +13 -0
  136. package/dist/io/url-drop.js +64 -33
  137. package/dist/isosurface/parse.js +52 -51
  138. package/dist/isosurface/slice.js +5 -4
  139. package/dist/isosurface/types.js +1 -1
  140. package/dist/keyboard.d.ts +3 -0
  141. package/dist/keyboard.js +23 -0
  142. package/dist/labels.d.ts +1 -1
  143. package/dist/labels.js +9 -8
  144. package/dist/layout/FullscreenButton.svelte +33 -0
  145. package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
  146. package/dist/layout/FullscreenToggle.svelte +8 -14
  147. package/dist/layout/PropertyFilter.svelte +3 -2
  148. package/dist/layout/SettingsSection.svelte +1 -1
  149. package/dist/layout/ViewerChrome.svelte +116 -0
  150. package/dist/layout/ViewerChrome.svelte.d.ts +17 -0
  151. package/dist/layout/fullscreen.d.ts +4 -0
  152. package/dist/layout/fullscreen.svelte.d.ts +8 -0
  153. package/dist/layout/fullscreen.svelte.js +37 -0
  154. package/dist/layout/index.d.ts +3 -0
  155. package/dist/layout/index.js +3 -0
  156. package/dist/layout/json-tree/JsonNode.svelte +1 -1
  157. package/dist/layout/json-tree/JsonTree.svelte +2 -2
  158. package/dist/layout/json-tree/utils.js +5 -4
  159. package/dist/marching-cubes.js +8 -13
  160. package/dist/math.d.ts +12 -4
  161. package/dist/math.js +42 -30
  162. package/dist/overlays/DraggablePane.svelte +4 -4
  163. package/dist/overlays/index.d.ts +4 -0
  164. package/dist/periodic-table/PeriodicTable.svelte +27 -15
  165. package/dist/periodic-table/PropertySelect.svelte +1 -0
  166. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +9 -3
  167. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  168. package/dist/phase-diagram/PhaseDiagramControls.svelte +3 -2
  169. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
  170. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +4 -2
  171. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
  172. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +47 -132
  173. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
  174. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +1 -1
  175. package/dist/phase-diagram/build-diagram.js +2 -2
  176. package/dist/phase-diagram/colors.js +1 -1
  177. package/dist/phase-diagram/parse.d.ts +2 -1
  178. package/dist/phase-diagram/parse.js +6 -5
  179. package/dist/phase-diagram/types.d.ts +1 -1
  180. package/dist/phase-diagram/utils.d.ts +3 -3
  181. package/dist/phase-diagram/utils.js +8 -12
  182. package/dist/plot/{BarPlot.svelte → bar/BarPlot.svelte} +246 -841
  183. package/dist/plot/{BarPlot.svelte.d.ts → bar/BarPlot.svelte.d.ts} +8 -16
  184. package/dist/plot/{BarPlotControls.svelte → bar/BarPlotControls.svelte} +6 -5
  185. package/dist/plot/{BarPlotControls.svelte.d.ts → bar/BarPlotControls.svelte.d.ts} +3 -3
  186. package/dist/plot/{SpacegroupBarPlot.svelte → bar/SpacegroupBarPlot.svelte} +8 -7
  187. package/dist/plot/{SpacegroupBarPlot.svelte.d.ts → bar/SpacegroupBarPlot.svelte.d.ts} +1 -1
  188. package/dist/plot/bar/data.d.ts +40 -0
  189. package/dist/plot/bar/data.js +154 -0
  190. package/dist/plot/bar/geometry.d.ts +39 -0
  191. package/dist/plot/bar/geometry.js +60 -0
  192. package/dist/plot/bar/index.d.ts +3 -0
  193. package/dist/plot/bar/index.js +3 -0
  194. package/dist/plot/box/BoxPlot.svelte +1292 -0
  195. package/dist/plot/box/BoxPlot.svelte.d.ts +95 -0
  196. package/dist/plot/box/BoxPlotControls.svelte +109 -0
  197. package/dist/plot/box/BoxPlotControls.svelte.d.ts +19 -0
  198. package/dist/plot/box/Violin.svelte +14 -0
  199. package/dist/plot/box/Violin.svelte.d.ts +70 -0
  200. package/dist/plot/box/box-plot.d.ts +56 -0
  201. package/dist/plot/box/box-plot.js +129 -0
  202. package/dist/plot/box/index.d.ts +5 -0
  203. package/dist/plot/box/index.js +5 -0
  204. package/dist/plot/box/kde.d.ts +17 -0
  205. package/dist/plot/box/kde.js +160 -0
  206. package/dist/plot/box/quantile.d.ts +3 -0
  207. package/dist/plot/box/quantile.js +53 -0
  208. package/dist/plot/{auto-place.d.ts → core/auto-place.d.ts} +1 -1
  209. package/dist/plot/{auto-place.js → core/auto-place.js} +6 -3
  210. package/dist/plot/core/axis-utils.d.ts +46 -0
  211. package/dist/plot/core/axis-utils.js +110 -0
  212. package/dist/plot/{AxisLabel.svelte → core/components/AxisLabel.svelte} +2 -2
  213. package/dist/plot/{AxisLabel.svelte.d.ts → core/components/AxisLabel.svelte.d.ts} +1 -1
  214. package/dist/plot/{ColorBar.svelte → core/components/ColorBar.svelte} +41 -38
  215. package/dist/plot/{ColorBar.svelte.d.ts → core/components/ColorBar.svelte.d.ts} +7 -6
  216. package/dist/plot/{ColorScaleSelect.svelte → core/components/ColorScaleSelect.svelte} +4 -3
  217. package/dist/plot/{ColorScaleSelect.svelte.d.ts → core/components/ColorScaleSelect.svelte.d.ts} +2 -2
  218. package/dist/plot/core/components/ControlPane.svelte +46 -0
  219. package/dist/plot/core/components/ControlPane.svelte.d.ts +13 -0
  220. package/dist/plot/{FillArea.svelte → core/components/FillArea.svelte} +17 -6
  221. package/dist/plot/{FillArea.svelte.d.ts → core/components/FillArea.svelte.d.ts} +1 -1
  222. package/dist/plot/{InteractiveAxisLabel.svelte → core/components/InteractiveAxisLabel.svelte} +3 -3
  223. package/dist/plot/{InteractiveAxisLabel.svelte.d.ts → core/components/InteractiveAxisLabel.svelte.d.ts} +2 -2
  224. package/dist/plot/{Line.svelte → core/components/Line.svelte} +33 -15
  225. package/dist/plot/{Line.svelte.d.ts → core/components/Line.svelte.d.ts} +3 -2
  226. package/dist/plot/{PlotAxis.svelte → core/components/PlotAxis.svelte} +9 -6
  227. package/dist/plot/{PlotAxis.svelte.d.ts → core/components/PlotAxis.svelte.d.ts} +5 -3
  228. package/dist/plot/{PlotControls.svelte → core/components/PlotControls.svelte} +17 -29
  229. package/dist/plot/core/components/PlotControls.svelte.d.ts +4 -0
  230. package/dist/plot/{PlotLegend.svelte → core/components/PlotLegend.svelte} +21 -10
  231. package/dist/plot/{PlotLegend.svelte.d.ts → core/components/PlotLegend.svelte.d.ts} +3 -2
  232. package/dist/plot/{PlotTooltip.svelte → core/components/PlotTooltip.svelte} +17 -1
  233. package/dist/plot/{PlotTooltip.svelte.d.ts → core/components/PlotTooltip.svelte.d.ts} +8 -0
  234. package/dist/plot/{PortalSelect.svelte → core/components/PortalSelect.svelte} +11 -7
  235. package/dist/plot/{ReferenceLine.svelte → core/components/ReferenceLine.svelte} +3 -3
  236. package/dist/plot/{ReferenceLine.svelte.d.ts → core/components/ReferenceLine.svelte.d.ts} +1 -1
  237. package/dist/plot/{ReferenceLine3D.svelte → core/components/ReferenceLine3D.svelte} +5 -5
  238. package/dist/plot/{ReferenceLine3D.svelte.d.ts → core/components/ReferenceLine3D.svelte.d.ts} +5 -5
  239. package/dist/plot/{ReferencePlane.svelte → core/components/ReferencePlane.svelte} +8 -8
  240. package/dist/plot/{ReferencePlane.svelte.d.ts → core/components/ReferencePlane.svelte.d.ts} +5 -5
  241. package/dist/plot/{ZeroLines.svelte → core/components/ZeroLines.svelte} +3 -3
  242. package/dist/plot/{ZeroLines.svelte.d.ts → core/components/ZeroLines.svelte.d.ts} +3 -3
  243. package/dist/plot/{ZoomRect.svelte → core/components/ZoomRect.svelte} +1 -1
  244. package/dist/plot/{ZoomRect.svelte.d.ts → core/components/ZoomRect.svelte.d.ts} +1 -1
  245. package/dist/plot/core/components/index.d.ts +17 -0
  246. package/dist/plot/core/components/index.js +17 -0
  247. package/dist/plot/{data-cleaning.d.ts → core/data-cleaning.d.ts} +71 -1
  248. package/dist/plot/{data-cleaning.js → core/data-cleaning.js} +21 -23
  249. package/dist/plot/{data-transform.d.ts → core/data-transform.d.ts} +2 -2
  250. package/dist/plot/{data-transform.js → core/data-transform.js} +3 -3
  251. package/dist/plot/core/fill-utils.d.ts +34 -0
  252. package/dist/plot/core/fill-utils.js +391 -0
  253. package/dist/plot/core/index.d.ts +10 -0
  254. package/dist/plot/core/index.js +11 -0
  255. package/dist/plot/core/interactions.d.ts +39 -0
  256. package/dist/plot/core/interactions.js +209 -0
  257. package/dist/plot/{layout.d.ts → core/layout.d.ts} +1 -0
  258. package/dist/plot/{layout.js → core/layout.js} +16 -8
  259. package/dist/plot/core/pan-zoom.svelte.d.ts +35 -0
  260. package/dist/plot/core/pan-zoom.svelte.js +221 -0
  261. package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
  262. package/dist/plot/core/placed-tween.svelte.js +68 -0
  263. package/dist/plot/{reference-line.d.ts → core/reference-line.d.ts} +11 -11
  264. package/dist/plot/{reference-line.js → core/reference-line.js} +29 -42
  265. package/dist/plot/core/scales.d.ts +40 -0
  266. package/dist/plot/{scales.js → core/scales.js} +94 -93
  267. package/dist/plot/core/svg.d.ts +3 -0
  268. package/dist/plot/core/svg.js +41 -0
  269. package/dist/plot/{types.d.ts → core/types.d.ts} +36 -85
  270. package/dist/plot/{types.js → core/types.js} +1 -1
  271. package/dist/plot/{utils → core/utils}/label-placement.d.ts +3 -3
  272. package/dist/plot/{utils → core/utils}/label-placement.js +3 -3
  273. package/dist/plot/core/utils/series-visibility.d.ts +26 -0
  274. package/dist/plot/{utils → core/utils}/series-visibility.js +29 -2
  275. package/dist/plot/core/utils.d.ts +12 -0
  276. package/dist/plot/core/utils.js +27 -0
  277. package/dist/plot/{Histogram.svelte → histogram/Histogram.svelte} +174 -551
  278. package/dist/plot/{Histogram.svelte.d.ts → histogram/Histogram.svelte.d.ts} +2 -2
  279. package/dist/plot/{HistogramControls.svelte → histogram/HistogramControls.svelte} +6 -6
  280. package/dist/plot/{HistogramControls.svelte.d.ts → histogram/HistogramControls.svelte.d.ts} +4 -4
  281. package/dist/plot/histogram/index.d.ts +2 -0
  282. package/dist/plot/histogram/index.js +2 -0
  283. package/dist/plot/index.d.ts +8 -41
  284. package/dist/plot/index.js +10 -39
  285. package/dist/plot/sankey/Sankey.svelte +697 -0
  286. package/dist/plot/sankey/Sankey.svelte.d.ts +74 -0
  287. package/dist/plot/sankey/SankeyControls.svelte +98 -0
  288. package/dist/plot/sankey/SankeyControls.svelte.d.ts +19 -0
  289. package/dist/plot/sankey/index.d.ts +4 -0
  290. package/dist/plot/sankey/index.js +3 -0
  291. package/dist/plot/sankey/sankey-types.d.ts +42 -0
  292. package/dist/plot/sankey/sankey-types.js +4 -0
  293. package/dist/plot/sankey/sankey.d.ts +52 -0
  294. package/dist/plot/sankey/sankey.js +189 -0
  295. package/dist/plot/{BinnedScatterPlot.svelte → scatter/BinnedScatterPlot.svelte} +64 -64
  296. package/dist/plot/{BinnedScatterPlot.svelte.d.ts → scatter/BinnedScatterPlot.svelte.d.ts} +6 -6
  297. package/dist/plot/{ElementScatter.svelte → scatter/ElementScatter.svelte} +6 -6
  298. package/dist/plot/{ElementScatter.svelte.d.ts → scatter/ElementScatter.svelte.d.ts} +2 -2
  299. package/dist/plot/{ScatterPlot.svelte → scatter/ScatterPlot.svelte} +297 -1008
  300. package/dist/plot/{ScatterPlot.svelte.d.ts → scatter/ScatterPlot.svelte.d.ts} +10 -18
  301. package/dist/plot/{ScatterPlotControls.svelte → scatter/ScatterPlotControls.svelte} +6 -5
  302. package/dist/plot/{ScatterPlotControls.svelte.d.ts → scatter/ScatterPlotControls.svelte.d.ts} +2 -2
  303. package/dist/plot/{ScatterPoint.svelte → scatter/ScatterPoint.svelte} +7 -7
  304. package/dist/plot/{ScatterPoint.svelte.d.ts → scatter/ScatterPoint.svelte.d.ts} +3 -3
  305. package/dist/plot/{adaptive-density.d.ts → scatter/adaptive-density.d.ts} +14 -4
  306. package/dist/plot/{adaptive-density.js → scatter/adaptive-density.js} +46 -20
  307. package/dist/plot/{binned-scatter-types.d.ts → scatter/binned-scatter-types.d.ts} +5 -12
  308. package/dist/plot/scatter/index.d.ts +7 -0
  309. package/dist/plot/scatter/index.js +5 -0
  310. package/dist/plot/scatter/scatter-data.d.ts +19 -0
  311. package/dist/plot/scatter/scatter-data.js +212 -0
  312. package/dist/plot/{ScatterPlot3D.svelte → scatter-3d/ScatterPlot3D.svelte} +25 -34
  313. package/dist/plot/{ScatterPlot3D.svelte.d.ts → scatter-3d/ScatterPlot3D.svelte.d.ts} +9 -17
  314. package/dist/plot/{ScatterPlot3DControls.svelte → scatter-3d/ScatterPlot3DControls.svelte} +14 -14
  315. package/dist/plot/{ScatterPlot3DControls.svelte.d.ts → scatter-3d/ScatterPlot3DControls.svelte.d.ts} +6 -6
  316. package/dist/plot/{ScatterPlot3DScene.svelte → scatter-3d/ScatterPlot3DScene.svelte} +129 -128
  317. package/dist/plot/{ScatterPlot3DScene.svelte.d.ts → scatter-3d/ScatterPlot3DScene.svelte.d.ts} +6 -15
  318. package/dist/plot/{Surface3D.svelte → scatter-3d/Surface3D.svelte} +7 -6
  319. package/dist/plot/{Surface3D.svelte.d.ts → scatter-3d/Surface3D.svelte.d.ts} +5 -4
  320. package/dist/plot/scatter-3d/index.d.ts +4 -0
  321. package/dist/plot/scatter-3d/index.js +4 -0
  322. package/dist/plot/sunburst/Sunburst.svelte +1041 -0
  323. package/dist/plot/sunburst/Sunburst.svelte.d.ts +97 -0
  324. package/dist/plot/sunburst/SunburstControls.svelte +200 -0
  325. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +26 -0
  326. package/dist/plot/sunburst/index.d.ts +4 -0
  327. package/dist/plot/sunburst/index.js +4 -0
  328. package/dist/plot/sunburst/render.d.ts +34 -0
  329. package/dist/plot/sunburst/render.js +122 -0
  330. package/dist/plot/sunburst/sunburst.d.ts +62 -0
  331. package/dist/plot/sunburst/sunburst.js +269 -0
  332. package/dist/rdf/RdfPlot.svelte +2 -1
  333. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  334. package/dist/rdf/calc-rdf.js +11 -24
  335. package/dist/sanitize.js +14 -3
  336. package/dist/scene/SceneCamera.svelte +62 -0
  337. package/dist/scene/SceneCamera.svelte.d.ts +19 -0
  338. package/dist/scene/bind-renderer.svelte.d.ts +2 -0
  339. package/dist/scene/bind-renderer.svelte.js +14 -0
  340. package/dist/scene/index.d.ts +4 -0
  341. package/dist/scene/index.js +5 -0
  342. package/dist/scene/props.js +52 -0
  343. package/dist/scene/types.d.ts +26 -0
  344. package/dist/scene/types.js +1 -0
  345. package/dist/settings.d.ts +79 -3
  346. package/dist/settings.js +321 -1
  347. package/dist/spectral/Bands.svelte +47 -36
  348. package/dist/spectral/Bands.svelte.d.ts +6 -6
  349. package/dist/spectral/BandsAndDos.svelte +23 -25
  350. package/dist/spectral/BrillouinBandsDos.svelte +42 -30
  351. package/dist/spectral/Dos.svelte +15 -23
  352. package/dist/spectral/Dos.svelte.d.ts +4 -3
  353. package/dist/spectral/helpers.d.ts +8 -6
  354. package/dist/spectral/helpers.js +137 -65
  355. package/dist/state.svelte.d.ts +0 -7
  356. package/dist/state.svelte.js +0 -6
  357. package/dist/structure/Arrow.svelte +2 -4
  358. package/dist/structure/AtomLegend.svelte +8 -9
  359. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  360. package/dist/structure/CanvasTooltip.svelte +1 -0
  361. package/dist/structure/CellSelect.svelte +12 -5
  362. package/dist/structure/CellSelect.svelte.d.ts +2 -1
  363. package/dist/structure/Cylinder.svelte +12 -8
  364. package/dist/structure/Cylinder.svelte.d.ts +4 -1
  365. package/dist/structure/Lattice.svelte +2 -2
  366. package/dist/structure/Structure.svelte +365 -423
  367. package/dist/structure/Structure.svelte.d.ts +5 -15
  368. package/dist/structure/StructureControls.svelte +217 -2
  369. package/dist/structure/StructureControls.svelte.d.ts +5 -3
  370. package/dist/structure/StructureExportPane.svelte +54 -156
  371. package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
  372. package/dist/structure/StructureInfoPane.svelte +10 -9
  373. package/dist/structure/StructureInfoPane.svelte.d.ts +5 -5
  374. package/dist/structure/StructureScene.svelte +376 -208
  375. package/dist/structure/StructureScene.svelte.d.ts +22 -20
  376. package/dist/structure/{label-placement.d.ts → atom-label-placement.d.ts} +3 -3
  377. package/dist/structure/{label-placement.js → atom-label-placement.js} +15 -5
  378. package/dist/structure/atom-properties.d.ts +1 -1
  379. package/dist/structure/atom-properties.js +17 -22
  380. package/dist/structure/bond-order-perception.js +3 -5
  381. package/dist/structure/bonding.d.ts +4 -0
  382. package/dist/structure/bonding.js +134 -63
  383. package/dist/structure/export.d.ts +24 -4
  384. package/dist/structure/export.js +89 -143
  385. package/dist/structure/index.d.ts +4 -4
  386. package/dist/structure/index.js +3 -3
  387. package/dist/structure/measure.d.ts +3 -2
  388. package/dist/structure/measure.js +6 -5
  389. package/dist/structure/parse.d.ts +3 -2
  390. package/dist/structure/parse.js +419 -438
  391. package/dist/structure/partial-occupancy.d.ts +0 -1
  392. package/dist/structure/partial-occupancy.js +1 -1
  393. package/dist/structure/pbc.d.ts +1 -1
  394. package/dist/structure/pbc.js +190 -13
  395. package/dist/structure/polyhedra.d.ts +41 -0
  396. package/dist/structure/polyhedra.js +602 -0
  397. package/dist/structure/site.d.ts +4 -0
  398. package/dist/structure/site.js +1 -0
  399. package/dist/structure/supercell.js +3 -2
  400. package/dist/structure/validation.js +5 -6
  401. package/dist/symmetry/SymmetryElementControls.svelte +69 -0
  402. package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
  403. package/dist/symmetry/SymmetryElements.svelte +354 -0
  404. package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
  405. package/dist/symmetry/SymmetryStats.svelte +113 -8
  406. package/dist/symmetry/WyckoffTable.svelte +68 -7
  407. package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
  408. package/dist/symmetry/cell-transform.js +7 -14
  409. package/dist/symmetry/index.d.ts +14 -4
  410. package/dist/symmetry/index.js +291 -72
  411. package/dist/symmetry/spacegroups.d.ts +12 -1
  412. package/dist/symmetry/spacegroups.js +63 -14
  413. package/dist/symmetry/symmetry-elements.d.ts +33 -0
  414. package/dist/symmetry/symmetry-elements.js +521 -0
  415. package/dist/symmetry/wyckoff-db.d.ts +9 -0
  416. package/dist/symmetry/wyckoff-db.js +87 -0
  417. package/dist/table/HeatmapTable.svelte +66 -25
  418. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  419. package/dist/table/index.d.ts +1 -3
  420. package/dist/table/index.js +1 -1
  421. package/dist/theme/index.js +8 -8
  422. package/dist/tooltip/KCoords.svelte +45 -0
  423. package/dist/tooltip/KCoords.svelte.d.ts +8 -0
  424. package/dist/tooltip/index.d.ts +1 -0
  425. package/dist/tooltip/index.js +1 -0
  426. package/dist/trajectory/Trajectory.svelte +123 -100
  427. package/dist/trajectory/Trajectory.svelte.d.ts +11 -22
  428. package/dist/trajectory/TrajectoryExportPane.svelte +17 -25
  429. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
  430. package/dist/trajectory/TrajectoryInfoPane.svelte +5 -3
  431. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
  432. package/dist/trajectory/constants.js +6 -2
  433. package/dist/trajectory/extract.js +17 -37
  434. package/dist/trajectory/format-detect.d.ts +1 -1
  435. package/dist/trajectory/format-detect.js +27 -19
  436. package/dist/trajectory/frame-reader.d.ts +0 -1
  437. package/dist/trajectory/frame-reader.js +63 -162
  438. package/dist/trajectory/helpers.d.ts +10 -2
  439. package/dist/trajectory/helpers.js +56 -36
  440. package/dist/trajectory/index.js +1 -1
  441. package/dist/trajectory/parse/ase.d.ts +9 -1
  442. package/dist/trajectory/parse/ase.js +47 -32
  443. package/dist/trajectory/parse/diagnostics.d.ts +3 -0
  444. package/dist/trajectory/parse/diagnostics.js +14 -0
  445. package/dist/trajectory/parse/hdf5.js +1 -1
  446. package/dist/trajectory/parse/index.d.ts +1 -1
  447. package/dist/trajectory/parse/index.js +65 -105
  448. package/dist/trajectory/parse/lammps.d.ts +0 -2
  449. package/dist/trajectory/parse/lammps.js +8 -6
  450. package/dist/trajectory/parse/pymatgen.d.ts +2 -0
  451. package/dist/trajectory/parse/pymatgen.js +74 -0
  452. package/dist/trajectory/parse/vasp.js +38 -18
  453. package/dist/trajectory/parse/xyz.d.ts +13 -1
  454. package/dist/trajectory/parse/xyz.js +102 -94
  455. package/dist/trajectory/plotting.d.ts +1 -2
  456. package/dist/trajectory/plotting.js +16 -113
  457. package/dist/utils.d.ts +2 -0
  458. package/dist/utils.js +7 -5
  459. package/dist/xrd/XrdPlot.svelte +16 -30
  460. package/dist/xrd/broadening.d.ts +2 -1
  461. package/dist/xrd/calc-xrd.js +18 -20
  462. package/dist/xrd/index.d.ts +2 -2
  463. package/dist/xrd/parse.js +2 -2
  464. package/package.json +43 -26
  465. package/dist/element/data.json +0 -11864
  466. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  467. package/dist/fermi-surface/marching-cubes.js +0 -2
  468. package/dist/plot/PlotControls.svelte.d.ts +0 -4
  469. package/dist/plot/axis-utils.d.ts +0 -19
  470. package/dist/plot/axis-utils.js +0 -78
  471. package/dist/plot/defaults.d.ts +0 -19
  472. package/dist/plot/defaults.js +0 -9
  473. package/dist/plot/fill-utils.d.ts +0 -46
  474. package/dist/plot/fill-utils.js +0 -322
  475. package/dist/plot/hover-lock.svelte.d.ts +0 -14
  476. package/dist/plot/hover-lock.svelte.js +0 -46
  477. package/dist/plot/interactions.d.ts +0 -12
  478. package/dist/plot/interactions.js +0 -101
  479. package/dist/plot/scales.d.ts +0 -48
  480. package/dist/plot/svg.d.ts +0 -1
  481. package/dist/plot/svg.js +0 -11
  482. package/dist/plot/utils/series-visibility.d.ts +0 -15
  483. package/dist/plot/utils.d.ts +0 -1
  484. package/dist/plot/utils.js +0 -14
  485. /package/dist/plot/{PortalSelect.svelte.d.ts → core/components/PortalSelect.svelte.d.ts} +0 -0
  486. /package/dist/plot/{binned-scatter-types.js → scatter/binned-scatter-types.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { format_value } from '../labels'
3
- import { FullscreenToggle, set_fullscreen_bg } from '../layout'
2
+ import { format_value } from '../../labels'
3
+ import { FullscreenToggle, set_fullscreen_bg } from '../../layout'
4
4
  import type {
5
5
  AxisLoadError,
6
6
  BarStyle,
@@ -9,70 +9,67 @@
9
9
  PanConfig,
10
10
  RefLine,
11
11
  RefLineEvent,
12
- } from './'
12
+ } from '..'
13
13
  import {
14
14
  compute_element_placement,
15
15
  HistogramControls,
16
16
  PlotAxis,
17
17
  PlotLegend,
18
18
  ReferenceLine,
19
- } from './'
20
- import type { AxisChangeState } from './axis-utils'
21
- import { create_axis_change_handler } from './axis-utils'
22
- import { extract_series_color, prepare_legend_data } from './data-transform'
23
- import { AXIS_DEFAULTS } from './defaults'
19
+ } from '..'
20
+ import type { AxisChangeState } from '../core/axis-utils'
21
+ import { AXIS_DEFAULTS, create_axis_loader } from '../core/axis-utils'
22
+ import { extract_series_color, prepare_legend_data } from '../core/data-transform'
23
+ import { create_placed_tween } from '../core/placed-tween.svelte'
24
+ import { create_pan_zoom } from '../core/pan-zoom.svelte'
25
+ import { create_legend_visibility } from '../core/utils/series-visibility'
24
26
  import {
25
- create_dimension_tracker,
26
- create_hover_lock,
27
- } from './hover-lock.svelte'
28
- import {
29
- get_relative_coords,
30
- pan_range,
31
- PINCH_ZOOM_THRESHOLD,
32
- pixels_to_data_delta,
33
- } from './interactions'
27
+ invert_rect_range,
28
+ resolve_axis_ranges,
29
+ vec2_equal,
30
+ } from '../core/interactions'
34
31
  import {
35
32
  calc_auto_padding,
36
- constrain_tooltip_position,
37
33
  filter_padding,
38
34
  LABEL_GAP_DEFAULT,
35
+ y2_axis_label_x,
39
36
  measure_max_tick_width,
40
- } from './layout'
37
+ } from '../core/layout'
41
38
  import {
42
39
  build_obstacles_norm,
43
40
  clip_bar,
44
41
  has_explicit_position,
45
42
  measured_footprint,
46
43
  place_decorations,
47
- } from './auto-place'
48
- import type { IndexedRefLine } from './reference-line'
49
- import { group_ref_lines_by_z, index_ref_lines } from './reference-line'
44
+ placed_coords,
45
+ } from '../core/auto-place'
46
+ import type { IndexedRefLine } from '../core/reference-line'
47
+ import { group_ref_lines_by_z, index_ref_lines } from '../core/reference-line'
50
48
  import {
51
49
  create_scale,
52
50
  generate_ticks,
53
51
  get_nice_data_range,
54
52
  get_tick_label,
55
- } from './scales'
53
+ } from '../core/scales'
56
54
  import type {
57
55
  BasePlotProps,
58
56
  DataSeries,
59
- InitialRanges,
60
57
  LegendConfig,
61
58
  PlotConfig,
62
59
  ScaleType,
63
- } from './types'
64
- import { get_scale_type_name } from './types'
65
- import ZeroLines from './ZeroLines.svelte'
66
- import ZoomRect from './ZoomRect.svelte'
67
- import { DEFAULTS } from '../settings'
60
+ } from '../core/types'
61
+ import { get_scale_type_name } from '../core/types'
62
+ import ZeroLines from '../core/components/ZeroLines.svelte'
63
+ import ZoomRect from '../core/components/ZoomRect.svelte'
64
+ import { DEFAULTS } from '../../settings'
68
65
  import { bin, max } from 'd3-array'
69
66
  import type { Snippet } from 'svelte'
70
- import { untrack } from 'svelte'
67
+ import { onDestroy, untrack } from 'svelte'
71
68
  import type { HTMLAttributes } from 'svelte/elements'
72
- import { Tween } from 'svelte/motion'
73
- import type { Vec2 } from '../math'
74
- import PlotTooltip from './PlotTooltip.svelte'
75
- import { bar_path } from './svg'
69
+ import type { Vec2 } from '../../math'
70
+ import PlotTooltip from '../core/components/PlotTooltip.svelte'
71
+ import { bar_path } from '../core/svg'
72
+ import { unique_id } from '../core/utils'
76
73
 
77
74
  let {
78
75
  series = $bindable([]),
@@ -81,10 +78,6 @@
81
78
  y_axis: y_axis_init = {},
82
79
  y2_axis: y2_axis_init = {},
83
80
  display: display_init = DEFAULTS.histogram.display,
84
- x_range = [null, null],
85
- x2_range = [null, null],
86
- y_range = [null, null],
87
- y2_range = [null, null],
88
81
  range_padding = 0.05,
89
82
  padding = { t: 20, b: 60, l: 60, r: 20 },
90
83
  bins = $bindable(100),
@@ -173,10 +166,10 @@
173
166
  ...x2_axis_init,
174
167
  })))
175
168
  let y_axis = $state(untrack(() => ({ ...axis_state_defaults, ...y_axis_init })))
176
- // y2-axis needs different default label_shift for right-side positioning
169
+ // y2 title stays vertically centered; its x position is computed by y2_axis_label_x
177
170
  let y2_axis = $state(untrack(() => ({
178
171
  ...axis_state_defaults,
179
- label_shift: { x: 0, y: 60 },
172
+ label_shift: { x: 0, y: 0 },
180
173
  ...y2_axis_init,
181
174
  })))
182
175
  let display = $state(
@@ -194,7 +187,7 @@
194
187
  let [width, height] = $state([0, 0])
195
188
  let wrapper: HTMLDivElement | undefined = $state()
196
189
  let svg_element: SVGElement | null = $state(null)
197
- let clip_path_id = `histogram-clip-${crypto?.randomUUID?.()}`
190
+ const clip_path_id = unique_id(`histogram-clip`) // stable, collision-resistant (see unique_id)
198
191
  let hover_info = $state<HistogramHandlerProps | null>(null)
199
192
 
200
193
  // Reference line hover state
@@ -206,32 +199,10 @@
206
199
  // Compute ref_lines with index and group by z-index (using shared utilities)
207
200
  let indexed_ref_lines = $derived(index_ref_lines(ref_lines))
208
201
  let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines))
209
- let tooltip_el = $state<HTMLDivElement | undefined>()
210
- let drag_state = $state<{
211
- start: { x: number; y: number } | null
212
- current: { x: number; y: number } | null
213
- bounds: DOMRect | null
214
- }>({ start: null, current: null, bounds: null })
215
-
216
- // Pan state
217
- let is_focused = $state(false)
218
- let shift_held = $state(false)
219
- let pan_drag_state = $state<
220
- InitialRanges & { start: { x: number; y: number } } | null
221
- >(null)
222
- let touch_state = $state<
223
- InitialRanges & { start_touches: { x: number; y: number }[] } | null
224
- >(null)
225
202
 
226
203
  // Legend placement stability state
227
204
  let legend_element = $state<HTMLDivElement | undefined>()
228
205
  let hovered_legend_series_idx = $state<number | null>(null)
229
- const legend_hover = create_hover_lock()
230
- const dim_tracker = create_dimension_tracker()
231
- let has_initial_legend_placement = $state(false)
232
-
233
- // Clear pending hover lock timeout on unmount
234
- $effect(() => () => legend_hover.cleanup())
235
206
 
236
207
  // Derived data
237
208
  type IndexedSeries = { series_data: DataSeries; series_idx: number }
@@ -270,11 +241,12 @@
270
241
  )
271
242
 
272
243
  let auto_ranges = $derived.by(() => {
273
- const all_values = selected_series.flatMap((srs: DataSeries) => srs.y)
244
+ // Only x1 series contribute to the x1 auto-range (x2 series get their own domain below)
245
+ const x1_values = selected_series.flatMap((srs) => srs.x_axis === `x2` ? [] : srs.y)
274
246
  const auto_x = get_nice_data_range(
275
- all_values.map((val) => ({ x: val, y: 0 })),
247
+ x1_values.map((val) => ({ x: val, y: 0 })),
276
248
  ({ x }) => x,
277
- x_range,
249
+ final_x_axis.range ?? [null, null],
278
250
  final_x_axis.scale_type ?? `linear`,
279
251
  range_padding,
280
252
  false,
@@ -285,7 +257,7 @@
285
257
  ? get_nice_data_range(
286
258
  x2_values.map((val) => ({ x: val, y: 0 })),
287
259
  ({ x }) => x,
288
- x2_range,
260
+ final_x2_axis.range ?? [null, null],
289
261
  final_x2_axis.scale_type ?? `linear`,
290
262
  range_padding,
291
263
  false,
@@ -295,20 +267,22 @@
295
267
  // Calculate y-range for a specific set of series
296
268
  const calc_y_range = (
297
269
  series_list: typeof selected_series,
298
- y_limit: typeof y_range,
270
+ y_limit: [number | null, number | null],
299
271
  scale_type: ScaleType,
300
272
  ): Vec2 => {
301
273
  const type_name = get_scale_type_name(scale_type)
302
- if (!series_list.length) {
274
+ if (series_list.length === 0) {
303
275
  const fallback = type_name === `log` ? 1 : 0
304
276
  return [fallback, 1]
305
277
  }
306
- const hist = bin().domain([auto_x[0], auto_x[1]]).thresholds(bins)
278
+ // Bin each series over the domain of the x-axis it renders on (d3 bin() drops
279
+ // out-of-domain values, so binning x2 series over the x1 domain skews max_count)
307
280
  const max_count = Math.max(
308
281
  0,
309
- ...series_list.map((srs: DataSeries) =>
310
- max(hist(srs.y), (data) => data.length) || 0
311
- ),
282
+ ...series_list.map((srs: DataSeries) => {
283
+ const hist = bin().domain(srs.x_axis === `x2` ? auto_x2 : auto_x).thresholds(bins)
284
+ return max(hist(srs.y), (data) => data.length) || 0
285
+ }),
312
286
  )
313
287
 
314
288
  // If there's effectively no data, avoid log-range issues (counts can't be <= 0 on log)
@@ -333,12 +307,12 @@
333
307
 
334
308
  const y1_range = calc_y_range(
335
309
  y1_series,
336
- y_range,
310
+ final_y_axis.range ?? [null, null],
337
311
  final_y_axis.scale_type ?? `linear`,
338
312
  )
339
313
  const y2_auto_range = calc_y_range(
340
314
  y2_series,
341
- y2_range,
315
+ final_y2_axis.range ?? [null, null],
342
316
  final_y2_axis.scale_type ?? `linear`,
343
317
  )
344
318
 
@@ -362,47 +336,21 @@
362
336
  })
363
337
 
364
338
  $effect(() => {
365
- // Support one-sided range pinning: merge user range with auto range for null values
366
- const new_x: [number, number] = final_x_axis.range
367
- ? [
368
- final_x_axis.range[0] ?? auto_ranges.x[0],
369
- final_x_axis.range[1] ?? auto_ranges.x[1],
370
- ]
371
- : auto_ranges.x
372
- const new_x2: [number, number] = final_x2_axis.range
373
- ? [
374
- final_x2_axis.range[0] ?? auto_ranges.x2[0],
375
- final_x2_axis.range[1] ?? auto_ranges.x2[1],
376
- ]
377
- : auto_ranges.x2
378
- const new_y: [number, number] = final_y_axis.range
379
- ? [
380
- final_y_axis.range[0] ?? auto_ranges.y[0],
381
- final_y_axis.range[1] ?? auto_ranges.y[1],
382
- ]
383
- : auto_ranges.y
384
- const new_y2: [number, number] = final_y2_axis.range
385
- ? [
386
- final_y2_axis.range[0] ?? auto_ranges.y2[0],
387
- final_y2_axis.range[1] ?? auto_ranges.y2[1],
388
- ]
389
- : auto_ranges.y2
390
-
391
- // Only update if the initial (data-driven) ranges changed, not when user pans
392
- // Comparing against initial preserves user's pan/zoom state
393
- const x_changed = new_x[0] !== ranges.initial.x[0] ||
394
- new_x[1] !== ranges.initial.x[1]
395
- const x2_changed = new_x2[0] !== ranges.initial.x2[0] ||
396
- new_x2[1] !== ranges.initial.x2[1]
397
- const y_changed = new_y[0] !== ranges.initial.y[0] ||
398
- new_y[1] !== ranges.initial.y[1]
399
- const y2_changed = new_y2[0] !== ranges.initial.y2[0] ||
400
- new_y2[1] !== ranges.initial.y2[1]
401
-
402
- if (x_changed) [ranges.initial.x, ranges.current.x] = [new_x, new_x]
403
- if (x2_changed) [ranges.initial.x2, ranges.current.x2] = [new_x2, new_x2]
404
- if (y_changed) [ranges.initial.y, ranges.current.y] = [new_y, new_y]
405
- if (y2_changed) [ranges.initial.y2, ranges.current.y2] = [new_y2, new_y2]
339
+ // Supports one-sided range pinning (null bounds fall back to auto); returns null
340
+ // for transient non-finite bounds (skip: writing NaN breaks scales and loops here)
341
+ const next = resolve_axis_ranges(
342
+ { x: final_x_axis, x2: final_x2_axis, y: final_y_axis, y2: final_y2_axis },
343
+ auto_ranges,
344
+ )
345
+ if (!next) return
346
+ // Update only changed axes (preserving each unchanged axis's panned current view).
347
+ // untrack the reads of `ranges` so the writes below can't re-trigger this effect
348
+ // (reading + writing the same state otherwise causes effect_update_depth_exceeded).
349
+ const init = untrack(() => ranges.initial)
350
+ if (!vec2_equal(init.x, next.x)) [ranges.initial.x, ranges.current.x] = [next.x, next.x]
351
+ if (!vec2_equal(init.x2, next.x2)) [ranges.initial.x2, ranges.current.x2] = [next.x2, next.x2]
352
+ if (!vec2_equal(init.y, next.y)) [ranges.initial.y, ranges.current.y] = [next.y, next.y]
353
+ if (!vec2_equal(init.y2, next.y2)) [ranges.initial.y2, ranges.current.y2] = [next.y2, next.y2]
406
354
  })
407
355
 
408
356
  // Layout: dynamic padding based on tick label widths
@@ -416,7 +364,7 @@
416
364
  const current_ticks_y = untrack(() => ticks.y)
417
365
  const current_ticks_y2 = untrack(() => ticks.y2)
418
366
 
419
- const new_pad = width && height && current_ticks_y.length
367
+ const new_pad = width && height && current_ticks_y.length > 0
420
368
  ? calc_auto_padding({
421
369
  padding,
422
370
  default_padding,
@@ -428,7 +376,7 @@
428
376
 
429
377
  // Add y2 axis label space (calc_auto_padding only accounts for tick labels)
430
378
  if (
431
- width && height && y2_series.length && current_ticks_y2.length &&
379
+ width && height && y2_series.length > 0 && current_ticks_y2.length > 0 &&
432
380
  final_y2_axis.label
433
381
  ) {
434
382
  const inside = final_y2_axis.tick?.label?.inside ?? false
@@ -444,7 +392,7 @@
444
392
 
445
393
  // Add x2 axis label space (mirroring y2 logic for top padding)
446
394
  if (
447
- width && height && x2_series.length && current_ticks_x2.length &&
395
+ width && height && x2_series.length > 0 && current_ticks_x2.length > 0 &&
448
396
  final_x2_axis.label
449
397
  ) {
450
398
  const inside = final_x2_axis.tick?.label?.inside ?? false
@@ -472,7 +420,7 @@
472
420
  // vertical segment (top -> baseline) so the legend can't hide inside a tall bar. Built from
473
421
  // histogram_bins (pad-independent) + ranges so the crowding decision can't see its own reservation.
474
422
  const obstacles_norm = $derived.by(() => {
475
- if (!width || !height || !histogram_bins.length) return []
423
+ if (!width || !height || histogram_bins.length === 0) return []
476
424
  const base_w = width - base_pad.l - base_pad.r
477
425
  const base_h = height - base_pad.t - base_pad.b
478
426
  if (base_w <= 0 || base_h <= 0) return []
@@ -540,7 +488,7 @@
540
488
 
541
489
  // Pad-independent binning (no pixel scales) so the auto-place obstacle field can reuse it
542
490
  let histogram_bins = $derived.by(() => {
543
- if (!selected_series.length || !width || !height) return []
491
+ if (selected_series.length === 0 || !width || !height) return []
544
492
  const hist_generator = bin()
545
493
  .domain([ranges.current.x[0], ranges.current.x[1]])
546
494
  .thresholds(bins)
@@ -625,7 +573,7 @@
625
573
 
626
574
  // Collect histogram bar positions for legend placement
627
575
  let hist_points_for_placement = $derived.by(() => {
628
- if (!width || !height || !histogram_data.length) return []
576
+ if (!width || !height || histogram_data.length === 0) return []
629
577
 
630
578
  const points: { x: number; y: number }[] = []
631
579
 
@@ -666,339 +614,59 @@
666
614
  return result
667
615
  })
668
616
 
669
- // Tweened legend coordinates for smooth animation - create once, update target via effect
670
- // untrack() explicitly captures initial tween config (intentional - config set once at mount)
671
- const tweened_legend_coords = new Tween(
672
- { x: 0, y: 0 },
673
- untrack(() => ({ duration: 400, ...legend?.tween })),
674
- )
675
-
676
- // Update legend position with stability checks
677
- $effect(() => {
678
- if (!width || !height || !legend_placement) return
679
-
680
- // Track dimensions for resize detection
681
- const dims_changed = dim_tracker.has_changed(width, height)
682
- if (dims_changed) dim_tracker.update(width, height)
683
-
684
- // Only update if: resize occurred, OR (not hover-locked AND (responsive OR not yet initially placed))
685
- const is_responsive = legend?.responsive ?? false
686
- const should_update = dims_changed || (!legend_hover.is_locked.current &&
687
- (is_responsive || !has_initial_legend_placement))
688
-
689
- if (should_update) {
690
- tweened_legend_coords.set(
691
- { x: legend_placement.x, y: legend_placement.y },
692
- // Skip animation on initial placement to avoid jump from (0, 0)
693
- has_initial_legend_placement ? undefined : { duration: 0 },
694
- )
695
- // Only lock position after we have actual measured size
696
- if (legend_element) {
697
- has_initial_legend_placement = true
698
- }
699
- }
617
+ // Tweened legend coordinates with shared placement stability gating
618
+ const legend_tween = create_placed_tween({
619
+ placement: () => legend_placement,
620
+ dims: () => ({ width, height }),
621
+ responsive: () => legend?.responsive ?? false,
622
+ element: () => legend_element,
623
+ tween: () => legend?.tween,
700
624
  })
701
625
 
702
- // Event handlers
703
- const handle_zoom = () => {
704
- if (!drag_state.start || !drag_state.current) return
705
- const start_x = scales.x.invert(drag_state.start.x)
706
- const end_x = scales.x.invert(drag_state.current.x)
707
- const start_x2 = scales.x2.invert(drag_state.start.x)
708
- const end_x2 = scales.x2.invert(drag_state.current.x)
709
- const start_y = scales.y.invert(drag_state.start.y)
710
- const end_y = scales.y.invert(drag_state.current.y)
711
- const start_y2 = scales.y2.invert(drag_state.start.y)
712
- const end_y2 = scales.y2.invert(drag_state.current.y)
713
-
714
- if (typeof start_x === `number` && typeof end_x === `number`) {
715
- const dx = Math.abs(drag_state.start.x - drag_state.current.x)
716
- const dy = Math.abs(drag_state.start.y - drag_state.current.y)
717
- if (dx > 5 && dy > 5) {
718
- // Update axis ranges to trigger reactivity and prevent effect from overriding
719
- x_axis = {
720
- ...x_axis,
721
- range: [Math.min(start_x, end_x), Math.max(start_x, end_x)],
722
- }
723
- if (x2_series.length > 0) {
724
- x2_axis = {
725
- ...x2_axis,
726
- range: [Math.min(start_x2, end_x2), Math.max(start_x2, end_x2)],
727
- }
728
- }
729
- y_axis = {
730
- ...y_axis,
731
- range: [Math.min(start_y, end_y), Math.max(start_y, end_y)],
732
- }
733
- y2_axis = {
734
- ...y2_axis,
735
- range: [Math.min(start_y2, end_y2), Math.max(start_y2, end_y2)],
736
- }
737
- }
738
- }
739
- }
740
-
741
- const on_window_mouse_move = (evt: MouseEvent) => {
742
- if (!drag_state.start || !drag_state.bounds) return
743
- drag_state.current = {
744
- x: evt.clientX - drag_state.bounds.left,
745
- y: evt.clientY - drag_state.bounds.top,
746
- }
747
- }
748
-
749
- const on_window_mouse_up = () => {
750
- handle_zoom()
751
- drag_state = { start: null, current: null, bounds: null }
752
- window.removeEventListener(`mousemove`, on_window_mouse_move)
753
- window.removeEventListener(`mouseup`, on_window_mouse_up)
754
- document.body.style.cursor = `default`
755
- }
756
-
757
- // Pan drag handlers
758
- const on_pan_move = (evt: MouseEvent) => {
759
- if (!pan_drag_state) return
760
- const dx = evt.clientX - pan_drag_state.start.x
761
- const dy = evt.clientY - pan_drag_state.start.y
762
-
763
- // Convert pixel delta to data delta (note: drag direction is inverted for natural pan feel)
764
- const plot_width = width - pad.l - pad.r
765
- const plot_height = height - pad.t - pad.b
766
- const sensitivity = pan?.drag_sensitivity ?? 1
767
-
768
- const x_delta = pixels_to_data_delta(
769
- -dx * sensitivity,
770
- pan_drag_state.initial_x_range,
771
- plot_width,
772
- )
773
- const x2_delta = pixels_to_data_delta(
774
- -dx * sensitivity,
775
- pan_drag_state.initial_x2_range,
776
- plot_width,
777
- )
778
- const y_delta = pixels_to_data_delta(
779
- dy * sensitivity,
780
- pan_drag_state.initial_y_range,
781
- plot_height,
782
- )
783
- const y2_delta = pixels_to_data_delta(
784
- dy * sensitivity,
785
- pan_drag_state.initial_y2_range,
786
- plot_height,
787
- )
788
-
789
- ranges.current.x = pan_range(pan_drag_state.initial_x_range, x_delta)
790
- ranges.current.x2 = pan_range(pan_drag_state.initial_x2_range, x2_delta)
791
- ranges.current.y = pan_range(pan_drag_state.initial_y_range, y_delta)
792
- ranges.current.y2 = pan_range(pan_drag_state.initial_y2_range, y2_delta)
793
- }
794
-
795
- const on_pan_end = () => {
796
- pan_drag_state = null
797
- document.body.style.cursor = ``
798
- window.removeEventListener(`mousemove`, on_pan_move)
799
- window.removeEventListener(`mouseup`, on_pan_end)
800
- }
801
-
802
- function handle_mouse_down(evt: MouseEvent) {
803
- const coords = get_relative_coords(evt)
804
- if (!coords || !svg_element) return
805
-
806
- // Check if pan is enabled and shift is held for pan mode
807
- const pan_enabled = pan?.enabled !== false
808
- if (pan_enabled && evt.shiftKey) {
809
- evt.preventDefault()
810
- pan_drag_state = {
811
- start: { x: evt.clientX, y: evt.clientY },
812
- initial_x_range: [...ranges.current.x] as [number, number],
813
- initial_x2_range: [...ranges.current.x2] as [number, number],
814
- initial_y_range: [...ranges.current.y] as [number, number],
815
- initial_y2_range: [...ranges.current.y2] as [number, number],
816
- }
817
- document.body.style.cursor = `grabbing`
818
- window.addEventListener(`mousemove`, on_pan_move)
819
- window.addEventListener(`mouseup`, on_pan_end)
820
- return
821
- }
822
-
823
- drag_state = {
824
- start: coords,
825
- current: coords,
826
- bounds: svg_element.getBoundingClientRect(),
827
- }
828
- window.addEventListener(`mousemove`, on_window_mouse_move)
829
- window.addEventListener(`mouseup`, on_window_mouse_up)
830
- evt.preventDefault()
831
- }
832
-
833
- // Wheel handler for pan (requires focus and shift)
834
- function handle_wheel(evt: WheelEvent) {
835
- const pan_enabled = pan?.enabled !== false
836
- // Only capture wheel when focused AND Shift is held
837
- // Use shift_held state (tracked via keydown/keyup) for compatibility with synthetic events
838
- if (!pan_enabled || !is_focused || !shift_held) return
839
-
840
- evt.preventDefault()
841
-
626
+ // Shared pan/zoom/touch/drag-rect interaction controller
627
+ const pan_zoom = create_pan_zoom({
628
+ ranges: () => ranges.current,
629
+ scale_type: (axis) =>
630
+ ({ x: final_x_axis, x2: final_x2_axis, y: final_y_axis, y2: final_y2_axis })[axis]
631
+ .scale_type,
842
632
  // Clamp to at least 1 to avoid Infinity deltas when padding equals container size
843
- const plot_width = Math.max(1, width - pad.l - pad.r)
844
- const plot_height = Math.max(1, height - pad.t - pad.b)
845
- const sensitivity = pan?.wheel_sensitivity ?? 1
846
-
847
- // Determine pan direction based on wheel delta
848
- const x_delta = pixels_to_data_delta(
849
- evt.deltaX * sensitivity,
850
- ranges.current.x,
851
- plot_width,
852
- )
853
- const x2_delta = pixels_to_data_delta(
854
- evt.deltaX * sensitivity,
855
- ranges.current.x2,
856
- plot_width,
857
- )
858
- const y_delta = pixels_to_data_delta(
859
- evt.deltaY * sensitivity,
860
- ranges.current.y,
861
- plot_height,
862
- )
863
- const y2_delta = pixels_to_data_delta(
864
- evt.deltaY * sensitivity,
865
- ranges.current.y2,
866
- plot_height,
867
- )
868
-
869
- if (Math.abs(evt.deltaX) > Math.abs(evt.deltaY)) {
870
- ranges.current.x = pan_range(ranges.current.x, x_delta)
871
- ranges.current.x2 = pan_range(ranges.current.x2, x2_delta)
872
- } else {
873
- ranges.current.y = pan_range(ranges.current.y, y_delta)
874
- ranges.current.y2 = pan_range(ranges.current.y2, y2_delta)
875
- }
876
- }
877
-
878
- // Touch handlers for pinch-zoom and two-finger pan
879
- function handle_touch_start(evt: TouchEvent) {
880
- const touch_enabled = pan?.enabled !== false && pan?.touch_enabled !== false
881
- if (!touch_enabled || evt.touches.length !== 2) return
882
-
883
- evt.preventDefault()
884
- const touches = Array.from(evt.touches)
885
- touch_state = {
886
- start_touches: touches.map((touch) => ({ x: touch.clientX, y: touch.clientY })),
887
- initial_x_range: [...ranges.current.x] as [number, number],
888
- initial_x2_range: [...ranges.current.x2] as [number, number],
889
- initial_y_range: [...ranges.current.y] as [number, number],
890
- initial_y2_range: [...ranges.current.y2] as [number, number],
891
- }
892
- }
893
-
894
- function handle_touch_move(evt: TouchEvent) {
895
- if (!touch_state || evt.touches.length !== 2) return
896
- evt.preventDefault()
897
-
898
- const [t1, t2] = Array.from(evt.touches)
899
- const [s1, s2] = touch_state.start_touches
900
-
901
- // Calculate center movement for pan
902
- const start_center = { x: (s1.x + s2.x) / 2, y: (s1.y + s2.y) / 2 }
903
- const curr_center = {
904
- x: (t1.clientX + t2.clientX) / 2,
905
- y: (t1.clientY + t2.clientY) / 2,
906
- }
907
- const dx = curr_center.x - start_center.x
908
- const dy = curr_center.y - start_center.y
909
-
910
- // Calculate pinch scale (curr/start so spread = zoom out, pinch = zoom in)
911
- const start_dist = Math.hypot(s2.x - s1.x, s2.y - s1.y)
912
- // Guard against zero-distance pinch to avoid Infinity scale
913
- if (start_dist < Number.EPSILON) return
914
- const curr_dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY)
915
- const scale = curr_dist / start_dist
916
-
917
- // Clamp to at least 1 to avoid Infinity deltas when padding equals container size
918
- const plot_width = Math.max(1, width - pad.l - pad.r)
919
- const plot_height = Math.max(1, height - pad.t - pad.b)
920
-
921
- // If scale changed significantly, treat as pinch-zoom
922
- // Also guard against scale being too small to avoid division by zero
923
- if (Math.abs(scale - 1) > PINCH_ZOOM_THRESHOLD && scale > Number.EPSILON) {
924
- // Pinch zoom centered on gesture center
925
- // Divide by scale so spread (scale > 1) = smaller span (zoom in)
926
- const x_span = touch_state.initial_x_range[1] - touch_state.initial_x_range[0]
927
- const x2_span = touch_state.initial_x2_range[1] -
928
- touch_state.initial_x2_range[0]
929
- const y_span = touch_state.initial_y_range[1] - touch_state.initial_y_range[0]
930
- const y2_span = touch_state.initial_y2_range[1] -
931
- touch_state.initial_y2_range[0]
932
- const x_center =
933
- (touch_state.initial_x_range[0] + touch_state.initial_x_range[1]) / 2
934
- const x2_center =
935
- (touch_state.initial_x2_range[0] + touch_state.initial_x2_range[1]) / 2
936
- const y_center =
937
- (touch_state.initial_y_range[0] + touch_state.initial_y_range[1]) / 2
938
- const y2_center =
939
- (touch_state.initial_y2_range[0] + touch_state.initial_y2_range[1]) / 2
940
-
941
- ranges.current.x = [
942
- x_center - x_span / scale / 2,
943
- x_center + x_span / scale / 2,
944
- ]
945
- ranges.current.x2 = [
946
- x2_center - x2_span / scale / 2,
947
- x2_center + x2_span / scale / 2,
948
- ]
949
- ranges.current.y = [
950
- y_center - y_span / scale / 2,
951
- y_center + y_span / scale / 2,
952
- ]
953
- ranges.current.y2 = [
954
- y2_center - y2_span / scale / 2,
955
- y2_center + y2_span / scale / 2,
956
- ]
957
- } else {
958
- // Pan
959
- const x_delta = pixels_to_data_delta(
960
- -dx,
961
- touch_state.initial_x_range,
962
- plot_width,
963
- )
964
- const x2_delta = pixels_to_data_delta(
965
- -dx,
966
- touch_state.initial_x2_range,
967
- plot_width,
968
- )
969
- const y_delta = pixels_to_data_delta(
970
- dy,
971
- touch_state.initial_y_range,
972
- plot_height,
973
- )
974
- const y2_delta = pixels_to_data_delta(
975
- dy,
976
- touch_state.initial_y2_range,
977
- plot_height,
978
- )
979
- ranges.current.x = pan_range(touch_state.initial_x_range, x_delta)
980
- ranges.current.x2 = pan_range(touch_state.initial_x2_range, x2_delta)
981
- ranges.current.y = pan_range(touch_state.initial_y_range, y_delta)
982
- ranges.current.y2 = pan_range(touch_state.initial_y2_range, y2_delta)
983
- }
984
- }
985
-
986
- function handle_touch_end() {
987
- touch_state = null
988
- }
989
-
990
- function handle_double_click() {
991
- // Reset zoom to initial ranges (undo any pan/zoom)
992
- ranges.current.x = [...ranges.initial.x] as [number, number]
993
- ranges.current.x2 = [...ranges.initial.x2] as [number, number]
994
- ranges.current.y = [...ranges.initial.y] as [number, number]
995
- ranges.current.y2 = [...ranges.initial.y2] as [number, number]
996
- // Also reset axis props so future data changes recalculate auto ranges
997
- x_axis = { ...x_axis, range: [null, null] }
998
- x2_axis = { ...x2_axis, range: [null, null] }
999
- y_axis = { ...y_axis, range: [null, null] }
1000
- y2_axis = { ...y2_axis, range: [null, null] }
1001
- }
633
+ plot_dims: () => ({
634
+ width: Math.max(1, width - pad.l - pad.r),
635
+ height: Math.max(1, height - pad.t - pad.b),
636
+ }),
637
+ pan: () => pan,
638
+ set_range: (axis, range) => (ranges.current[axis] = range),
639
+ svg: () => svg_element,
640
+ on_rect_zoom: (start, current) => {
641
+ // Update axis ranges to trigger reactivity and prevent effect from overriding
642
+ const next_x = invert_rect_range(scales.x, start.x, current.x)
643
+ if (!next_x) return
644
+ x_axis = { ...x_axis, range: next_x }
645
+ // gate x2/y2 on series presence: their scales are [0, 1] sentinels otherwise,
646
+ // so inverting would store a phantom range in the bindable prop
647
+ const next_x2 = x2_series.length > 0 ? invert_rect_range(scales.x2, start.x, current.x) : null
648
+ if (next_x2) x2_axis = { ...x2_axis, range: next_x2 }
649
+ const next_y = invert_rect_range(scales.y, start.y, current.y)
650
+ if (next_y) y_axis = { ...y_axis, range: next_y }
651
+ const next_y2 = y2_series.length > 0 ? invert_rect_range(scales.y2, start.y, current.y) : null
652
+ if (next_y2) y2_axis = { ...y2_axis, range: next_y2 }
653
+ },
654
+ on_reset: () => {
655
+ // Reset zoom to initial ranges (undo any pan/zoom)
656
+ ranges.current = {
657
+ x: [...ranges.initial.x] as Vec2,
658
+ x2: [...ranges.initial.x2] as Vec2,
659
+ y: [...ranges.initial.y] as Vec2,
660
+ y2: [...ranges.initial.y2] as Vec2,
661
+ }
662
+ // Also reset axis props so future data changes recalculate auto ranges
663
+ x_axis = { ...x_axis, range: [null, null] }
664
+ x2_axis = { ...x2_axis, range: [null, null] }
665
+ y_axis = { ...y_axis, range: [null, null] }
666
+ y2_axis = { ...y2_axis, range: [null, null] }
667
+ },
668
+ })
669
+ onDestroy(() => pan_zoom.destroy())
1002
670
 
1003
671
  function handle_mouse_move(
1004
672
  evt: MouseEvent,
@@ -1030,16 +698,7 @@
1030
698
  on_bar_hover?.({ value, count, property, event: evt })
1031
699
  }
1032
700
 
1033
- function toggle_series_visibility(series_idx: number) {
1034
- if (series_idx >= 0 && series_idx < series.length) {
1035
- // Toggle series visibility
1036
- series = series.map((srs: DataSeries, idx: number) => {
1037
- if (idx === series_idx) return { ...srs, visible: !(srs.visible ?? true) }
1038
- return srs
1039
- })
1040
- ;(legend?.on_toggle || on_series_toggle)(series_idx)
1041
- }
1042
- }
701
+ const legend_vis = create_legend_visibility(() => series, (next) => (series = next))
1043
702
 
1044
703
  // Set theme-aware background when entering fullscreen
1045
704
  $effect(() => {
@@ -1067,32 +726,12 @@
1067
726
  set_loading: (axis) => (axis_loading = axis),
1068
727
  }
1069
728
 
1070
- // Create shared handler bound to this component's state
1071
- // Using $derived so handler updates when callback props change
1072
- const handle_axis_change = $derived(create_axis_change_handler(
729
+ // Shared handler + one-shot auto-load bound to this component's state
730
+ const { handle_axis_change, try_auto_load } = create_axis_loader(
1073
731
  axis_state,
1074
- data_loader,
1075
- on_axis_change,
1076
- on_error,
1077
- ))
1078
-
1079
- let auto_load_attempted = false // prevent infinite retries on failure
1080
-
1081
- // Auto-load data if series is empty but options exist (runs once)
1082
- $effect(() => {
1083
- if (series.length === 0 && data_loader && !auto_load_attempted) {
1084
- // Check x-axis first, then y-axis
1085
- if (x_axis.options?.length) {
1086
- auto_load_attempted = true
1087
- const first_key = x_axis.selected_key ?? x_axis.options[0].key
1088
- handle_axis_change(`x`, first_key).catch(() => {})
1089
- } else if (y_axis.options?.length) {
1090
- auto_load_attempted = true
1091
- const first_key = y_axis.selected_key ?? y_axis.options[0].key
1092
- handle_axis_change(`y`, first_key).catch(() => {})
1093
- }
1094
- }
1095
- })
732
+ () => ({ data_loader, on_axis_change, on_error }),
733
+ )
734
+ $effect(try_auto_load)
1096
735
  </script>
1097
736
 
1098
737
  {#snippet ref_lines_layer(lines: IndexedRefLine[])}
@@ -1129,11 +768,9 @@
1129
768
  evt.preventDefault()
1130
769
  fullscreen = false
1131
770
  }
1132
- if (evt.key === `Shift`) shift_held = true
1133
- }}
1134
- onkeyup={(evt) => {
1135
- if (evt.key === `Shift`) shift_held = false
771
+ pan_zoom.on_window_key_down(evt)
1136
772
  }}
773
+ onkeyup={pan_zoom.on_window_key_up}
1137
774
  />
1138
775
 
1139
776
  <div
@@ -1161,34 +798,23 @@
1161
798
  ([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) ||
1162
799
  `Histogram`)}
1163
800
  tabindex="0"
1164
- onfocusin={() => (is_focused = true)}
1165
- onfocusout={() => (is_focused = false)}
801
+ onfocusin={() => pan_zoom.set_focused(true)}
802
+ onfocusout={() => pan_zoom.set_focused(false)}
1166
803
  onmouseenter={() => (hovered = true)}
1167
- onmousedown={handle_mouse_down}
804
+ onmousedown={pan_zoom.on_mouse_down}
1168
805
  onmouseleave={() => {
1169
806
  hovered = false
1170
807
  hover_info = null
1171
808
  on_bar_hover?.(null)
1172
809
  }}
1173
- ondblclick={handle_double_click}
1174
- onwheel={handle_wheel}
1175
- ontouchstart={handle_touch_start}
1176
- ontouchmove={handle_touch_move}
1177
- ontouchend={handle_touch_end}
1178
- style:cursor={pan_drag_state
1179
- ? `grabbing`
1180
- : shift_held && pan?.enabled !== false
1181
- ? `grab`
1182
- : `crosshair`}
1183
- onkeydown={(event) => {
1184
- if (event.key === `Escape` && drag_state.start) {
1185
- drag_state = { start: null, current: null, bounds: null }
1186
- }
1187
- if ([`Enter`, ` `].includes(event.key)) {
1188
- event.preventDefault()
1189
- handle_double_click()
1190
- }
1191
- }}
810
+ ondblclick={pan_zoom.reset_view}
811
+ onwheel={pan_zoom.on_wheel}
812
+ ontouchstart={pan_zoom.on_touch_start}
813
+ ontouchmove={pan_zoom.on_touch_move}
814
+ ontouchend={pan_zoom.on_touch_end}
815
+ ontouchcancel={pan_zoom.on_touch_end}
816
+ style:cursor={pan_zoom.cursor}
817
+ onkeydown={pan_zoom.on_key_down}
1192
818
  >
1193
819
  <!-- Define clip path for chart area -->
1194
820
  <defs>
@@ -1205,7 +831,7 @@
1205
831
  <!-- Reference lines: below grid (must render first to appear behind grid) -->
1206
832
  {@render ref_lines_layer(ref_lines_by_z.below_grid)}
1207
833
 
1208
- <ZoomRect start={drag_state.start} current={drag_state.current} />
834
+ <ZoomRect start={pan_zoom.drag_start} current={pan_zoom.drag_current} />
1209
835
 
1210
836
  <ZeroLines
1211
837
  {display}
@@ -1240,6 +866,7 @@
1240
866
  ticks={ticks.x as number[]}
1241
867
  place={scales.x}
1242
868
  axis={final_x_axis}
869
+ domain={ranges.current.x as Vec2}
1243
870
  {pad}
1244
871
  {width}
1245
872
  {height}
@@ -1258,6 +885,7 @@
1258
885
  ticks={ticks.x2 as number[]}
1259
886
  place={scales.x2}
1260
887
  axis={final_x2_axis}
888
+ domain={ranges.current.x2 as Vec2}
1261
889
  {pad}
1262
890
  {width}
1263
891
  {height}
@@ -1276,6 +904,7 @@
1276
904
  ticks={ticks.y as number[]}
1277
905
  place={scales.y}
1278
906
  axis={final_y_axis}
907
+ domain={ranges.current.y as Vec2}
1279
908
  {pad}
1280
909
  {width}
1281
910
  {height}
@@ -1293,21 +922,18 @@
1293
922
 
1294
923
  <!-- Y2-axis (Right) -->
1295
924
  {#if y2_series.length > 0}
1296
- {@const y2_inside = final_y2_axis.tick?.label?.inside ?? false}
1297
- {@const y2_tick_shift = y2_inside ? 0 : (final_y2_axis.tick?.label?.shift?.x ?? 0) + 8}
1298
- {@const y2_tick_width = y2_inside ? 0 : tick_label_widths.y2_max}
1299
925
  <PlotAxis
1300
926
  side="y2"
1301
927
  ticks={ticks.y2 as number[]}
1302
928
  place={scales.y2}
1303
929
  axis={final_y2_axis}
930
+ domain={ranges.current.y2 as Vec2}
1304
931
  {pad}
1305
932
  {width}
1306
933
  {height}
1307
934
  show_grid={display.y2_grid}
1308
935
  tick_label={(tick) => get_tick_label(tick, final_y2_axis.ticks)}
1309
- label_x={width - pad.r + y2_tick_shift + y2_tick_width + LABEL_GAP_DEFAULT +
1310
- (final_y2_axis.label_shift?.x ?? 0)}
936
+ label_x={y2_axis_label_x(final_y2_axis, width, pad.r, tick_label_widths.y2_max)}
1311
937
  label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y2_axis.label_shift?.y ?? 0)}
1312
938
  axis_loading={axis_loading === `y2`}
1313
939
  on_axis_change={(key) => handle_axis_change(`y2`, key)}
@@ -1323,6 +949,7 @@
1323
949
  <g
1324
950
  class="histogram-series"
1325
951
  data-series-idx={series_idx}
952
+ clip-path="url(#{clip_path_id})"
1326
953
  opacity={hovered_legend_series_idx !== null &&
1327
954
  hovered_legend_series_idx !== series_idx
1328
955
  ? 0.25
@@ -1389,20 +1016,12 @@
1389
1016
  {@const { value, count, property, active_y_axis, active_x_axis } = hover_info}
1390
1017
  {@const tooltip_x = (active_x_axis === `x2` ? scales.x2 : scales.x)(value)}
1391
1018
  {@const tooltip_y = (active_y_axis === `y2` ? scales.y2 : scales.y)(count)}
1392
- {@const tooltip_pos = constrain_tooltip_position(
1393
- tooltip_x,
1394
- tooltip_y,
1395
- tooltip_el?.offsetWidth ?? 120,
1396
- tooltip_el?.offsetHeight ?? (mode === `overlay` ? 60 : 40),
1397
- width,
1398
- height,
1399
- { offset_x: 5, offset_y: -10 },
1400
- )}
1401
1019
  <PlotTooltip
1402
- x={tooltip_pos.x}
1403
- y={tooltip_pos.y}
1404
- offset={{ x: 0, y: 0 }}
1405
- bind:wrapper={tooltip_el}
1020
+ x={tooltip_x}
1021
+ y={tooltip_y}
1022
+ offset={{ x: 5, y: -10 }}
1023
+ constrain_to={{ width, height }}
1024
+ fallback_size={{ width: 120, height: mode === `overlay` ? 60 : 40 }}
1406
1025
  >
1407
1026
  {#if tooltip}
1408
1027
  {@render tooltip({ ...hover_info, fullscreen })}
@@ -1447,31 +1066,33 @@
1447
1066
  {/if}
1448
1067
 
1449
1068
  {#if show_legend && legend != null && series.length > 1}
1450
- {@const legend_left = legend_auto_outside
1451
- ? legend_outside_x
1452
- : legend_placement
1453
- ? tweened_legend_coords.current.x
1454
- : pad.l + 10}
1455
- {@const legend_top = legend_auto_outside
1456
- ? legend_outside_y
1457
- : legend_placement
1458
- ? tweened_legend_coords.current.y
1459
- : pad.t + 10}
1069
+ {@const legend_pos = placed_coords(
1070
+ legend_auto_outside,
1071
+ { x: legend_outside_x, y: legend_outside_y },
1072
+ legend_placement,
1073
+ legend_tween.coords.current,
1074
+ { x: pad.l + 10, y: pad.t + 10 },
1075
+ )}
1460
1076
  <PlotLegend
1461
1077
  bind:root_element={legend_element}
1462
1078
  {...legend}
1463
1079
  series_data={legend_data}
1464
- on_toggle={legend?.on_toggle || toggle_series_visibility}
1465
- on_hover_change={legend_hover.set_locked}
1466
- on_item_hover={(series_idx: number | null) =>
1467
- (hovered_legend_series_idx = series_idx != null && series_idx >= 0
1468
- ? series_idx
1080
+ on_toggle={legend?.on_toggle ?? ((series_idx: number) => {
1081
+ if (series_idx < 0 || series_idx >= series.length) return
1082
+ legend_vis.on_toggle(series_idx)
1083
+ on_series_toggle(series_idx)
1084
+ })}
1085
+ on_double_click={legend?.on_double_click ?? legend_vis.on_double_click}
1086
+ on_hover_change={legend_tween.set_locked}
1087
+ on_item_hover={(item) =>
1088
+ (hovered_legend_series_idx = item != null && item.series_idx >= 0
1089
+ ? item.series_idx
1469
1090
  : null)}
1470
1091
  active_series_idx={hover_info?.series_idx ?? hovered_legend_series_idx}
1471
1092
  style={`
1472
1093
  position: absolute;
1473
- left: ${legend_left}px;
1474
- top: ${legend_top}px;
1094
+ left: ${legend_pos.x}px;
1095
+ top: ${legend_pos.y}px;
1475
1096
  pointer-events: auto;
1476
1097
  ${legend?.style || ``}
1477
1098
  `}
@@ -1509,8 +1130,10 @@
1509
1130
  background: var(--histogram-fullscreen-bg, var(--histogram-bg, var(--plot-bg)));
1510
1131
  max-height: none !important;
1511
1132
  overflow: hidden;
1512
- /* Add padding to prevent titles from being cropped at top */
1513
- padding-top: var(--plot-fullscreen-padding-top, 2em);
1133
+ /* border-top (not padding-top): bind:clientHeight includes padding but excludes
1134
+ borders - padding made the chart overflow + clip its bottom 2em (x-axis title) */
1135
+ border-top: var(--plot-fullscreen-padding-top, 2em) solid
1136
+ var(--histogram-fullscreen-bg, var(--histogram-bg, var(--plot-bg, transparent)));
1514
1137
  box-sizing: border-box;
1515
1138
  }
1516
1139
  .header-controls {