matterviz 0.3.7 → 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 (324) 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 +8 -3
  6. package/dist/brillouin/BrillouinZone.svelte.d.ts +2 -1
  7. package/dist/brillouin/BrillouinZoneScene.svelte +52 -6
  8. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -0
  9. package/dist/brillouin/BrillouinZoneTooltip.svelte +16 -25
  10. package/dist/brillouin/compute.js +10 -14
  11. package/dist/chempot-diagram/ChemPotDiagram.svelte +14 -13
  12. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +12 -15
  13. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +8 -10
  14. package/dist/chempot-diagram/async-compute.svelte.js +3 -1
  15. package/dist/chempot-diagram/chempot-worker.js +2 -1
  16. package/dist/chempot-diagram/compute.d.ts +1 -1
  17. package/dist/chempot-diagram/compute.js +17 -19
  18. package/dist/colors/index.js +6 -5
  19. package/dist/composition/FormulaFilter.svelte +12 -6
  20. package/dist/composition/PieChart.svelte +6 -5
  21. package/dist/composition/chem-sys.d.ts +8 -0
  22. package/dist/composition/chem-sys.js +85 -0
  23. package/dist/composition/format.js +4 -2
  24. package/dist/composition/index.d.ts +1 -0
  25. package/dist/composition/index.js +1 -0
  26. package/dist/composition/parse.js +25 -13
  27. package/dist/convex-hull/ConvexHull2D.svelte +12 -10
  28. package/dist/convex-hull/ConvexHull3D.svelte +5 -5
  29. package/dist/convex-hull/ConvexHull4D.svelte +5 -9
  30. package/dist/convex-hull/ConvexHullStats.svelte +12 -12
  31. package/dist/convex-hull/GasPressureControls.svelte +4 -4
  32. package/dist/convex-hull/TemperatureSlider.svelte +2 -2
  33. package/dist/convex-hull/demo-temperature.d.ts +1 -1
  34. package/dist/convex-hull/demo-temperature.js +20 -22
  35. package/dist/convex-hull/gas-thermodynamics.d.ts +2 -2
  36. package/dist/convex-hull/gas-thermodynamics.js +22 -30
  37. package/dist/convex-hull/helpers.d.ts +3 -0
  38. package/dist/convex-hull/helpers.js +17 -9
  39. package/dist/convex-hull/index.d.ts +1 -1
  40. package/dist/convex-hull/thermodynamics.js +83 -78
  41. package/dist/convex-hull/types.d.ts +1 -1
  42. package/dist/coordination/CoordinationBarPlot.svelte +23 -23
  43. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  44. package/dist/element/ElementTile.svelte.d.ts +1 -1
  45. package/dist/fermi-surface/FermiSlice.svelte +13 -5
  46. package/dist/fermi-surface/FermiSurface.svelte +11 -5
  47. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  48. package/dist/fermi-surface/FermiSurfaceControls.svelte +1 -1
  49. package/dist/fermi-surface/FermiSurfaceScene.svelte +3 -0
  50. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +8 -34
  51. package/dist/fermi-surface/compute.js +59 -59
  52. package/dist/fermi-surface/export.js +3 -2
  53. package/dist/fermi-surface/parse.js +7 -4
  54. package/dist/fermi-surface/types.d.ts +1 -0
  55. package/dist/heatmap-matrix/HeatmapMatrix.svelte +23 -21
  56. package/dist/heatmap-matrix/index.js +1 -1
  57. package/dist/io/decompress.js +4 -2
  58. package/dist/io/export.d.ts +4 -4
  59. package/dist/io/export.js +47 -25
  60. package/dist/io/fetch.js +5 -1
  61. package/dist/io/file-drop.d.ts +1 -1
  62. package/dist/io/file-drop.js +35 -36
  63. package/dist/io/url-drop.js +64 -33
  64. package/dist/isosurface/parse.js +6 -7
  65. package/dist/isosurface/slice.js +5 -4
  66. package/dist/isosurface/types.js +1 -1
  67. package/dist/keyboard.d.ts +3 -0
  68. package/dist/keyboard.js +23 -0
  69. package/dist/labels.d.ts +1 -1
  70. package/dist/labels.js +8 -7
  71. package/dist/layout/PropertyFilter.svelte +3 -2
  72. package/dist/layout/SettingsSection.svelte +1 -1
  73. package/dist/layout/json-tree/JsonNode.svelte +1 -1
  74. package/dist/layout/json-tree/JsonTree.svelte +2 -2
  75. package/dist/layout/json-tree/utils.js +5 -4
  76. package/dist/marching-cubes.js +8 -13
  77. package/dist/math.d.ts +5 -1
  78. package/dist/math.js +24 -9
  79. package/dist/overlays/DraggablePane.svelte +4 -4
  80. package/dist/periodic-table/PeriodicTable.svelte +20 -9
  81. package/dist/periodic-table/PropertySelect.svelte +1 -0
  82. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +9 -3
  83. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  84. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  85. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  86. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +1 -1
  87. package/dist/phase-diagram/build-diagram.js +2 -2
  88. package/dist/phase-diagram/parse.js +6 -5
  89. package/dist/phase-diagram/types.d.ts +1 -1
  90. package/dist/phase-diagram/utils.d.ts +3 -3
  91. package/dist/phase-diagram/utils.js +8 -12
  92. package/dist/plot/{BarPlot.svelte → bar/BarPlot.svelte} +229 -587
  93. package/dist/plot/{BarPlot.svelte.d.ts → bar/BarPlot.svelte.d.ts} +5 -5
  94. package/dist/plot/{BarPlotControls.svelte → bar/BarPlotControls.svelte} +6 -5
  95. package/dist/plot/{BarPlotControls.svelte.d.ts → bar/BarPlotControls.svelte.d.ts} +3 -3
  96. package/dist/plot/{SpacegroupBarPlot.svelte → bar/SpacegroupBarPlot.svelte} +6 -6
  97. package/dist/plot/{SpacegroupBarPlot.svelte.d.ts → bar/SpacegroupBarPlot.svelte.d.ts} +1 -1
  98. package/dist/plot/bar/data.d.ts +40 -0
  99. package/dist/plot/bar/data.js +154 -0
  100. package/dist/plot/bar/geometry.d.ts +39 -0
  101. package/dist/plot/bar/geometry.js +60 -0
  102. package/dist/plot/bar/index.d.ts +3 -0
  103. package/dist/plot/bar/index.js +3 -0
  104. package/dist/plot/box/BoxPlot.svelte +1462 -0
  105. package/dist/plot/box/BoxPlot.svelte.d.ts +94 -0
  106. package/dist/plot/box/BoxPlotControls.svelte +109 -0
  107. package/dist/plot/box/BoxPlotControls.svelte.d.ts +19 -0
  108. package/dist/plot/box/Violin.svelte +14 -0
  109. package/dist/plot/box/Violin.svelte.d.ts +70 -0
  110. package/dist/plot/box/box-plot.d.ts +55 -0
  111. package/dist/plot/box/box-plot.js +126 -0
  112. package/dist/plot/box/index.d.ts +5 -0
  113. package/dist/plot/box/index.js +5 -0
  114. package/dist/plot/box/kde.d.ts +16 -0
  115. package/dist/plot/box/kde.js +160 -0
  116. package/dist/plot/box/quantile.d.ts +3 -0
  117. package/dist/plot/box/quantile.js +53 -0
  118. package/dist/plot/{auto-place.js → core/auto-place.js} +2 -2
  119. package/dist/plot/core/axis-utils.d.ts +46 -0
  120. package/dist/plot/core/axis-utils.js +110 -0
  121. package/dist/plot/{AxisLabel.svelte → core/components/AxisLabel.svelte} +2 -2
  122. package/dist/plot/{AxisLabel.svelte.d.ts → core/components/AxisLabel.svelte.d.ts} +1 -1
  123. package/dist/plot/{ColorBar.svelte → core/components/ColorBar.svelte} +36 -33
  124. package/dist/plot/{ColorBar.svelte.d.ts → core/components/ColorBar.svelte.d.ts} +2 -2
  125. package/dist/plot/{ColorScaleSelect.svelte → core/components/ColorScaleSelect.svelte} +4 -3
  126. package/dist/plot/{ColorScaleSelect.svelte.d.ts → core/components/ColorScaleSelect.svelte.d.ts} +2 -2
  127. package/dist/plot/core/components/ControlPane.svelte +46 -0
  128. package/dist/plot/core/components/ControlPane.svelte.d.ts +13 -0
  129. package/dist/plot/{FillArea.svelte → core/components/FillArea.svelte} +17 -6
  130. package/dist/plot/{FillArea.svelte.d.ts → core/components/FillArea.svelte.d.ts} +1 -1
  131. package/dist/plot/{InteractiveAxisLabel.svelte → core/components/InteractiveAxisLabel.svelte} +3 -3
  132. package/dist/plot/{InteractiveAxisLabel.svelte.d.ts → core/components/InteractiveAxisLabel.svelte.d.ts} +2 -2
  133. package/dist/plot/{Line.svelte → core/components/Line.svelte} +30 -13
  134. package/dist/plot/{PlotAxis.svelte → core/components/PlotAxis.svelte} +7 -5
  135. package/dist/plot/{PlotAxis.svelte.d.ts → core/components/PlotAxis.svelte.d.ts} +3 -2
  136. package/dist/plot/{PlotControls.svelte → core/components/PlotControls.svelte} +17 -29
  137. package/dist/plot/core/components/PlotControls.svelte.d.ts +4 -0
  138. package/dist/plot/{PlotLegend.svelte → core/components/PlotLegend.svelte} +21 -10
  139. package/dist/plot/{PlotLegend.svelte.d.ts → core/components/PlotLegend.svelte.d.ts} +3 -2
  140. package/dist/plot/{PlotTooltip.svelte → core/components/PlotTooltip.svelte} +17 -1
  141. package/dist/plot/{PlotTooltip.svelte.d.ts → core/components/PlotTooltip.svelte.d.ts} +8 -0
  142. package/dist/plot/{PortalSelect.svelte → core/components/PortalSelect.svelte} +11 -7
  143. package/dist/plot/{ReferenceLine.svelte → core/components/ReferenceLine.svelte} +3 -3
  144. package/dist/plot/{ReferenceLine.svelte.d.ts → core/components/ReferenceLine.svelte.d.ts} +1 -1
  145. package/dist/plot/{ReferenceLine3D.svelte → core/components/ReferenceLine3D.svelte} +4 -4
  146. package/dist/plot/{ReferenceLine3D.svelte.d.ts → core/components/ReferenceLine3D.svelte.d.ts} +2 -2
  147. package/dist/plot/{ReferencePlane.svelte → core/components/ReferencePlane.svelte} +7 -7
  148. package/dist/plot/{ReferencePlane.svelte.d.ts → core/components/ReferencePlane.svelte.d.ts} +2 -2
  149. package/dist/plot/{ZeroLines.svelte → core/components/ZeroLines.svelte} +3 -3
  150. package/dist/plot/{ZeroLines.svelte.d.ts → core/components/ZeroLines.svelte.d.ts} +3 -3
  151. package/dist/plot/{ZoomRect.svelte → core/components/ZoomRect.svelte} +1 -1
  152. package/dist/plot/{ZoomRect.svelte.d.ts → core/components/ZoomRect.svelte.d.ts} +1 -1
  153. package/dist/plot/core/components/index.d.ts +17 -0
  154. package/dist/plot/core/components/index.js +17 -0
  155. package/dist/plot/{data-cleaning.d.ts → core/data-cleaning.d.ts} +71 -1
  156. package/dist/plot/{data-cleaning.js → core/data-cleaning.js} +3 -5
  157. package/dist/plot/{data-transform.d.ts → core/data-transform.d.ts} +2 -2
  158. package/dist/plot/{data-transform.js → core/data-transform.js} +3 -3
  159. package/dist/plot/core/fill-utils.d.ts +33 -0
  160. package/dist/plot/core/fill-utils.js +388 -0
  161. package/dist/plot/{hover-lock.svelte.js → core/hover-lock.svelte.js} +5 -6
  162. package/dist/plot/core/index.d.ts +10 -0
  163. package/dist/plot/core/index.js +11 -0
  164. package/dist/plot/core/interactions.d.ts +35 -0
  165. package/dist/plot/core/interactions.js +195 -0
  166. package/dist/plot/{layout.d.ts → core/layout.d.ts} +1 -0
  167. package/dist/plot/{layout.js → core/layout.js} +16 -8
  168. package/dist/plot/{reference-line.d.ts → core/reference-line.d.ts} +1 -1
  169. package/dist/plot/{reference-line.js → core/reference-line.js} +23 -36
  170. package/dist/plot/{scales.d.ts → core/scales.d.ts} +2 -2
  171. package/dist/plot/{scales.js → core/scales.js} +84 -85
  172. package/dist/plot/core/svg.d.ts +2 -0
  173. package/dist/plot/core/svg.js +41 -0
  174. package/dist/plot/{types.d.ts → core/types.d.ts} +19 -79
  175. package/dist/plot/{types.js → core/types.js} +1 -1
  176. package/dist/plot/{utils → core/utils}/label-placement.d.ts +2 -2
  177. package/dist/plot/core/utils/series-visibility.d.ts +26 -0
  178. package/dist/plot/{utils → core/utils}/series-visibility.js +29 -2
  179. package/dist/plot/core/utils.d.ts +11 -0
  180. package/dist/plot/core/utils.js +27 -0
  181. package/dist/plot/{Histogram.svelte → histogram/Histogram.svelte} +154 -294
  182. package/dist/plot/{Histogram.svelte.d.ts → histogram/Histogram.svelte.d.ts} +2 -2
  183. package/dist/plot/{HistogramControls.svelte → histogram/HistogramControls.svelte} +6 -6
  184. package/dist/plot/{HistogramControls.svelte.d.ts → histogram/HistogramControls.svelte.d.ts} +4 -4
  185. package/dist/plot/histogram/index.d.ts +2 -0
  186. package/dist/plot/histogram/index.js +2 -0
  187. package/dist/plot/index.d.ts +8 -41
  188. package/dist/plot/index.js +10 -39
  189. package/dist/plot/sankey/Sankey.svelte +700 -0
  190. package/dist/plot/sankey/Sankey.svelte.d.ts +74 -0
  191. package/dist/plot/sankey/SankeyControls.svelte +98 -0
  192. package/dist/plot/sankey/SankeyControls.svelte.d.ts +19 -0
  193. package/dist/plot/sankey/index.d.ts +4 -0
  194. package/dist/plot/sankey/index.js +3 -0
  195. package/dist/plot/sankey/sankey-types.d.ts +42 -0
  196. package/dist/plot/sankey/sankey-types.js +4 -0
  197. package/dist/plot/sankey/sankey.d.ts +52 -0
  198. package/dist/plot/sankey/sankey.js +187 -0
  199. package/dist/plot/{BinnedScatterPlot.svelte → scatter/BinnedScatterPlot.svelte} +61 -59
  200. package/dist/plot/{BinnedScatterPlot.svelte.d.ts → scatter/BinnedScatterPlot.svelte.d.ts} +4 -4
  201. package/dist/plot/{ElementScatter.svelte → scatter/ElementScatter.svelte} +6 -6
  202. package/dist/plot/{ElementScatter.svelte.d.ts → scatter/ElementScatter.svelte.d.ts} +2 -2
  203. package/dist/plot/{ScatterPlot.svelte → scatter/ScatterPlot.svelte} +221 -642
  204. package/dist/plot/{ScatterPlot.svelte.d.ts → scatter/ScatterPlot.svelte.d.ts} +7 -7
  205. package/dist/plot/{ScatterPlotControls.svelte → scatter/ScatterPlotControls.svelte} +6 -5
  206. package/dist/plot/{ScatterPlotControls.svelte.d.ts → scatter/ScatterPlotControls.svelte.d.ts} +1 -1
  207. package/dist/plot/{ScatterPoint.svelte → scatter/ScatterPoint.svelte} +7 -7
  208. package/dist/plot/{ScatterPoint.svelte.d.ts → scatter/ScatterPoint.svelte.d.ts} +3 -3
  209. package/dist/plot/{adaptive-density.d.ts → scatter/adaptive-density.d.ts} +14 -4
  210. package/dist/plot/{adaptive-density.js → scatter/adaptive-density.js} +46 -20
  211. package/dist/plot/{binned-scatter-types.d.ts → scatter/binned-scatter-types.d.ts} +3 -3
  212. package/dist/plot/scatter/index.d.ts +7 -0
  213. package/dist/plot/scatter/index.js +5 -0
  214. package/dist/plot/scatter/scatter-data.d.ts +19 -0
  215. package/dist/plot/scatter/scatter-data.js +212 -0
  216. package/dist/plot/{ScatterPlot3D.svelte → scatter-3d/ScatterPlot3D.svelte} +12 -10
  217. package/dist/plot/{ScatterPlot3D.svelte.d.ts → scatter-3d/ScatterPlot3D.svelte.d.ts} +7 -7
  218. package/dist/plot/{ScatterPlot3DControls.svelte → scatter-3d/ScatterPlot3DControls.svelte} +5 -4
  219. package/dist/plot/{ScatterPlot3DControls.svelte.d.ts → scatter-3d/ScatterPlot3DControls.svelte.d.ts} +2 -2
  220. package/dist/plot/{ScatterPlot3DScene.svelte → scatter-3d/ScatterPlot3DScene.svelte} +11 -11
  221. package/dist/plot/{ScatterPlot3DScene.svelte.d.ts → scatter-3d/ScatterPlot3DScene.svelte.d.ts} +3 -3
  222. package/dist/plot/{Surface3D.svelte → scatter-3d/Surface3D.svelte} +1 -1
  223. package/dist/plot/{Surface3D.svelte.d.ts → scatter-3d/Surface3D.svelte.d.ts} +1 -1
  224. package/dist/plot/scatter-3d/index.d.ts +4 -0
  225. package/dist/plot/scatter-3d/index.js +4 -0
  226. package/dist/plot/sunburst/Sunburst.svelte +1045 -0
  227. package/dist/plot/sunburst/Sunburst.svelte.d.ts +96 -0
  228. package/dist/plot/sunburst/SunburstControls.svelte +200 -0
  229. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +26 -0
  230. package/dist/plot/sunburst/index.d.ts +4 -0
  231. package/dist/plot/sunburst/index.js +4 -0
  232. package/dist/plot/sunburst/render.d.ts +34 -0
  233. package/dist/plot/sunburst/render.js +122 -0
  234. package/dist/plot/sunburst/sunburst.d.ts +62 -0
  235. package/dist/plot/sunburst/sunburst.js +266 -0
  236. package/dist/rdf/RdfPlot.svelte +2 -1
  237. package/dist/rdf/calc-rdf.js +11 -24
  238. package/dist/sanitize.js +1 -1
  239. package/dist/settings.d.ts +65 -1
  240. package/dist/settings.js +262 -0
  241. package/dist/spectral/Bands.svelte +39 -29
  242. package/dist/spectral/Bands.svelte.d.ts +3 -4
  243. package/dist/spectral/BandsAndDos.svelte +1 -1
  244. package/dist/spectral/BrillouinBandsDos.svelte +39 -27
  245. package/dist/spectral/Dos.svelte +10 -19
  246. package/dist/spectral/Dos.svelte.d.ts +2 -2
  247. package/dist/spectral/helpers.d.ts +3 -1
  248. package/dist/spectral/helpers.js +95 -29
  249. package/dist/structure/AtomLegend.svelte +8 -9
  250. package/dist/structure/CellSelect.svelte +1 -2
  251. package/dist/structure/Cylinder.svelte +12 -8
  252. package/dist/structure/Cylinder.svelte.d.ts +4 -1
  253. package/dist/structure/Structure.svelte +78 -72
  254. package/dist/structure/Structure.svelte.d.ts +1 -1
  255. package/dist/structure/StructureInfoPane.svelte +5 -6
  256. package/dist/structure/StructureScene.svelte +11 -10
  257. package/dist/structure/atom-properties.js +6 -6
  258. package/dist/structure/bond-order-perception.js +1 -1
  259. package/dist/structure/bonding.d.ts +1 -0
  260. package/dist/structure/bonding.js +43 -15
  261. package/dist/structure/export.js +27 -23
  262. package/dist/structure/index.d.ts +2 -4
  263. package/dist/structure/index.js +1 -3
  264. package/dist/structure/label-placement.js +4 -4
  265. package/dist/structure/measure.d.ts +3 -2
  266. package/dist/structure/measure.js +6 -5
  267. package/dist/structure/parse.js +121 -103
  268. package/dist/structure/pbc.js +4 -0
  269. package/dist/symmetry/SymmetryStats.svelte +2 -2
  270. package/dist/symmetry/index.d.ts +1 -1
  271. package/dist/symmetry/index.js +22 -24
  272. package/dist/symmetry/spacegroups.d.ts +7 -0
  273. package/dist/symmetry/spacegroups.js +48 -13
  274. package/dist/table/HeatmapTable.svelte +63 -11
  275. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  276. package/dist/table/index.d.ts +1 -3
  277. package/dist/table/index.js +1 -1
  278. package/dist/theme/index.js +8 -8
  279. package/dist/tooltip/KCoords.svelte +45 -0
  280. package/dist/tooltip/KCoords.svelte.d.ts +8 -0
  281. package/dist/tooltip/index.d.ts +1 -0
  282. package/dist/tooltip/index.js +1 -0
  283. package/dist/trajectory/Trajectory.svelte +66 -40
  284. package/dist/trajectory/Trajectory.svelte.d.ts +2 -1
  285. package/dist/trajectory/TrajectoryExportPane.svelte +2 -1
  286. package/dist/trajectory/TrajectoryInfoPane.svelte +2 -1
  287. package/dist/trajectory/format-detect.d.ts +1 -0
  288. package/dist/trajectory/format-detect.js +25 -11
  289. package/dist/trajectory/frame-reader.js +17 -50
  290. package/dist/trajectory/helpers.js +1 -1
  291. package/dist/trajectory/index.js +1 -1
  292. package/dist/trajectory/parse/hdf5.js +1 -1
  293. package/dist/trajectory/parse/index.js +14 -6
  294. package/dist/trajectory/parse/vasp.js +36 -17
  295. package/dist/trajectory/parse/xyz.d.ts +24 -0
  296. package/dist/trajectory/parse/xyz.js +102 -89
  297. package/dist/trajectory/plotting.d.ts +1 -1
  298. package/dist/trajectory/plotting.js +15 -15
  299. package/dist/utils.d.ts +1 -0
  300. package/dist/utils.js +6 -4
  301. package/dist/xrd/XrdPlot.svelte +2 -1
  302. package/dist/xrd/calc-xrd.js +15 -12
  303. package/dist/xrd/parse.js +2 -2
  304. package/package.json +22 -18
  305. package/dist/plot/PlotControls.svelte.d.ts +0 -4
  306. package/dist/plot/axis-utils.d.ts +0 -19
  307. package/dist/plot/axis-utils.js +0 -78
  308. package/dist/plot/defaults.d.ts +0 -19
  309. package/dist/plot/defaults.js +0 -9
  310. package/dist/plot/fill-utils.d.ts +0 -46
  311. package/dist/plot/fill-utils.js +0 -322
  312. package/dist/plot/interactions.d.ts +0 -12
  313. package/dist/plot/interactions.js +0 -101
  314. package/dist/plot/svg.d.ts +0 -1
  315. package/dist/plot/svg.js +0 -11
  316. package/dist/plot/utils/series-visibility.d.ts +0 -15
  317. package/dist/plot/utils.d.ts +0 -1
  318. package/dist/plot/utils.js +0 -14
  319. /package/dist/plot/{auto-place.d.ts → core/auto-place.d.ts} +0 -0
  320. /package/dist/plot/{Line.svelte.d.ts → core/components/Line.svelte.d.ts} +0 -0
  321. /package/dist/plot/{PortalSelect.svelte.d.ts → core/components/PortalSelect.svelte.d.ts} +0 -0
  322. /package/dist/plot/{hover-lock.svelte.d.ts → core/hover-lock.svelte.d.ts} +0 -0
  323. /package/dist/plot/{utils → core/utils}/label-placement.js +0 -0
  324. /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,50 +9,55 @@
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'
24
23
  import {
25
24
  create_dimension_tracker,
26
25
  create_hover_lock,
27
- } from './hover-lock.svelte'
26
+ } from '../core/hover-lock.svelte'
27
+ import { create_legend_visibility } from '../core/utils/series-visibility'
28
28
  import {
29
29
  get_relative_coords,
30
- pan_range,
30
+ MIN_TOUCH_DISTANCE_PIXELS,
31
+ pan_range_by_pixels,
31
32
  PINCH_ZOOM_THRESHOLD,
32
- pixels_to_data_delta,
33
- } from './interactions'
33
+ remove_drag_listeners,
34
+ resolve_axis_ranges,
35
+ sorted_range,
36
+ vec2_equal,
37
+ zoom_range_by_factor,
38
+ } from '../core/interactions'
34
39
  import {
35
40
  calc_auto_padding,
36
- constrain_tooltip_position,
37
41
  filter_padding,
38
42
  LABEL_GAP_DEFAULT,
43
+ y2_axis_label_x,
39
44
  measure_max_tick_width,
40
- } from './layout'
45
+ } from '../core/layout'
41
46
  import {
42
47
  build_obstacles_norm,
43
48
  clip_bar,
44
49
  has_explicit_position,
45
50
  measured_footprint,
46
51
  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'
52
+ } from '../core/auto-place'
53
+ import type { IndexedRefLine } from '../core/reference-line'
54
+ import { group_ref_lines_by_z, index_ref_lines } from '../core/reference-line'
50
55
  import {
51
56
  create_scale,
52
57
  generate_ticks,
53
58
  get_nice_data_range,
54
59
  get_tick_label,
55
- } from './scales'
60
+ } from '../core/scales'
56
61
  import type {
57
62
  BasePlotProps,
58
63
  DataSeries,
@@ -60,19 +65,20 @@
60
65
  LegendConfig,
61
66
  PlotConfig,
62
67
  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'
68
+ } from '../core/types'
69
+ import { get_scale_type_name } from '../core/types'
70
+ import ZeroLines from '../core/components/ZeroLines.svelte'
71
+ import ZoomRect from '../core/components/ZoomRect.svelte'
72
+ import { DEFAULTS } from '../../settings'
68
73
  import { bin, max } from 'd3-array'
69
74
  import type { Snippet } from 'svelte'
70
- import { untrack } from 'svelte'
75
+ import { onDestroy, untrack } from 'svelte'
71
76
  import type { HTMLAttributes } from 'svelte/elements'
72
77
  import { Tween } from 'svelte/motion'
73
- import type { Vec2 } from '../math'
74
- import PlotTooltip from './PlotTooltip.svelte'
75
- import { bar_path } from './svg'
78
+ import type { Vec2 } from '../../math'
79
+ import PlotTooltip from '../core/components/PlotTooltip.svelte'
80
+ import { bar_path } from '../core/svg'
81
+ import { unique_id } from '../core/utils'
76
82
 
77
83
  let {
78
84
  series = $bindable([]),
@@ -81,10 +87,6 @@
81
87
  y_axis: y_axis_init = {},
82
88
  y2_axis: y2_axis_init = {},
83
89
  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
90
  range_padding = 0.05,
89
91
  padding = { t: 20, b: 60, l: 60, r: 20 },
90
92
  bins = $bindable(100),
@@ -173,10 +175,10 @@
173
175
  ...x2_axis_init,
174
176
  })))
175
177
  let y_axis = $state(untrack(() => ({ ...axis_state_defaults, ...y_axis_init })))
176
- // y2-axis needs different default label_shift for right-side positioning
178
+ // y2 title stays vertically centered; its x position is computed by y2_axis_label_x
177
179
  let y2_axis = $state(untrack(() => ({
178
180
  ...axis_state_defaults,
179
- label_shift: { x: 0, y: 60 },
181
+ label_shift: { x: 0, y: 0 },
180
182
  ...y2_axis_init,
181
183
  })))
182
184
  let display = $state(
@@ -194,7 +196,7 @@
194
196
  let [width, height] = $state([0, 0])
195
197
  let wrapper: HTMLDivElement | undefined = $state()
196
198
  let svg_element: SVGElement | null = $state(null)
197
- let clip_path_id = `histogram-clip-${crypto?.randomUUID?.()}`
199
+ const clip_path_id = unique_id(`histogram-clip`) // stable, collision-resistant (see unique_id)
198
200
  let hover_info = $state<HistogramHandlerProps | null>(null)
199
201
 
200
202
  // Reference line hover state
@@ -206,7 +208,6 @@
206
208
  // Compute ref_lines with index and group by z-index (using shared utilities)
207
209
  let indexed_ref_lines = $derived(index_ref_lines(ref_lines))
208
210
  let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines))
209
- let tooltip_el = $state<HTMLDivElement | undefined>()
210
211
  let drag_state = $state<{
211
212
  start: { x: number; y: number } | null
212
213
  current: { x: number; y: number } | null
@@ -270,11 +271,12 @@
270
271
  )
271
272
 
272
273
  let auto_ranges = $derived.by(() => {
273
- const all_values = selected_series.flatMap((srs: DataSeries) => srs.y)
274
+ // Only x1 series contribute to the x1 auto-range (x2 series get their own domain below)
275
+ const x1_values = selected_series.flatMap((srs) => srs.x_axis === `x2` ? [] : srs.y)
274
276
  const auto_x = get_nice_data_range(
275
- all_values.map((val) => ({ x: val, y: 0 })),
277
+ x1_values.map((val) => ({ x: val, y: 0 })),
276
278
  ({ x }) => x,
277
- x_range,
279
+ final_x_axis.range ?? [null, null],
278
280
  final_x_axis.scale_type ?? `linear`,
279
281
  range_padding,
280
282
  false,
@@ -285,7 +287,7 @@
285
287
  ? get_nice_data_range(
286
288
  x2_values.map((val) => ({ x: val, y: 0 })),
287
289
  ({ x }) => x,
288
- x2_range,
290
+ final_x2_axis.range ?? [null, null],
289
291
  final_x2_axis.scale_type ?? `linear`,
290
292
  range_padding,
291
293
  false,
@@ -295,20 +297,22 @@
295
297
  // Calculate y-range for a specific set of series
296
298
  const calc_y_range = (
297
299
  series_list: typeof selected_series,
298
- y_limit: typeof y_range,
300
+ y_limit: [number | null, number | null],
299
301
  scale_type: ScaleType,
300
302
  ): Vec2 => {
301
303
  const type_name = get_scale_type_name(scale_type)
302
- if (!series_list.length) {
304
+ if (series_list.length === 0) {
303
305
  const fallback = type_name === `log` ? 1 : 0
304
306
  return [fallback, 1]
305
307
  }
306
- const hist = bin().domain([auto_x[0], auto_x[1]]).thresholds(bins)
308
+ // Bin each series over the domain of the x-axis it renders on (d3 bin() drops
309
+ // out-of-domain values, so binning x2 series over the x1 domain skews max_count)
307
310
  const max_count = Math.max(
308
311
  0,
309
- ...series_list.map((srs: DataSeries) =>
310
- max(hist(srs.y), (data) => data.length) || 0
311
- ),
312
+ ...series_list.map((srs: DataSeries) => {
313
+ const hist = bin().domain(srs.x_axis === `x2` ? auto_x2 : auto_x).thresholds(bins)
314
+ return max(hist(srs.y), (data) => data.length) || 0
315
+ }),
312
316
  )
313
317
 
314
318
  // If there's effectively no data, avoid log-range issues (counts can't be <= 0 on log)
@@ -333,12 +337,12 @@
333
337
 
334
338
  const y1_range = calc_y_range(
335
339
  y1_series,
336
- y_range,
340
+ final_y_axis.range ?? [null, null],
337
341
  final_y_axis.scale_type ?? `linear`,
338
342
  )
339
343
  const y2_auto_range = calc_y_range(
340
344
  y2_series,
341
- y2_range,
345
+ final_y2_axis.range ?? [null, null],
342
346
  final_y2_axis.scale_type ?? `linear`,
343
347
  )
344
348
 
@@ -362,47 +366,21 @@
362
366
  })
363
367
 
364
368
  $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]
369
+ // Supports one-sided range pinning (null bounds fall back to auto); returns null
370
+ // for transient non-finite bounds (skip: writing NaN breaks scales and loops here)
371
+ const next = resolve_axis_ranges(
372
+ { x: final_x_axis, x2: final_x2_axis, y: final_y_axis, y2: final_y2_axis },
373
+ auto_ranges,
374
+ )
375
+ if (!next) return
376
+ // Update only changed axes (preserving each unchanged axis's panned current view).
377
+ // untrack the reads of `ranges` so the writes below can't re-trigger this effect
378
+ // (reading + writing the same state otherwise causes effect_update_depth_exceeded).
379
+ const init = untrack(() => ranges.initial)
380
+ if (!vec2_equal(init.x, next.x)) [ranges.initial.x, ranges.current.x] = [next.x, next.x]
381
+ if (!vec2_equal(init.x2, next.x2)) [ranges.initial.x2, ranges.current.x2] = [next.x2, next.x2]
382
+ if (!vec2_equal(init.y, next.y)) [ranges.initial.y, ranges.current.y] = [next.y, next.y]
383
+ if (!vec2_equal(init.y2, next.y2)) [ranges.initial.y2, ranges.current.y2] = [next.y2, next.y2]
406
384
  })
407
385
 
408
386
  // Layout: dynamic padding based on tick label widths
@@ -416,7 +394,7 @@
416
394
  const current_ticks_y = untrack(() => ticks.y)
417
395
  const current_ticks_y2 = untrack(() => ticks.y2)
418
396
 
419
- const new_pad = width && height && current_ticks_y.length
397
+ const new_pad = width && height && current_ticks_y.length > 0
420
398
  ? calc_auto_padding({
421
399
  padding,
422
400
  default_padding,
@@ -428,7 +406,7 @@
428
406
 
429
407
  // Add y2 axis label space (calc_auto_padding only accounts for tick labels)
430
408
  if (
431
- width && height && y2_series.length && current_ticks_y2.length &&
409
+ width && height && y2_series.length > 0 && current_ticks_y2.length > 0 &&
432
410
  final_y2_axis.label
433
411
  ) {
434
412
  const inside = final_y2_axis.tick?.label?.inside ?? false
@@ -444,7 +422,7 @@
444
422
 
445
423
  // Add x2 axis label space (mirroring y2 logic for top padding)
446
424
  if (
447
- width && height && x2_series.length && current_ticks_x2.length &&
425
+ width && height && x2_series.length > 0 && current_ticks_x2.length > 0 &&
448
426
  final_x2_axis.label
449
427
  ) {
450
428
  const inside = final_x2_axis.tick?.label?.inside ?? false
@@ -472,7 +450,7 @@
472
450
  // vertical segment (top -> baseline) so the legend can't hide inside a tall bar. Built from
473
451
  // histogram_bins (pad-independent) + ranges so the crowding decision can't see its own reservation.
474
452
  const obstacles_norm = $derived.by(() => {
475
- if (!width || !height || !histogram_bins.length) return []
453
+ if (!width || !height || histogram_bins.length === 0) return []
476
454
  const base_w = width - base_pad.l - base_pad.r
477
455
  const base_h = height - base_pad.t - base_pad.b
478
456
  if (base_w <= 0 || base_h <= 0) return []
@@ -540,7 +518,7 @@
540
518
 
541
519
  // Pad-independent binning (no pixel scales) so the auto-place obstacle field can reuse it
542
520
  let histogram_bins = $derived.by(() => {
543
- if (!selected_series.length || !width || !height) return []
521
+ if (selected_series.length === 0 || !width || !height) return []
544
522
  const hist_generator = bin()
545
523
  .domain([ranges.current.x[0], ranges.current.x[1]])
546
524
  .thresholds(bins)
@@ -625,7 +603,7 @@
625
603
 
626
604
  // Collect histogram bar positions for legend placement
627
605
  let hist_points_for_placement = $derived.by(() => {
628
- if (!width || !height || !histogram_data.length) return []
606
+ if (!width || !height || histogram_data.length === 0) return []
629
607
 
630
608
  const points: { x: number; y: number }[] = []
631
609
 
@@ -716,23 +694,15 @@
716
694
  const dy = Math.abs(drag_state.start.y - drag_state.current.y)
717
695
  if (dx > 5 && dy > 5) {
718
696
  // 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
- }
697
+ x_axis = { ...x_axis, range: sorted_range(start_x, end_x) }
723
698
  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)],
699
+ x2_axis = { ...x2_axis, range: sorted_range(start_x2, end_x2) }
732
700
  }
733
- y2_axis = {
734
- ...y2_axis,
735
- range: [Math.min(start_y2, end_y2), Math.max(start_y2, end_y2)],
701
+ y_axis = { ...y_axis, range: sorted_range(start_y, end_y) }
702
+ // gate on y2 series presence (like x2): the y2 scale is a [0, 1] sentinel
703
+ // otherwise, so inverting would store a phantom range in the bindable prop
704
+ if (y2_series.length > 0) {
705
+ y2_axis = { ...y2_axis, range: sorted_range(start_y2, end_y2) }
736
706
  }
737
707
  }
738
708
  }
@@ -754,42 +724,33 @@
754
724
  document.body.style.cursor = `default`
755
725
  }
756
726
 
757
- // Pan drag handlers
727
+ // Pan/zoom all four axes from an interaction-start snapshot, each in its own
728
+ // scale's transform space (log axes pan by a constant factor, linear by a shift).
729
+ // Plot dims clamped to 1px so degenerate containers can't produce Infinity deltas.
730
+ const pan_all_axes = (init: InitialRanges, dx_px: number, dy_px: number) => {
731
+ const plot_width = Math.max(1, width - pad.l - pad.r)
732
+ const plot_height = Math.max(1, height - pad.t - pad.b)
733
+ ranges.current.x = pan_range_by_pixels(init.initial_x_range, dx_px, plot_width, final_x_axis.scale_type)
734
+ ranges.current.x2 = pan_range_by_pixels(init.initial_x2_range, dx_px, plot_width, final_x2_axis.scale_type)
735
+ ranges.current.y = pan_range_by_pixels(init.initial_y_range, dy_px, plot_height, final_y_axis.scale_type)
736
+ ranges.current.y2 = pan_range_by_pixels(init.initial_y2_range, dy_px, plot_height, final_y2_axis.scale_type)
737
+ }
738
+ const zoom_all_axes = (init: InitialRanges, factor: number) => {
739
+ ranges.current.x = zoom_range_by_factor(init.initial_x_range, factor, final_x_axis.scale_type)
740
+ ranges.current.x2 = zoom_range_by_factor(init.initial_x2_range, factor, final_x2_axis.scale_type)
741
+ ranges.current.y = zoom_range_by_factor(init.initial_y_range, factor, final_y_axis.scale_type)
742
+ ranges.current.y2 = zoom_range_by_factor(init.initial_y2_range, factor, final_y2_axis.scale_type)
743
+ }
744
+
745
+ // Pan drag handler (drag direction inverted on x for natural pan feel)
758
746
  const on_pan_move = (evt: MouseEvent) => {
759
747
  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
748
  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,
749
+ pan_all_axes(
750
+ pan_drag_state,
751
+ -(evt.clientX - pan_drag_state.start.x) * sensitivity,
752
+ (evt.clientY - pan_drag_state.start.y) * sensitivity,
777
753
  )
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
754
  }
794
755
 
795
756
  const on_pan_end = () => {
@@ -799,6 +760,15 @@
799
760
  window.removeEventListener(`mouseup`, on_pan_end)
800
761
  }
801
762
 
763
+ // Tear down any window listeners + cursor override if the component unmounts mid-drag
764
+ // (mouseup/panend would otherwise never fire, leaking listeners and a stuck cursor).
765
+ // onDestroy also runs during SSR teardown, where window/document don't exist.
766
+ onDestroy(() => {
767
+ remove_drag_listeners([on_window_mouse_move, on_pan_move], [on_window_mouse_up, on_pan_end])
768
+ drag_state = { start: null, current: null, bounds: null }
769
+ pan_drag_state = null
770
+ })
771
+
802
772
  function handle_mouse_down(evt: MouseEvent) {
803
773
  const coords = get_relative_coords(evt)
804
774
  if (!coords || !svg_element) return
@@ -844,34 +814,15 @@
844
814
  const plot_height = Math.max(1, height - pad.t - pad.b)
845
815
  const sensitivity = pan?.wheel_sensitivity ?? 1
846
816
 
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
-
817
+ // Pan along the dominant wheel direction
869
818
  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)
819
+ const dx = evt.deltaX * sensitivity
820
+ ranges.current.x = pan_range_by_pixels(ranges.current.x, dx, plot_width, final_x_axis.scale_type)
821
+ ranges.current.x2 = pan_range_by_pixels(ranges.current.x2, dx, plot_width, final_x2_axis.scale_type)
872
822
  } else {
873
- ranges.current.y = pan_range(ranges.current.y, y_delta)
874
- ranges.current.y2 = pan_range(ranges.current.y2, y2_delta)
823
+ const dy = evt.deltaY * sensitivity
824
+ ranges.current.y = pan_range_by_pixels(ranges.current.y, dy, plot_height, final_y_axis.scale_type)
825
+ ranges.current.y2 = pan_range_by_pixels(ranges.current.y2, dy, plot_height, final_y2_axis.scale_type)
875
826
  }
876
827
  }
877
828
 
@@ -909,78 +860,15 @@
909
860
 
910
861
  // Calculate pinch scale (curr/start so spread = zoom out, pinch = zoom in)
911
862
  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
863
+ // ignore near-coincident touches so curr_dist / start_dist can't blow up the scale
864
+ if (start_dist < MIN_TOUCH_DISTANCE_PIXELS) return
914
865
  const curr_dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY)
915
866
  const scale = curr_dist / start_dist
916
867
 
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
868
+ // Pinch zoom about the view center (spread = zoom in, pinch = zoom out)
923
869
  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
- }
870
+ zoom_all_axes(touch_state, scale)
871
+ } else pan_all_axes(touch_state, -dx, dy)
984
872
  }
985
873
 
986
874
  function handle_touch_end() {
@@ -1030,16 +918,7 @@
1030
918
  on_bar_hover?.({ value, count, property, event: evt })
1031
919
  }
1032
920
 
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
- }
921
+ const legend_vis = create_legend_visibility(() => series, (next) => (series = next))
1043
922
 
1044
923
  // Set theme-aware background when entering fullscreen
1045
924
  $effect(() => {
@@ -1067,32 +946,12 @@
1067
946
  set_loading: (axis) => (axis_loading = axis),
1068
947
  }
1069
948
 
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(
949
+ // Shared handler + one-shot auto-load bound to this component's state
950
+ const { handle_axis_change, try_auto_load } = create_axis_loader(
1073
951
  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
- })
952
+ () => ({ data_loader, on_axis_change, on_error }),
953
+ )
954
+ $effect(try_auto_load)
1096
955
  </script>
1097
956
 
1098
957
  {#snippet ref_lines_layer(lines: IndexedRefLine[])}
@@ -1175,6 +1034,7 @@
1175
1034
  ontouchstart={handle_touch_start}
1176
1035
  ontouchmove={handle_touch_move}
1177
1036
  ontouchend={handle_touch_end}
1037
+ ontouchcancel={handle_touch_end}
1178
1038
  style:cursor={pan_drag_state
1179
1039
  ? `grabbing`
1180
1040
  : shift_held && pan?.enabled !== false
@@ -1240,6 +1100,7 @@
1240
1100
  ticks={ticks.x as number[]}
1241
1101
  place={scales.x}
1242
1102
  axis={final_x_axis}
1103
+ domain={ranges.current.x as Vec2}
1243
1104
  {pad}
1244
1105
  {width}
1245
1106
  {height}
@@ -1258,6 +1119,7 @@
1258
1119
  ticks={ticks.x2 as number[]}
1259
1120
  place={scales.x2}
1260
1121
  axis={final_x2_axis}
1122
+ domain={ranges.current.x2 as Vec2}
1261
1123
  {pad}
1262
1124
  {width}
1263
1125
  {height}
@@ -1276,6 +1138,7 @@
1276
1138
  ticks={ticks.y as number[]}
1277
1139
  place={scales.y}
1278
1140
  axis={final_y_axis}
1141
+ domain={ranges.current.y as Vec2}
1279
1142
  {pad}
1280
1143
  {width}
1281
1144
  {height}
@@ -1293,21 +1156,18 @@
1293
1156
 
1294
1157
  <!-- Y2-axis (Right) -->
1295
1158
  {#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
1159
  <PlotAxis
1300
1160
  side="y2"
1301
1161
  ticks={ticks.y2 as number[]}
1302
1162
  place={scales.y2}
1303
1163
  axis={final_y2_axis}
1164
+ domain={ranges.current.y2 as Vec2}
1304
1165
  {pad}
1305
1166
  {width}
1306
1167
  {height}
1307
1168
  show_grid={display.y2_grid}
1308
1169
  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)}
1170
+ label_x={y2_axis_label_x(final_y2_axis, width, pad.r, tick_label_widths.y2_max)}
1311
1171
  label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y2_axis.label_shift?.y ?? 0)}
1312
1172
  axis_loading={axis_loading === `y2`}
1313
1173
  on_axis_change={(key) => handle_axis_change(`y2`, key)}
@@ -1323,6 +1183,7 @@
1323
1183
  <g
1324
1184
  class="histogram-series"
1325
1185
  data-series-idx={series_idx}
1186
+ clip-path="url(#{clip_path_id})"
1326
1187
  opacity={hovered_legend_series_idx !== null &&
1327
1188
  hovered_legend_series_idx !== series_idx
1328
1189
  ? 0.25
@@ -1389,20 +1250,12 @@
1389
1250
  {@const { value, count, property, active_y_axis, active_x_axis } = hover_info}
1390
1251
  {@const tooltip_x = (active_x_axis === `x2` ? scales.x2 : scales.x)(value)}
1391
1252
  {@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
1253
  <PlotTooltip
1402
- x={tooltip_pos.x}
1403
- y={tooltip_pos.y}
1404
- offset={{ x: 0, y: 0 }}
1405
- bind:wrapper={tooltip_el}
1254
+ x={tooltip_x}
1255
+ y={tooltip_y}
1256
+ offset={{ x: 5, y: -10 }}
1257
+ constrain_to={{ width, height }}
1258
+ fallback_size={{ width: 120, height: mode === `overlay` ? 60 : 40 }}
1406
1259
  >
1407
1260
  {#if tooltip}
1408
1261
  {@render tooltip({ ...hover_info, fullscreen })}
@@ -1461,11 +1314,16 @@
1461
1314
  bind:root_element={legend_element}
1462
1315
  {...legend}
1463
1316
  series_data={legend_data}
1464
- on_toggle={legend?.on_toggle || toggle_series_visibility}
1317
+ on_toggle={legend?.on_toggle ?? ((series_idx: number) => {
1318
+ if (series_idx < 0 || series_idx >= series.length) return
1319
+ legend_vis.on_toggle(series_idx)
1320
+ on_series_toggle(series_idx)
1321
+ })}
1322
+ on_double_click={legend?.on_double_click ?? legend_vis.on_double_click}
1465
1323
  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
1324
+ on_item_hover={(item) =>
1325
+ (hovered_legend_series_idx = item != null && item.series_idx >= 0
1326
+ ? item.series_idx
1469
1327
  : null)}
1470
1328
  active_series_idx={hover_info?.series_idx ?? hovered_legend_series_idx}
1471
1329
  style={`
@@ -1509,8 +1367,10 @@
1509
1367
  background: var(--histogram-fullscreen-bg, var(--histogram-bg, var(--plot-bg)));
1510
1368
  max-height: none !important;
1511
1369
  overflow: hidden;
1512
- /* Add padding to prevent titles from being cropped at top */
1513
- padding-top: var(--plot-fullscreen-padding-top, 2em);
1370
+ /* border-top (not padding-top): bind:clientHeight includes padding but excludes
1371
+ borders - padding made the chart overflow + clip its bottom 2em (x-axis title) */
1372
+ border-top: var(--plot-fullscreen-padding-top, 2em) solid
1373
+ var(--histogram-fullscreen-bg, var(--histogram-bg, var(--plot-bg, transparent)));
1514
1374
  box-sizing: border-box;
1515
1375
  }
1516
1376
  .header-controls {