matterviz 0.4.1 → 0.4.3

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 (499) hide show
  1. package/dist/EmptyState.svelte +10 -4
  2. package/dist/FilePicker.svelte +48 -60
  3. package/dist/Icon.svelte +7 -4
  4. package/dist/MillerIndexInput.svelte +2 -2
  5. package/dist/api/optimade.js +10 -7
  6. package/dist/app.css +68 -0
  7. package/dist/brillouin/BrillouinZone.svelte +90 -91
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
  9. package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
  11. package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
  12. package/dist/brillouin/BrillouinZoneScene.svelte +46 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  14. package/dist/brillouin/compute.js +10 -16
  15. package/dist/brillouin/geometry.js +2 -2
  16. package/dist/chempot-diagram/ChemPotDiagram.svelte +48 -50
  17. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +61 -169
  18. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
  19. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +559 -755
  20. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
  21. package/dist/chempot-diagram/async-compute.svelte.js +13 -14
  22. package/dist/chempot-diagram/color.d.ts +21 -1
  23. package/dist/chempot-diagram/color.js +70 -1
  24. package/dist/chempot-diagram/compute.d.ts +7 -0
  25. package/dist/chempot-diagram/compute.js +85 -26
  26. package/dist/chempot-diagram/controls-state.svelte.js +1 -1
  27. package/dist/chempot-diagram/export.js +1 -6
  28. package/dist/chempot-diagram/temperature.js +9 -10
  29. package/dist/colors/index.d.ts +0 -4
  30. package/dist/colors/index.js +19 -20
  31. package/dist/composition/BarChart.svelte +30 -26
  32. package/dist/composition/BubbleChart.svelte +17 -17
  33. package/dist/composition/Composition.svelte +10 -19
  34. package/dist/composition/Formula.svelte +20 -27
  35. package/dist/composition/FormulaFilter.svelte +115 -184
  36. package/dist/composition/PieChart.svelte +55 -41
  37. package/dist/composition/format.js +7 -9
  38. package/dist/composition/index.d.ts +2 -0
  39. package/dist/composition/index.js +14 -0
  40. package/dist/composition/parse.d.ts +2 -2
  41. package/dist/composition/parse.js +61 -115
  42. package/dist/constants.d.ts +0 -1
  43. package/dist/constants.js +17 -26
  44. package/dist/convex-hull/ConvexHull.svelte +12 -15
  45. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHull2D.svelte +93 -94
  47. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  48. package/dist/convex-hull/ConvexHull3D.svelte +127 -186
  49. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  50. package/dist/convex-hull/ConvexHull4D.svelte +131 -180
  51. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  52. package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
  53. package/dist/convex-hull/ConvexHullControls.svelte +46 -48
  54. package/dist/convex-hull/ConvexHullStats.svelte +193 -195
  55. package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
  56. package/dist/convex-hull/GasPressureControls.svelte +5 -12
  57. package/dist/convex-hull/StructurePopup.svelte +18 -22
  58. package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
  59. package/dist/convex-hull/TemperatureSlider.svelte +4 -10
  60. package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
  61. package/dist/convex-hull/gas-thermodynamics.js +21 -53
  62. package/dist/convex-hull/helpers.d.ts +8 -10
  63. package/dist/convex-hull/helpers.js +37 -43
  64. package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
  65. package/dist/convex-hull/thermodynamics.js +37 -44
  66. package/dist/coordination/CoordinationBarPlot.svelte +49 -53
  67. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  68. package/dist/coordination/calc-coordination.d.ts +1 -1
  69. package/dist/coordination/calc-coordination.js +28 -25
  70. package/dist/element/BohrAtom.svelte +5 -9
  71. package/dist/element/ElementHeading.svelte +4 -1
  72. package/dist/element/ElementPhoto.svelte +11 -9
  73. package/dist/element/ElementStats.svelte +12 -10
  74. package/dist/element/ElementTile.svelte +44 -67
  75. package/dist/element/Nucleus.svelte +4 -2
  76. package/dist/element/data.d.ts +3 -2
  77. package/dist/element/data.js +5 -1
  78. package/dist/element/helpers.d.ts +4 -0
  79. package/dist/element/helpers.js +18 -0
  80. package/dist/element/index.d.ts +2 -5
  81. package/dist/element/index.js +4 -19
  82. package/dist/feedback/DragOverlay.svelte +5 -5
  83. package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
  84. package/dist/feedback/StatusMessage.svelte +7 -4
  85. package/dist/fermi-surface/FermiSlice.svelte +21 -18
  86. package/dist/fermi-surface/FermiSurface.svelte +35 -44
  87. package/dist/fermi-surface/FermiSurface.svelte.d.ts +7 -13
  88. package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
  89. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  90. package/dist/fermi-surface/FermiSurfaceScene.svelte +64 -136
  91. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  92. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
  93. package/dist/fermi-surface/compute.js +25 -58
  94. package/dist/fermi-surface/parse.js +54 -88
  95. package/dist/file-viewer/JsonBrowser.svelte +1043 -0
  96. package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
  97. package/dist/file-viewer/PlotPanel.svelte +350 -0
  98. package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
  99. package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
  100. package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
  101. package/dist/file-viewer/detect.d.ts +11 -0
  102. package/dist/file-viewer/detect.js +381 -0
  103. package/dist/file-viewer/eligibility.d.ts +4 -0
  104. package/dist/file-viewer/eligibility.js +53 -0
  105. package/dist/file-viewer/host-protocol.d.ts +50 -0
  106. package/dist/file-viewer/host-transfer.d.ts +25 -0
  107. package/dist/file-viewer/host-transfer.js +42 -0
  108. package/dist/file-viewer/index.d.ts +5 -0
  109. package/dist/file-viewer/index.js +5 -0
  110. package/dist/file-viewer/main.d.ts +44 -0
  111. package/dist/file-viewer/main.js +535 -0
  112. package/dist/file-viewer/parse.d.ts +9 -0
  113. package/dist/file-viewer/parse.js +170 -0
  114. package/dist/file-viewer/plot-utils.d.ts +24 -0
  115. package/dist/file-viewer/plot-utils.js +191 -0
  116. package/dist/file-viewer/types.d.ts +4 -0
  117. package/dist/file-viewer/types.js +5 -0
  118. package/dist/heatmap-matrix/HeatmapMatrix.svelte +153 -219
  119. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
  120. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
  121. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  122. package/dist/heatmap-matrix/index.d.ts +5 -0
  123. package/dist/icons.d.ts +4 -0
  124. package/dist/icons.js +4 -0
  125. package/dist/index.d.ts +2 -2
  126. package/dist/index.js +1 -1
  127. package/dist/io/ExportPane.svelte +11 -7
  128. package/dist/io/decompress.d.ts +4 -1
  129. package/dist/io/decompress.js +24 -36
  130. package/dist/io/export.d.ts +11 -4
  131. package/dist/io/export.js +75 -19
  132. package/dist/io/fetch.d.ts +0 -3
  133. package/dist/io/fetch.js +0 -11
  134. package/dist/io/file-drop.js +60 -35
  135. package/dist/io/index.d.ts +0 -1
  136. package/dist/io/index.js +2 -9
  137. package/dist/io/is-binary.d.ts +9 -0
  138. package/dist/io/is-binary.js +45 -1
  139. package/dist/io/url-drop.d.ts +2 -0
  140. package/dist/io/url-drop.js +40 -40
  141. package/dist/isosurface/Isosurface.svelte +700 -215
  142. package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
  143. package/dist/isosurface/IsosurfaceControls.svelte +590 -116
  144. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
  145. package/dist/isosurface/VolumeSlice.svelte +224 -0
  146. package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
  147. package/dist/isosurface/coloring.d.ts +20 -0
  148. package/dist/isosurface/coloring.js +118 -0
  149. package/dist/isosurface/geometry-worker-types.d.ts +38 -0
  150. package/dist/isosurface/geometry.worker.d.ts +1 -0
  151. package/dist/isosurface/geometry.worker.js +59 -0
  152. package/dist/isosurface/grid.d.ts +10 -0
  153. package/dist/isosurface/grid.js +31 -0
  154. package/dist/isosurface/index.d.ts +4 -0
  155. package/dist/isosurface/index.js +4 -0
  156. package/dist/isosurface/parse-vaspwave.d.ts +3 -0
  157. package/dist/isosurface/parse-vaspwave.js +138 -0
  158. package/dist/isosurface/parse.js +6 -6
  159. package/dist/isosurface/profile.d.ts +12 -0
  160. package/dist/isosurface/profile.js +19 -0
  161. package/dist/isosurface/sampling.d.ts +34 -0
  162. package/dist/isosurface/sampling.js +408 -0
  163. package/dist/isosurface/slice-rendering.d.ts +13 -0
  164. package/dist/isosurface/slice-rendering.js +79 -0
  165. package/dist/isosurface/slice.d.ts +22 -2
  166. package/dist/isosurface/slice.js +200 -116
  167. package/dist/isosurface/types.d.ts +24 -1
  168. package/dist/isosurface/types.js +120 -8
  169. package/dist/json-path.d.ts +4 -0
  170. package/dist/json-path.js +89 -0
  171. package/dist/labels.d.ts +6 -2
  172. package/dist/labels.js +44 -14
  173. package/dist/layout/FullscreenButton.svelte +2 -1
  174. package/dist/layout/FullscreenToggle.svelte +10 -4
  175. package/dist/layout/InfoCard.svelte +8 -21
  176. package/dist/layout/InfoTag.svelte +1 -3
  177. package/dist/layout/NumberRangeInput.svelte +46 -0
  178. package/dist/layout/NumberRangeInput.svelte.d.ts +12 -0
  179. package/dist/layout/PropertyFilter.svelte +2 -6
  180. package/dist/layout/SettingsSection.svelte +57 -62
  181. package/dist/layout/SettingsSection.svelte.d.ts +6 -7
  182. package/dist/layout/SubpageGrid.svelte +3 -1
  183. package/dist/layout/ViewerChrome.svelte +16 -11
  184. package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
  185. package/dist/layout/fullscreen.js +3 -5
  186. package/dist/layout/fullscreen.svelte.js +1 -2
  187. package/dist/layout/index.d.ts +1 -0
  188. package/dist/layout/index.js +1 -0
  189. package/dist/layout/json-tree/JsonNode.svelte +22 -59
  190. package/dist/layout/json-tree/JsonTree.svelte +101 -172
  191. package/dist/layout/json-tree/JsonValue.svelte +9 -32
  192. package/dist/layout/json-tree/utils.d.ts +0 -3
  193. package/dist/layout/json-tree/utils.js +50 -188
  194. package/dist/marching-cubes.d.ts +8 -1
  195. package/dist/marching-cubes.js +115 -48
  196. package/dist/math.d.ts +5 -2
  197. package/dist/math.js +48 -38
  198. package/dist/overlays/ContextMenu.svelte +19 -34
  199. package/dist/overlays/CopyButton.svelte +11 -5
  200. package/dist/overlays/DragControlTab.svelte +6 -1
  201. package/dist/overlays/DraggablePane.svelte +110 -74
  202. package/dist/overlays/DraggablePane.svelte.d.ts +2 -1
  203. package/dist/overlays/GlassChip.svelte +32 -0
  204. package/dist/overlays/GlassChip.svelte.d.ts +8 -0
  205. package/dist/overlays/InfoPaneCards.svelte +11 -23
  206. package/dist/overlays/index.d.ts +7 -0
  207. package/dist/overlays/index.js +26 -0
  208. package/dist/overlays/portal.d.ts +2 -0
  209. package/dist/overlays/portal.js +14 -0
  210. package/dist/periodic-table/PeriodicTable.svelte +113 -145
  211. package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
  212. package/dist/periodic-table/PeriodicTableControls.svelte +37 -115
  213. package/dist/periodic-table/TableInset.svelte +9 -4
  214. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +68 -105
  215. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
  216. package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
  217. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  218. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  219. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
  220. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
  221. package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
  222. package/dist/phase-diagram/build-diagram.js +4 -9
  223. package/dist/phase-diagram/diagram-input.d.ts +1 -2
  224. package/dist/phase-diagram/parse.js +15 -15
  225. package/dist/phase-diagram/svg-to-diagram.js +28 -27
  226. package/dist/phase-diagram/utils.js +2 -3
  227. package/dist/plot/bar/BarPlot.svelte +461 -426
  228. package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
  229. package/dist/plot/bar/BarPlotControls.svelte +1 -3
  230. package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
  231. package/dist/plot/bar/SpacegroupBarPlot.svelte +46 -77
  232. package/dist/plot/bar/data.d.ts +5 -4
  233. package/dist/plot/bar/data.js +2 -1
  234. package/dist/plot/box/BoxPlot.svelte +322 -226
  235. package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
  236. package/dist/plot/box/Violin.svelte.d.ts +1 -0
  237. package/dist/plot/box/box-plot.js +1 -1
  238. package/dist/plot/core/auto-place.d.ts +1 -1
  239. package/dist/plot/core/auto-place.js +2 -2
  240. package/dist/plot/core/axis-utils.d.ts +12 -7
  241. package/dist/plot/core/axis-utils.js +48 -48
  242. package/dist/plot/core/components/AxisLabel.svelte +82 -20
  243. package/dist/plot/core/components/AxisLabel.svelte.d.ts +1 -0
  244. package/dist/plot/core/components/ColorBar.svelte +101 -106
  245. package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
  246. package/dist/plot/core/components/FillArea.svelte +10 -17
  247. package/dist/plot/core/components/HierarchyControls.svelte +249 -0
  248. package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
  249. package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
  250. package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
  251. package/dist/plot/core/components/Line.svelte +18 -9
  252. package/dist/plot/core/components/Line.svelte.d.ts +2 -0
  253. package/dist/plot/core/components/PlotAxis.svelte +40 -17
  254. package/dist/plot/core/components/PlotControls.svelte +65 -41
  255. package/dist/plot/core/components/PlotLegend.svelte +31 -35
  256. package/dist/plot/core/components/PlotMarginals.svelte +616 -0
  257. package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
  258. package/dist/plot/core/components/PlotTooltip.svelte +1 -1
  259. package/dist/plot/core/components/PortalSelect.svelte +4 -8
  260. package/dist/plot/core/components/ReferenceLine.svelte +27 -24
  261. package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
  262. package/dist/plot/core/components/ReferencePlane.svelte +7 -8
  263. package/dist/plot/core/components/ZeroLines.svelte +4 -8
  264. package/dist/plot/core/components/ZoomRect.svelte +1 -2
  265. package/dist/plot/core/components/index.d.ts +1 -0
  266. package/dist/plot/core/components/index.js +1 -0
  267. package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
  268. package/dist/plot/core/data-cleaning-signal.js +467 -0
  269. package/dist/plot/core/data-cleaning.d.ts +2 -42
  270. package/dist/plot/core/data-cleaning.js +27 -474
  271. package/dist/plot/core/data-transform.d.ts +1 -2
  272. package/dist/plot/core/data-transform.js +9 -15
  273. package/dist/plot/core/fill-utils.d.ts +3 -1
  274. package/dist/plot/core/fill-utils.js +28 -6
  275. package/dist/plot/core/index.d.ts +1 -0
  276. package/dist/plot/core/index.js +1 -0
  277. package/dist/plot/core/interactions.js +0 -2
  278. package/dist/plot/core/layout.d.ts +15 -6
  279. package/dist/plot/core/layout.js +57 -38
  280. package/dist/plot/core/marginals.d.ts +170 -0
  281. package/dist/plot/core/marginals.js +436 -0
  282. package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
  283. package/dist/plot/core/pan-zoom.svelte.js +34 -17
  284. package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
  285. package/dist/plot/core/placed-tween.svelte.js +68 -21
  286. package/dist/plot/core/reference-line.js +1 -1
  287. package/dist/plot/core/scales.d.ts +23 -0
  288. package/dist/plot/core/scales.js +39 -13
  289. package/dist/plot/core/svg.js +0 -1
  290. package/dist/plot/core/types/fills.d.ts +101 -0
  291. package/dist/plot/core/types/fills.js +15 -0
  292. package/dist/plot/core/types/plot-3d.d.ts +87 -0
  293. package/dist/plot/core/types/plot-3d.js +4 -0
  294. package/dist/plot/core/types/reference-lines.d.ts +140 -0
  295. package/dist/plot/core/types/reference-lines.js +9 -0
  296. package/dist/plot/core/types.d.ts +29 -324
  297. package/dist/plot/core/types.js +23 -20
  298. package/dist/plot/core/utils/hierarchy-chart.d.ts +52 -0
  299. package/dist/plot/core/utils/hierarchy-chart.js +191 -0
  300. package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
  301. package/dist/plot/core/utils/hierarchy-labels.js +100 -0
  302. package/dist/plot/core/utils/label-placement.js +2 -2
  303. package/dist/plot/histogram/Histogram.svelte +314 -340
  304. package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
  305. package/dist/plot/histogram/HistogramControls.svelte +30 -28
  306. package/dist/plot/histogram/histogram.d.ts +31 -0
  307. package/dist/plot/histogram/histogram.js +70 -0
  308. package/dist/plot/histogram/index.d.ts +1 -0
  309. package/dist/plot/histogram/index.js +1 -0
  310. package/dist/plot/index.d.ts +1 -0
  311. package/dist/plot/index.js +1 -0
  312. package/dist/plot/sankey/Sankey.svelte +78 -69
  313. package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
  314. package/dist/plot/sankey/SankeyControls.svelte +22 -16
  315. package/dist/plot/sankey/sankey.js +13 -9
  316. package/dist/plot/scatter/BinnedScatterPlot.svelte +311 -141
  317. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
  318. package/dist/plot/scatter/ElementScatter.svelte +4 -1
  319. package/dist/plot/scatter/ScatterPlot.svelte +517 -410
  320. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
  321. package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
  322. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
  323. package/dist/plot/scatter/ScatterPoint.svelte +16 -9
  324. package/dist/plot/scatter/adaptive-density.d.ts +1 -1
  325. package/dist/plot/scatter/adaptive-density.js +36 -20
  326. package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
  327. package/dist/plot/scatter/binned-scatter-types.js +6 -1
  328. package/dist/plot/scatter/scatter-data.d.ts +1 -1
  329. package/dist/plot/scatter/scatter-data.js +8 -7
  330. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
  331. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
  332. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
  333. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
  334. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
  335. package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
  336. package/dist/plot/sunburst/Sunburst.svelte +269 -347
  337. package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
  338. package/dist/plot/sunburst/index.d.ts +1 -1
  339. package/dist/plot/sunburst/index.js +2 -1
  340. package/dist/plot/sunburst/render.d.ts +2 -2
  341. package/dist/plot/sunburst/render.js +47 -22
  342. package/dist/plot/sunburst/sunburst.d.ts +5 -1
  343. package/dist/plot/sunburst/sunburst.js +10 -3
  344. package/dist/plot/treemap/Treemap.svelte +1022 -0
  345. package/dist/plot/treemap/Treemap.svelte.d.ts +90 -0
  346. package/dist/plot/treemap/index.d.ts +4 -0
  347. package/dist/plot/treemap/index.js +4 -0
  348. package/dist/plot/treemap/labels.d.ts +38 -0
  349. package/dist/plot/treemap/labels.js +80 -0
  350. package/dist/plot/treemap/treemap.d.ts +28 -0
  351. package/dist/plot/treemap/treemap.js +102 -0
  352. package/dist/rdf/RdfPlot.svelte +37 -59
  353. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  354. package/dist/rdf/calc-rdf.js +63 -52
  355. package/dist/rdf/index.d.ts +0 -1
  356. package/dist/sanitize.js +85 -27
  357. package/dist/scene/SceneCamera.svelte +1 -1
  358. package/dist/scene/SceneCamera.svelte.d.ts +1 -1
  359. package/dist/scene/bind-renderer.svelte.js +14 -6
  360. package/dist/scene/index.d.ts +1 -2
  361. package/dist/scene/index.js +1 -1
  362. package/dist/scene/{props.js → props.svelte.js} +15 -1
  363. package/dist/settings.d.ts +19 -9
  364. package/dist/settings.js +229 -525
  365. package/dist/spectral/Bands.svelte +207 -221
  366. package/dist/spectral/BandsAndDos.svelte +11 -9
  367. package/dist/spectral/BrillouinBandsDos.svelte +19 -42
  368. package/dist/spectral/Dos.svelte +92 -93
  369. package/dist/spectral/helpers.d.ts +3 -3
  370. package/dist/spectral/helpers.js +65 -140
  371. package/dist/state.svelte.js +5 -18
  372. package/dist/structure/Arrow.svelte +5 -18
  373. package/dist/structure/ArrowInstances.svelte +160 -0
  374. package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
  375. package/dist/structure/AtomLegend.svelte +120 -85
  376. package/dist/structure/AtomLegend.svelte.d.ts +2 -3
  377. package/dist/structure/Bond.svelte +31 -23
  378. package/dist/structure/CanvasTooltip.svelte +15 -3
  379. package/dist/structure/CellSelect.svelte +45 -35
  380. package/dist/structure/Cylinder.svelte +24 -40
  381. package/dist/structure/InstancedAtoms.svelte +114 -0
  382. package/dist/structure/InstancedAtoms.svelte.d.ts +15 -0
  383. package/dist/structure/Lattice.svelte +85 -149
  384. package/dist/structure/SiteLabels.svelte +122 -0
  385. package/dist/structure/SiteLabels.svelte.d.ts +18 -0
  386. package/dist/structure/Structure.svelte +636 -504
  387. package/dist/structure/Structure.svelte.d.ts +4 -2
  388. package/dist/structure/StructureCarousel.svelte +637 -0
  389. package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
  390. package/dist/structure/StructureControls.svelte +165 -425
  391. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  392. package/dist/structure/StructureExportPane.svelte +17 -21
  393. package/dist/structure/StructureInfoPane.svelte +128 -133
  394. package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
  395. package/dist/structure/StructureScene.svelte +535 -578
  396. package/dist/structure/StructureScene.svelte.d.ts +6 -2
  397. package/dist/structure/StructureViewport.svelte +451 -0
  398. package/dist/structure/StructureViewport.svelte.d.ts +65 -0
  399. package/dist/structure/atom-label-placement.d.ts +16 -12
  400. package/dist/structure/atom-label-placement.js +70 -37
  401. package/dist/structure/atom-properties.d.ts +2 -0
  402. package/dist/structure/atom-properties.js +105 -38
  403. package/dist/structure/bond-order-perception.js +3 -8
  404. package/dist/structure/bonding.d.ts +9 -3
  405. package/dist/structure/bonding.js +95 -46
  406. package/dist/structure/edit-history.d.ts +6 -0
  407. package/dist/structure/edit-history.js +27 -0
  408. package/dist/structure/export.d.ts +1 -0
  409. package/dist/structure/export.js +89 -105
  410. package/dist/structure/format-detect.d.ts +1 -0
  411. package/dist/structure/format-detect.js +20 -0
  412. package/dist/structure/geometry.d.ts +9 -0
  413. package/dist/structure/geometry.js +27 -0
  414. package/dist/structure/index.d.ts +16 -1
  415. package/dist/structure/index.js +15 -10
  416. package/dist/structure/measure.d.ts +1 -1
  417. package/dist/structure/measure.js +6 -12
  418. package/dist/structure/parse.d.ts +2 -1
  419. package/dist/structure/parse.js +272 -313
  420. package/dist/structure/partial-occupancy.js +10 -5
  421. package/dist/structure/pbc.js +85 -107
  422. package/dist/structure/polyhedra.d.ts +0 -1
  423. package/dist/structure/polyhedra.js +7 -21
  424. package/dist/structure/prop-groups.d.ts +85 -0
  425. package/dist/structure/prop-groups.js +18 -0
  426. package/dist/structure/supercell.d.ts +1 -2
  427. package/dist/structure/supercell.js +7 -13
  428. package/dist/symmetry/SymmetryElements.svelte +11 -15
  429. package/dist/symmetry/SymmetryStats.svelte +55 -71
  430. package/dist/symmetry/WyckoffTable.svelte +10 -28
  431. package/dist/symmetry/cell-transform.js +6 -14
  432. package/dist/symmetry/index.d.ts +1 -0
  433. package/dist/symmetry/index.js +13 -10
  434. package/dist/symmetry/spacegroups.js +3 -2
  435. package/dist/table/HeatmapTable.svelte +1142 -447
  436. package/dist/table/HeatmapTable.svelte.d.ts +12 -2
  437. package/dist/table/ToggleMenu.svelte +168 -53
  438. package/dist/table/ToggleMenu.svelte.d.ts +1 -1
  439. package/dist/table/index.d.ts +21 -6
  440. package/dist/table/index.js +34 -21
  441. package/dist/theme/ThemeControl.svelte +16 -8
  442. package/dist/theme/embedded.d.ts +18 -0
  443. package/dist/theme/embedded.js +206 -0
  444. package/dist/theme/index.d.ts +5 -5
  445. package/dist/theme/index.js +5 -12
  446. package/dist/theme/themes.mjs +44 -66
  447. package/dist/tooltip/KCoords.svelte +4 -1
  448. package/dist/tooltip/TooltipContent.svelte +3 -6
  449. package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
  450. package/dist/tooltip/index.d.ts +8 -1
  451. package/dist/trajectory/Trajectory.svelte +613 -386
  452. package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
  453. package/dist/trajectory/TrajectoryError.svelte +12 -74
  454. package/dist/trajectory/TrajectoryExportPane.svelte +14 -32
  455. package/dist/trajectory/TrajectoryInfoPane.svelte +127 -164
  456. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
  457. package/dist/trajectory/extract.js +61 -49
  458. package/dist/trajectory/format-detect.d.ts +2 -0
  459. package/dist/trajectory/format-detect.js +17 -13
  460. package/dist/trajectory/frame-reader.d.ts +2 -0
  461. package/dist/trajectory/frame-reader.js +35 -32
  462. package/dist/trajectory/helpers.d.ts +2 -0
  463. package/dist/trajectory/helpers.js +34 -56
  464. package/dist/trajectory/index.d.ts +1 -0
  465. package/dist/trajectory/index.js +5 -8
  466. package/dist/trajectory/parse/ase.js +1 -1
  467. package/dist/trajectory/parse/h5-utils.d.ts +13 -0
  468. package/dist/trajectory/parse/h5-utils.js +109 -0
  469. package/dist/trajectory/parse/hdf5.d.ts +1 -1
  470. package/dist/trajectory/parse/hdf5.js +116 -106
  471. package/dist/trajectory/parse/index.d.ts +6 -6
  472. package/dist/trajectory/parse/index.js +25 -33
  473. package/dist/trajectory/parse/lammps.js +8 -12
  474. package/dist/trajectory/parse/vasp.js +10 -7
  475. package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
  476. package/dist/trajectory/parse/vaspout-electronic.js +207 -0
  477. package/dist/trajectory/parse/vaspout-h5.d.ts +4 -0
  478. package/dist/trajectory/parse/vaspout-h5.js +281 -0
  479. package/dist/trajectory/parse/xyz.js +59 -9
  480. package/dist/trajectory/plotting.d.ts +8 -9
  481. package/dist/trajectory/plotting.js +113 -64
  482. package/dist/utils.d.ts +3 -0
  483. package/dist/utils.js +7 -0
  484. package/dist/xrd/XrdPlot.svelte +79 -118
  485. package/dist/xrd/broadening.js +2 -1
  486. package/dist/xrd/calc-xrd.js +24 -62
  487. package/dist/xrd/parse.d.ts +0 -1
  488. package/dist/xrd/parse.js +69 -95
  489. package/package.json +94 -34
  490. package/readme.md +6 -5
  491. package/dist/api/mp.d.ts +0 -6
  492. package/dist/api/mp.js +0 -22
  493. package/dist/plot/sunburst/SunburstControls.svelte +0 -200
  494. package/dist/scene/types.d.ts +0 -26
  495. package/dist/tooltip/types.d.ts +0 -8
  496. package/dist/trajectory/constants.d.ts +0 -6
  497. package/dist/trajectory/constants.js +0 -11
  498. /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
  499. /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { format_value } from '../../labels'
2
+ import { format_value_or_num } from '../../labels'
3
3
  import { FullscreenToggle, set_fullscreen_bg } from '../../layout'
4
4
  import type {
5
5
  AxisLoadError,
@@ -15,10 +15,22 @@
15
15
  HistogramControls,
16
16
  PlotAxis,
17
17
  PlotLegend,
18
+ PlotMarginals,
18
19
  ReferenceLine,
19
20
  } from '..'
20
- import type { AxisChangeState } from '../core/axis-utils'
21
- import { AXIS_DEFAULTS, create_axis_loader } from '../core/axis-utils'
21
+ import type { MarginalSeriesInput, MarginalsProp } from '../core/marginals'
22
+ import {
23
+ add_sides,
24
+ marginal_axis,
25
+ marginal_axis_presence,
26
+ normalize_marginals,
27
+ reserve_marginal_pad,
28
+ } from '../core/marginals'
29
+ import {
30
+ AXIS_DEFAULTS,
31
+ type AxisChangeState,
32
+ create_axis_loader,
33
+ } from '../core/axis-utils'
22
34
  import { extract_series_color, prepare_legend_data } from '../core/data-transform'
23
35
  import { create_placed_tween } from '../core/placed-tween.svelte'
24
36
  import { create_pan_zoom } from '../core/pan-zoom.svelte'
@@ -29,7 +41,9 @@
29
41
  vec2_equal,
30
42
  } from '../core/interactions'
31
43
  import {
44
+ AXIS_TITLE_OFFSET,
32
45
  calc_auto_padding,
46
+ DEFAULT_PLOT_PADDING,
33
47
  filter_padding,
34
48
  LABEL_GAP_DEFAULT,
35
49
  y2_axis_label_x,
@@ -46,7 +60,7 @@
46
60
  import type { IndexedRefLine } from '../core/reference-line'
47
61
  import { group_ref_lines_by_z, index_ref_lines } from '../core/reference-line'
48
62
  import {
49
- create_scale,
63
+ create_axis_scales,
50
64
  generate_ticks,
51
65
  get_nice_data_range,
52
66
  get_tick_label,
@@ -56,13 +70,15 @@
56
70
  DataSeries,
57
71
  LegendConfig,
58
72
  PlotConfig,
59
- ScaleType,
60
73
  } from '../core/types'
61
- import { get_scale_type_name } from '../core/types'
74
+ import {
75
+ compute_count_range,
76
+ compute_histogram_bins,
77
+ log_safe_range,
78
+ } from './histogram'
62
79
  import ZeroLines from '../core/components/ZeroLines.svelte'
63
80
  import ZoomRect from '../core/components/ZoomRect.svelte'
64
81
  import { DEFAULTS } from '../../settings'
65
- import { bin, max } from 'd3-array'
66
82
  import type { Snippet } from 'svelte'
67
83
  import { onDestroy, untrack } from 'svelte'
68
84
  import type { HTMLAttributes } from 'svelte/elements'
@@ -78,8 +94,8 @@
78
94
  y_axis: y_axis_init = {},
79
95
  y2_axis: y2_axis_init = {},
80
96
  display: display_init = DEFAULTS.histogram.display,
81
- range_padding = 0.05,
82
- padding = { t: 20, b: 60, l: 60, r: 20 },
97
+ range_padding = 0,
98
+ padding = DEFAULT_PLOT_PADDING,
83
99
  bins = $bindable(100),
84
100
  show_legend = $bindable(true),
85
101
  legend = {},
@@ -108,49 +124,47 @@
108
124
  on_axis_change,
109
125
  on_error,
110
126
  pan = {},
127
+ marginals = false,
111
128
  ...rest
112
- }: HTMLAttributes<HTMLDivElement> & BasePlotProps & PlotConfig & {
113
- series: DataSeries[]
114
- // Component-specific props
115
- bins?: number
116
- show_legend?: boolean
117
- legend?: LegendConfig | null
118
- bar?: BarStyle
119
- selected_property?: string
120
- mode?: `single` | `overlay`
121
- tooltip?: Snippet<[HistogramHandlerProps]>
122
- header_controls?: Snippet<
123
- [{ height: number; width: number; fullscreen: boolean }]
124
- >
125
- controls_extra?: Snippet<[Required<PlotConfig>]>
126
- change?: (data: { value: number; count: number; property: string } | null) => void
127
- on_bar_click?: (
128
- data: {
129
+ }: HTMLAttributes<HTMLDivElement> &
130
+ BasePlotProps &
131
+ PlotConfig & {
132
+ series: DataSeries[]
133
+ // Component-specific props
134
+ bins?: number
135
+ show_legend?: boolean
136
+ legend?: LegendConfig | null
137
+ bar?: BarStyle
138
+ selected_property?: string
139
+ mode?: `single` | `overlay`
140
+ tooltip?: Snippet<[HistogramHandlerProps]>
141
+ header_controls?: Snippet<[{ height: number; width: number; fullscreen: boolean }]>
142
+ controls_extra?: Snippet<[Required<PlotConfig>]>
143
+ change?: (data: { value: number; count: number; property: string } | null) => void
144
+ on_bar_click?: (data: {
129
145
  value: number
130
146
  count: number
131
147
  property: string
132
148
  event: MouseEvent | KeyboardEvent
133
- },
134
- ) => void
135
- on_bar_hover?: (
136
- data:
137
- | { value: number; count: number; property: string; event: MouseEvent }
138
- | null,
139
- ) => void
140
- ref_lines?: RefLine[]
141
- on_ref_line_click?: (event: RefLineEvent) => void
142
- on_ref_line_hover?: (event: RefLineEvent | null) => void
143
- on_series_toggle?: (series_idx: number) => void
144
- // Interactive axis props
145
- data_loader?: DataLoaderFn
146
- on_axis_change?: (
147
- axis: `x` | `x2` | `y` | `y2`,
148
- key: string,
149
- new_series: DataSeries[],
150
- ) => void
151
- on_error?: (error: AxisLoadError) => void
152
- pan?: PanConfig
153
- } = $props()
149
+ }) => void
150
+ on_bar_hover?: (
151
+ data: { value: number; count: number; property: string; event: MouseEvent } | null,
152
+ ) => void
153
+ ref_lines?: RefLine[]
154
+ on_ref_line_click?: (event: RefLineEvent) => void
155
+ on_ref_line_hover?: (event: RefLineEvent | null) => void
156
+ on_series_toggle?: (series_idx: number) => void
157
+ // Interactive axis props
158
+ data_loader?: DataLoaderFn
159
+ on_axis_change?: (
160
+ axis: `x` | `x2` | `y` | `y2`,
161
+ key: string,
162
+ new_series: DataSeries[],
163
+ ) => void
164
+ on_error?: (error: AxisLoadError) => void
165
+ pan?: PanConfig
166
+ marginals?: MarginalsProp
167
+ } = $props()
154
168
 
155
169
  // Local state for controls (initialized from props, owned by this component)
156
170
  // Include key AXIS_DEFAULTS props (range, ticks, scale_type) that PlotControls needs
@@ -160,25 +174,29 @@
160
174
  let bar = $state(untrack(() => ({ ...DEFAULTS.histogram.bar, ...bar_init })))
161
175
  let x_axis = $state(untrack(() => ({ ...axis_state_defaults, ...x_axis_init })))
162
176
  // x2-axis needs different default label_shift for top-side positioning
163
- let x2_axis = $state(untrack(() => ({
164
- ...axis_state_defaults,
165
- label_shift: { x: 0, y: 40 },
166
- ...x2_axis_init,
167
- })))
177
+ let x2_axis = $state(
178
+ untrack(() => ({
179
+ ...axis_state_defaults,
180
+ label_shift: { x: 0, y: AXIS_TITLE_OFFSET },
181
+ ...x2_axis_init,
182
+ })),
183
+ )
168
184
  let y_axis = $state(untrack(() => ({ ...axis_state_defaults, ...y_axis_init })))
169
185
  // y2 title stays vertically centered; its x position is computed by y2_axis_label_x
170
- let y2_axis = $state(untrack(() => ({
171
- ...axis_state_defaults,
172
- label_shift: { x: 0, y: 0 },
173
- ...y2_axis_init,
174
- })))
175
- let display = $state(
176
- untrack(() => ({ ...DEFAULTS.histogram.display, ...display_init })),
186
+ let y2_axis = $state(
187
+ untrack(() => ({
188
+ ...axis_state_defaults,
189
+ label_shift: { x: 0, y: 0 },
190
+ ...y2_axis_init,
191
+ })),
177
192
  )
193
+ let display = $state(untrack(() => ({ ...DEFAULTS.histogram.display, ...display_init })))
178
194
 
179
195
  // Merge component-specific defaults with local state (format comes from here, not AXIS_DEFAULTS)
180
- const final_x_axis = $derived({ label: `Value`, format: `.2~s`, ...x_axis })
181
- const final_x2_axis = $derived({ label: `Value`, format: `.2~s`, ...x2_axis })
196
+ // No default tick format: PlotAxis falls back to format_num, which uses SI
197
+ // prefixes above 1 but plain decimals below (0.2 must not render as 200m)
198
+ const final_x_axis = $derived({ label: `Value`, ...x_axis })
199
+ const final_x2_axis = $derived({ label: `Value`, ...x2_axis })
182
200
  const final_y_axis = $derived({ label: `Count`, format: `d`, ...y_axis })
183
201
  const final_bar = $derived({ ...DEFAULTS.histogram.bar, ...bar })
184
202
  const final_y2_axis = $derived({ label: `Count`, format: `d`, ...y2_axis })
@@ -197,8 +215,7 @@
197
215
  let axis_loading = $state<`x` | `x2` | `y` | `y2` | null>(null)
198
216
 
199
217
  // Compute ref_lines with index and group by z-index (using shared utilities)
200
- let indexed_ref_lines = $derived(index_ref_lines(ref_lines))
201
- let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines))
218
+ let ref_lines_by_z = $derived(group_ref_lines_by_z(index_ref_lines(ref_lines)))
202
219
 
203
220
  // Legend placement stability state
204
221
  let legend_element = $state<HTMLDivElement | undefined>()
@@ -220,29 +237,24 @@
220
237
  let selected_series_entries = $derived<IndexedSeries[]>(
221
238
  series
222
239
  .map((series_data: DataSeries, series_idx: number) => ({ series_data, series_idx }))
223
- .filter(({ series_data }) =>
224
- (series_data.visible ?? true) &&
225
- (mode !== `single` || !selected_property || series_data.label === selected_property)
240
+ .filter(
241
+ ({ series_data }) =>
242
+ (series_data.visible ?? true) &&
243
+ (mode !== `single` || !selected_property || series_data.label === selected_property),
226
244
  ),
227
245
  )
228
- let selected_series = $derived(
229
- selected_series_entries.map(({ series_data }) => series_data),
230
- )
246
+ let selected_series = $derived(selected_series_entries.map(({ series_data }) => series_data))
231
247
 
232
248
  // Separate series by y-axis
233
249
  let y1_series = $derived(
234
250
  selected_series.filter((srs: DataSeries) => (srs.y_axis ?? `y1`) === `y1`),
235
251
  )
236
- let y2_series = $derived(
237
- selected_series.filter((srs: DataSeries) => srs.y_axis === `y2`),
238
- )
239
- let x2_series = $derived(
240
- selected_series.filter((srs: DataSeries) => srs.x_axis === `x2`),
241
- )
252
+ let y2_series = $derived(selected_series.filter((srs: DataSeries) => srs.y_axis === `y2`))
253
+ let x2_series = $derived(selected_series.filter((srs: DataSeries) => srs.x_axis === `x2`))
242
254
 
243
255
  let auto_ranges = $derived.by(() => {
244
256
  // 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)
257
+ const x1_values = selected_series.flatMap((srs) => (srs.x_axis === `x2` ? [] : srs.y))
246
258
  const auto_x = get_nice_data_range(
247
259
  x1_values.map((val) => ({ x: val, y: 0 })),
248
260
  ({ x }) => x,
@@ -253,68 +265,29 @@
253
265
  )
254
266
 
255
267
  const x2_values = x2_series.flatMap((srs: DataSeries) => srs.y)
256
- const auto_x2 = x2_values.length > 0
257
- ? get_nice_data_range(
258
- x2_values.map((val) => ({ x: val, y: 0 })),
259
- ({ x }) => x,
260
- final_x2_axis.range ?? [null, null],
261
- final_x2_axis.scale_type ?? `linear`,
262
- range_padding,
263
- false,
264
- )
265
- : [0, 1] as Vec2
266
-
267
- // Calculate y-range for a specific set of series
268
- const calc_y_range = (
269
- series_list: typeof selected_series,
270
- y_limit: [number | null, number | null],
271
- scale_type: ScaleType,
272
- ): Vec2 => {
273
- const type_name = get_scale_type_name(scale_type)
274
- if (series_list.length === 0) {
275
- const fallback = type_name === `log` ? 1 : 0
276
- return [fallback, 1]
277
- }
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)
280
- const max_count = Math.max(
281
- 0,
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
- }),
286
- )
287
-
288
- // If there's effectively no data, avoid log-range issues (counts can't be <= 0 on log)
289
- if (max_count <= 0) {
290
- const fallback = type_name === `log` ? 1 : 0
291
- return [fallback, 1]
292
- }
293
-
294
- const [y0, y1] = get_nice_data_range(
295
- [{ x: 0, y: 0 }, { x: max_count, y: 0 }],
296
- ({ x }) => x,
297
- y_limit,
298
- scale_type,
299
- range_padding,
300
- false,
301
- )
302
- // For log scale, minimum must be >= 1 (count can't be 0 on log)
303
- // For linear/arcsinh, start from 0
304
- const y_min = type_name === `log` ? Math.max(1, y0) : Math.max(0, y0)
305
- return [y_min, y1]
306
- }
307
-
308
- const y1_range = calc_y_range(
309
- y1_series,
310
- final_y_axis.range ?? [null, null],
311
- final_y_axis.scale_type ?? `linear`,
312
- )
313
- const y2_auto_range = calc_y_range(
314
- y2_series,
315
- final_y2_axis.range ?? [null, null],
316
- final_y2_axis.scale_type ?? `linear`,
317
- )
268
+ const auto_x2 =
269
+ x2_values.length > 0
270
+ ? get_nice_data_range(
271
+ x2_values.map((val) => ({ x: val, y: 0 })),
272
+ ({ x }) => x,
273
+ final_x2_axis.range ?? [null, null],
274
+ final_x2_axis.scale_type ?? `linear`,
275
+ range_padding,
276
+ false,
277
+ )
278
+ : ([0, 1] as Vec2)
279
+
280
+ const count_cfg = { x_domain: auto_x, x2_domain: auto_x2, bin_count: bins, range_padding }
281
+ const y1_range = compute_count_range(y1_series, {
282
+ ...count_cfg,
283
+ scale_type: final_y_axis.scale_type ?? `linear`,
284
+ y_limit: log_safe_range(final_y_axis),
285
+ })
286
+ const y2_auto_range = compute_count_range(y2_series, {
287
+ ...count_cfg,
288
+ scale_type: final_y2_axis.scale_type ?? `linear`,
289
+ y_limit: log_safe_range(final_y2_axis),
290
+ })
318
291
 
319
292
  return { x: auto_x, x2: auto_x2, y: y1_range, y2: y2_auto_range }
320
293
  })
@@ -336,10 +309,16 @@
336
309
  })
337
310
 
338
311
  $effect(() => {
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)
312
+ // Supports one-sided range pinning (null bounds fall back to auto); returns null for transient
313
+ // non-finite bounds (skip: writing NaN breaks scales and loops here). y/y2 ranges are
314
+ // log-sanitized so an invalid (<= 0) log lower falls back to the auto count minimum.
341
315
  const next = resolve_axis_ranges(
342
- { x: final_x_axis, x2: final_x2_axis, y: final_y_axis, y2: final_y2_axis },
316
+ {
317
+ x: final_x_axis,
318
+ x2: final_x2_axis,
319
+ y: { range: log_safe_range(final_y_axis) },
320
+ y2: { range: log_safe_range(final_y2_axis) },
321
+ },
343
322
  auto_ranges,
344
323
  )
345
324
  if (!next) return
@@ -348,15 +327,16 @@
348
327
  // (reading + writing the same state otherwise causes effect_update_depth_exceeded).
349
328
  const init = untrack(() => ranges.initial)
350
329
  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]
330
+ if (!vec2_equal(init.x2, next.x2))
331
+ [ranges.initial.x2, ranges.current.x2] = [next.x2, next.x2]
352
332
  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]
333
+ if (!vec2_equal(init.y2, next.y2))
334
+ [ranges.initial.y2, ranges.current.y2] = [next.y2, next.y2]
354
335
  })
355
336
 
356
337
  // Layout: dynamic padding based on tick label widths
357
- const default_padding = { t: 20, b: 60, l: 60, r: 20 }
358
338
  // base_pad reserves space for tick labels/axis titles; pad (below) adds decoration reservations
359
- let base_pad = $derived(filter_padding(padding, default_padding))
339
+ let base_pad = $derived(filter_padding(padding, DEFAULT_PLOT_PADDING))
360
340
 
361
341
  // Update padding based on tick label widths (untrack breaks circular dependency)
362
342
  $effect(() => {
@@ -364,19 +344,23 @@
364
344
  const current_ticks_y = untrack(() => ticks.y)
365
345
  const current_ticks_y2 = untrack(() => ticks.y2)
366
346
 
367
- const new_pad = width && height && current_ticks_y.length > 0
368
- ? calc_auto_padding({
369
- padding,
370
- default_padding,
371
- x2_axis: { ...final_x2_axis, tick_values: current_ticks_x2 },
372
- y_axis: { ...final_y_axis, tick_values: current_ticks_y },
373
- y2_axis: { ...final_y2_axis, tick_values: current_ticks_y2 },
374
- })
375
- : filter_padding(padding, default_padding)
347
+ const new_pad =
348
+ width && height && current_ticks_y.length > 0
349
+ ? calc_auto_padding({
350
+ padding,
351
+ default_padding: DEFAULT_PLOT_PADDING,
352
+ x2_axis: { ...final_x2_axis, tick_values: current_ticks_x2 },
353
+ y_axis: { ...final_y_axis, tick_values: current_ticks_y },
354
+ y2_axis: { ...final_y2_axis, tick_values: current_ticks_y2 },
355
+ })
356
+ : filter_padding(padding, DEFAULT_PLOT_PADDING)
376
357
 
377
358
  // Add y2 axis label space (calc_auto_padding only accounts for tick labels)
378
359
  if (
379
- width && height && y2_series.length > 0 && current_ticks_y2.length > 0 &&
360
+ width &&
361
+ height &&
362
+ y2_series.length > 0 &&
363
+ current_ticks_y2.length > 0 &&
380
364
  final_y2_axis.label
381
365
  ) {
382
366
  const inside = final_y2_axis.tick?.label?.inside ?? false
@@ -392,29 +376,35 @@
392
376
 
393
377
  // Add x2 axis label space (mirroring y2 logic for top padding)
394
378
  if (
395
- width && height && x2_series.length > 0 && current_ticks_x2.length > 0 &&
379
+ width &&
380
+ height &&
381
+ x2_series.length > 0 &&
382
+ current_ticks_x2.length > 0 &&
396
383
  final_x2_axis.label
397
384
  ) {
398
385
  const inside = final_x2_axis.tick?.label?.inside ?? false
399
- const tick_shift = inside
400
- ? 0
401
- : Math.abs(final_x2_axis.tick?.label?.shift?.y ?? 0) + 8
386
+ const tick_shift = inside ? 0 : Math.abs(final_x2_axis.tick?.label?.shift?.y ?? 0) + 8
402
387
  const label_thickness = Math.round(12 * 1.2)
403
- new_pad.t = Math.max(
404
- new_pad.t,
405
- tick_shift + LABEL_GAP_DEFAULT + label_thickness,
406
- )
388
+ new_pad.t = Math.max(new_pad.t, tick_shift + LABEL_GAP_DEFAULT + label_thickness)
407
389
  }
408
390
 
409
391
  // Only update if padding actually changed
410
392
  if (
411
- base_pad.t !== new_pad.t || base_pad.b !== new_pad.b ||
412
- base_pad.l !== new_pad.l || base_pad.r !== new_pad.r
413
- ) base_pad = new_pad
393
+ base_pad.t !== new_pad.t ||
394
+ base_pad.b !== new_pad.b ||
395
+ base_pad.l !== new_pad.l ||
396
+ base_pad.r !== new_pad.r
397
+ )
398
+ base_pad = new_pad
414
399
  })
415
400
 
416
- const legend_footprint = $derived(measured_footprint(legend_element, { width: 120, height: 60 }))
401
+ let legend_size_revision = $state(0)
402
+ const legend_footprint = $derived.by(() => {
403
+ void legend_size_revision
404
+ return measured_footprint(legend_element, { width: 120, height: 60 })
405
+ })
417
406
  const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
407
+ const should_show_legend = $derived(show_legend && legend != null && series.length > 1)
418
408
 
419
409
  // Obstacle field in normalized [0,1] plot coords (y=0 at top). Each filled bar is modeled as a
420
410
  // vertical segment (top -> baseline) so the legend can't hide inside a tall bar. Built from
@@ -444,73 +434,64 @@
444
434
  })
445
435
 
446
436
  // Move the legend to the bottom margin when no interior spot avoids the bars
447
- const decor = $derived.by(() =>
437
+ const decor = $derived(
448
438
  place_decorations({
449
439
  base_pad,
450
440
  width,
451
441
  height,
452
442
  obstacles_norm,
453
443
  // gate on legend_element (the render signal) not legend_data, whose entries can read pad
454
- legend: show_legend && legend != null && series.length > 1 &&
455
- legend_element != null && !legend_has_explicit_pos
456
- ? { footprint: legend_footprint, clearance: legend?.axis_clearance }
457
- : null,
458
- })
444
+ legend:
445
+ should_show_legend && legend_element != null && !legend_has_explicit_pos
446
+ ? { footprint: legend_footprint, clearance: legend?.axis_clearance }
447
+ : null,
448
+ }),
449
+ )
450
+ // Resolve marginals (default: CDF strip on top) and reserve outer-band padding. Pass the
451
+ // histogram's `bins` as the marginal's histogram bin count (NOT `size`/thickness) so a
452
+ // `histogram` marginal inherits the main binning via normalize_marginals' merge; `cdf`
453
+ // ignores `bins`. Samples lie along x and equal series.y, so the adapter maps y -> x.
454
+ const resolved_marginals = $derived(
455
+ normalize_marginals(marginals, { top: { type: `cdf`, bins } }),
456
+ )
457
+ const pad = $derived(add_sides(decor.pad, reserve_marginal_pad(resolved_marginals)))
458
+ // a lone series uses the configured bar color; with multiple, each gets its own
459
+ const series_color = (series_data: DataSeries) =>
460
+ selected_series.length === 1 ? final_bar.color : extract_series_color(series_data)
461
+ const marginal_series = $derived<MarginalSeriesInput[]>(
462
+ selected_series_entries.map(({ series_data }) => ({
463
+ x: series_data.y ?? [],
464
+ color: series_color(series_data),
465
+ label: series_data.label,
466
+ visible: true,
467
+ x_axis: series_data.x_axis,
468
+ y_axis: series_data.y_axis,
469
+ })),
470
+ )
471
+ const marginal_has_axis = $derived(
472
+ marginal_axis_presence(x2_series.length > 0, y2_series.length > 0),
459
473
  )
460
- const pad = $derived(decor.pad)
461
- const legend_auto_outside = $derived(decor.legend_outside)
462
- const legend_outside_x = $derived(decor.legend_pos.x)
463
- const legend_outside_y = $derived(decor.legend_pos.y)
464
474
 
465
- // Scales and data
466
- let scales = $derived({
467
- x: create_scale(
468
- final_x_axis.scale_type ?? `linear`,
469
- ranges.current.x,
470
- [pad.l, width - pad.r],
471
- ),
472
- x2: create_scale(
473
- final_x2_axis.scale_type ?? `linear`,
474
- ranges.current.x2,
475
- [pad.l, width - pad.r],
476
- ),
477
- y: create_scale(
478
- final_y_axis.scale_type ?? `linear`,
479
- ranges.current.y,
480
- [height - pad.b, pad.t],
481
- ),
482
- y2: create_scale(
483
- final_y2_axis.scale_type ?? `linear`,
484
- ranges.current.y2,
485
- [height - pad.b, pad.t],
475
+ // Scales and data (x/x2 share the horizontal pixel span, y/y2 the inverted vertical one)
476
+ let scales = $derived(
477
+ create_axis_scales(
478
+ { x: final_x_axis, x2: final_x2_axis, y: final_y_axis, y2: final_y2_axis },
479
+ ranges.current,
480
+ pad,
481
+ width,
482
+ height,
486
483
  ),
487
- })
484
+ )
488
485
 
489
486
  // Pad-independent binning (no pixel scales) so the auto-place obstacle field can reuse it
490
487
  let histogram_bins = $derived.by(() => {
491
488
  if (selected_series.length === 0 || !width || !height) return []
492
- const hist_generator = bin()
493
- .domain([ranges.current.x[0], ranges.current.x[1]])
494
- .thresholds(bins)
495
- const x2_hist_generator = x2_series.length > 0
496
- ? bin().domain([ranges.current.x2[0], ranges.current.x2[1]]).thresholds(bins)
497
- : null
498
- return selected_series_entries.map(({ series_data, series_idx }) => {
499
- const use_x2 = series_data.x_axis === `x2`
500
- const active_hist = use_x2 && x2_hist_generator ? x2_hist_generator : hist_generator
501
- const bins_arr = active_hist(series_data.y)
502
- return {
503
- id: series_data.id ?? series_idx,
504
- series_idx,
505
- label: series_data.label || `Series ${series_idx + 1}`,
506
- color: selected_series.length === 1
507
- ? final_bar.color
508
- : extract_series_color(series_data),
509
- bins: bins_arr,
510
- max_count: max(bins_arr, (data) => data.length) || 0,
511
- x_axis: series_data.x_axis,
512
- y_axis: series_data.y_axis,
513
- }
489
+ return compute_histogram_bins(selected_series_entries, {
490
+ x_domain: ranges.current.x,
491
+ x2_domain: ranges.current.x2,
492
+ has_x2: x2_series.length > 0,
493
+ bin_count: bins,
494
+ series_color,
514
495
  })
515
496
  })
516
497
  // Render-time data adds the pixel scales (pad-dependent)
@@ -522,49 +503,30 @@
522
503
  })),
523
504
  )
524
505
 
525
- let ticks = $derived({
526
- x: width && height
527
- ? generate_ticks(
528
- ranges.current.x,
529
- final_x_axis.scale_type ?? `linear`,
530
- final_x_axis.ticks,
531
- scales.x,
532
- { default_count: 8 },
533
- )
534
- : [],
535
- x2: width && height && x2_series.length > 0
536
- ? generate_ticks(
537
- ranges.current.x2,
538
- final_x2_axis.scale_type ?? `linear`,
539
- final_x2_axis.ticks,
540
- scales.x2,
541
- { default_count: 8 },
542
- )
543
- : [],
544
- y: width && height
545
- ? generate_ticks(
546
- ranges.current.y,
547
- final_y_axis.scale_type ?? `linear`,
548
- final_y_axis.ticks,
549
- scales.y,
550
- { default_count: 6 },
551
- )
552
- : [],
553
- y2: width && height && y2_series.length > 0
554
- ? generate_ticks(
555
- ranges.current.y2,
556
- final_y2_axis.scale_type ?? `linear`,
557
- final_y2_axis.ticks,
558
- scales.y2,
559
- { default_count: 6 },
560
- )
561
- : [],
506
+ let ticks = $derived.by(() => {
507
+ // x/y always render; x2/y2 only when their series exist (else their scale is a [0,1] sentinel)
508
+ const axis_ticks = (
509
+ axis: typeof final_x_axis,
510
+ range: Vec2,
511
+ scale: typeof scales.x,
512
+ default_count: number,
513
+ show = true,
514
+ ) =>
515
+ width && height && show
516
+ ? generate_ticks(range, axis.scale_type ?? `linear`, axis.ticks, scale, {
517
+ default_count,
518
+ })
519
+ : []
520
+ const x = axis_ticks(final_x_axis, ranges.current.x, scales.x, 8)
521
+ const x2 = axis_ticks(final_x2_axis, ranges.current.x2, scales.x2, 8, x2_series.length > 0)
522
+ const y = axis_ticks(final_y_axis, ranges.current.y, scales.y, 6)
523
+ const y2 = axis_ticks(final_y2_axis, ranges.current.y2, scales.y2, 6, y2_series.length > 0)
524
+ return { x, x2, y, y2 }
562
525
  })
563
526
 
564
527
  // Cache measured tick-label widths so expensive text measurement only runs
565
528
  // when tick values/format change, not on every template rerender.
566
529
  let tick_label_widths = $derived({
567
- x2_max: measure_max_tick_width(ticks.x2, final_x2_axis.format ?? ``),
568
530
  y_max: measure_max_tick_width(ticks.y, final_y_axis.format ?? ``),
569
531
  y2_max: measure_max_tick_width(ticks.y2, final_y2_axis.format ?? ``),
570
532
  })
@@ -595,14 +557,13 @@
595
557
  })
596
558
 
597
559
  // Calculate best legend placement using continuous grid sampling
598
- let legend_placement = $derived.by(() => {
599
- const should_place = show_legend && legend != null && series.length > 1
600
- if (!should_place || !width || !height) return null
560
+ const get_legend_placement = () => {
561
+ if (!should_show_legend || !width || !height) return null
601
562
 
602
563
  const plot_width = width - pad.l - pad.r
603
564
  const plot_height = height - pad.t - pad.b
604
565
 
605
- const result = compute_element_placement({
566
+ return compute_element_placement({
606
567
  plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
607
568
  element: legend_element,
608
569
  element_size: { width: 120, height: 60 }, // fallback before first render
@@ -610,17 +571,16 @@
610
571
  exclude_rects: [],
611
572
  points: hist_points_for_placement,
612
573
  })
613
-
614
- return result
615
- })
574
+ }
616
575
 
617
576
  // Tweened legend coordinates with shared placement stability gating
618
577
  const legend_tween = create_placed_tween({
619
- placement: () => legend_placement,
578
+ placement: get_legend_placement,
620
579
  dims: () => ({ width, height }),
621
580
  responsive: () => legend?.responsive ?? false,
622
581
  element: () => legend_element,
623
582
  tween: () => legend?.tween,
583
+ on_element_resize: () => (legend_size_revision += 1),
624
584
  })
625
585
 
626
586
  // Shared pan/zoom/touch/drag-rect interaction controller
@@ -630,7 +590,9 @@
630
590
  ({ x: final_x_axis, x2: final_x2_axis, y: final_y_axis, y2: final_y2_axis })[axis]
631
591
  .scale_type,
632
592
  // Clamp to at least 1 to avoid Infinity deltas when padding equals container size
633
- plot_dims: () => ({
593
+ plot_bounds: () => ({
594
+ x: pad.l,
595
+ y: pad.t,
634
596
  width: Math.max(1, width - pad.l - pad.r),
635
597
  height: Math.max(1, height - pad.t - pad.b),
636
598
  }),
@@ -644,11 +606,13 @@
644
606
  x_axis = { ...x_axis, range: next_x }
645
607
  // gate x2/y2 on series presence: their scales are [0, 1] sentinels otherwise,
646
608
  // 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
609
+ const next_x2 =
610
+ x2_series.length > 0 ? invert_rect_range(scales.x2, start.x, current.x) : null
648
611
  if (next_x2) x2_axis = { ...x2_axis, range: next_x2 }
649
612
  const next_y = invert_rect_range(scales.y, start.y, current.y)
650
613
  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
614
+ const next_y2 =
615
+ y2_series.length > 0 ? invert_rect_range(scales.y2, start.y, current.y) : null
652
616
  if (next_y2) y2_axis = { ...y2_axis, range: next_y2 }
653
617
  },
654
618
  on_reset: () => {
@@ -698,7 +662,10 @@
698
662
  on_bar_hover?.({ value, count, property, event: evt })
699
663
  }
700
664
 
701
- const legend_vis = create_legend_visibility(() => series, (next) => (series = next))
665
+ const legend_vis = create_legend_visibility(
666
+ () => series,
667
+ (next) => (series = next),
668
+ )
702
669
 
703
670
  // Set theme-aware background when entering fullscreen
704
671
  $effect(() => {
@@ -706,31 +673,24 @@
706
673
  })
707
674
 
708
675
  // State accessors for shared axis change handler
676
+ // Spread into existing state in each setter to preserve merged type structure
709
677
  const axis_state: AxisChangeState<DataSeries> = {
710
- get_axis: (axis) => {
711
- if (axis === `x`) return x_axis
712
- if (axis === `x2`) return x2_axis
713
- if (axis === `y`) return y_axis
714
- return y2_axis
715
- },
716
- set_axis: (axis, config) => {
717
- // Spread into existing state to preserve merged type structure
718
- if (axis === `x`) x_axis = { ...x_axis, ...config }
719
- else if (axis === `x2`) x2_axis = { ...x2_axis, ...config }
720
- else if (axis === `y`) y_axis = { ...y_axis, ...config }
721
- else y2_axis = { ...y2_axis, ...config }
678
+ axes: {
679
+ x: { get: () => x_axis, set: (config) => (x_axis = { ...x_axis, ...config }) },
680
+ x2: { get: () => x2_axis, set: (config) => (x2_axis = { ...x2_axis, ...config }) },
681
+ y: { get: () => y_axis, set: (config) => (y_axis = { ...y_axis, ...config }) },
682
+ y2: { get: () => y2_axis, set: (config) => (y2_axis = { ...y2_axis, ...config }) },
722
683
  },
723
- get_series: () => series,
724
- set_series: (new_series) => (series = new_series),
725
- get_loading: () => axis_loading,
726
- set_loading: (axis) => (axis_loading = axis),
684
+ series: { get: () => series, set: (next) => (series = next) },
685
+ loading: { get: () => axis_loading, set: (axis) => (axis_loading = axis) },
727
686
  }
728
687
 
729
688
  // Shared handler + one-shot auto-load bound to this component's state
730
- const { handle_axis_change, try_auto_load } = create_axis_loader(
731
- axis_state,
732
- () => ({ data_loader, on_axis_change, on_error }),
733
- )
689
+ const { handle_axis_change, try_auto_load } = create_axis_loader(axis_state, () => ({
690
+ data_loader,
691
+ on_axis_change,
692
+ on_error,
693
+ }))
734
694
  $effect(try_auto_load)
735
695
  </script>
736
696
 
@@ -795,8 +755,7 @@
795
755
  bind:this={svg_element}
796
756
  role="application"
797
757
  aria-label={rest[`aria-label`] ??
798
- ([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) ||
799
- `Histogram`)}
758
+ ([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) || `Histogram`)}
800
759
  tabindex="0"
801
760
  onfocusin={() => pan_zoom.set_focused(true)}
802
761
  onfocusout={() => pan_zoom.set_focused(false)}
@@ -863,17 +822,17 @@
863
822
  <!-- X-axis -->
864
823
  <PlotAxis
865
824
  side="x"
866
- ticks={ticks.x as number[]}
825
+ ticks={ticks.x}
867
826
  place={scales.x}
868
827
  axis={final_x_axis}
869
- domain={ranges.current.x as Vec2}
828
+ domain={ranges.current.x}
870
829
  {pad}
871
830
  {width}
872
831
  {height}
873
832
  show_grid={display.x_grid}
874
833
  tick_label={(tick) => get_tick_label(tick, final_x_axis.ticks)}
875
834
  label_x={(pad.l + width - pad.r) / 2 + (final_x_axis.label_shift?.x ?? 0)}
876
- label_y={height - 10 + (final_x_axis.label_shift?.y ?? 0)}
835
+ label_y={height - pad.b + AXIS_TITLE_OFFSET + (final_x_axis.label_shift?.y ?? 0)}
877
836
  axis_loading={axis_loading === `x`}
878
837
  on_axis_change={(key) => handle_axis_change(`x`, key)}
879
838
  />
@@ -882,17 +841,17 @@
882
841
  {#if x2_series.length > 0}
883
842
  <PlotAxis
884
843
  side="x2"
885
- ticks={ticks.x2 as number[]}
844
+ ticks={ticks.x2}
886
845
  place={scales.x2}
887
846
  axis={final_x2_axis}
888
- domain={ranges.current.x2 as Vec2}
847
+ domain={ranges.current.x2}
889
848
  {pad}
890
849
  {width}
891
850
  {height}
892
851
  show_grid={display.x2_grid}
893
852
  tick_label={(tick) => get_tick_label(tick, final_x2_axis.ticks)}
894
853
  label_x={(pad.l + width - pad.r) / 2 + (final_x2_axis.label_shift?.x ?? 0)}
895
- label_y={Math.max(12, pad.t - (final_x2_axis.label_shift?.y ?? 40))}
854
+ label_y={Math.max(12, pad.t - (final_x2_axis.label_shift?.y ?? AXIS_TITLE_OFFSET))}
896
855
  axis_loading={axis_loading === `x2`}
897
856
  on_axis_change={(key) => handle_axis_change(`x2`, key)}
898
857
  />
@@ -901,10 +860,10 @@
901
860
  <!-- Y-axis -->
902
861
  <PlotAxis
903
862
  side="y"
904
- ticks={ticks.y as number[]}
863
+ ticks={ticks.y}
905
864
  place={scales.y}
906
865
  axis={final_y_axis}
907
- domain={ranges.current.y as Vec2}
866
+ domain={ranges.current.y}
908
867
  {pad}
909
868
  {width}
910
869
  {height}
@@ -912,7 +871,8 @@
912
871
  tick_label={(tick) => get_tick_label(tick, final_y_axis.ticks)}
913
872
  label_x={Math.max(
914
873
  12,
915
- pad.l - (final_y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
874
+ pad.l -
875
+ (final_y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
916
876
  LABEL_GAP_DEFAULT,
917
877
  ) + (final_y_axis.label_shift?.x ?? 0)}
918
878
  label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y_axis.label_shift?.y ?? 0)}
@@ -924,10 +884,10 @@
924
884
  {#if y2_series.length > 0}
925
885
  <PlotAxis
926
886
  side="y2"
927
- ticks={ticks.y2 as number[]}
887
+ ticks={ticks.y2}
928
888
  place={scales.y2}
929
889
  axis={final_y2_axis}
930
- domain={ranges.current.y2 as Vec2}
890
+ domain={ranges.current.y2}
931
891
  {pad}
932
892
  {width}
933
893
  {height}
@@ -941,24 +901,19 @@
941
901
  {/if}
942
902
 
943
903
  <!-- Histogram bars (rendered after axes so bars appear above grid lines) -->
944
- {#each histogram_data as
945
- { id, bins, color, label, x_scale, y_scale, x_axis: srs_x_axis, y_axis, series_idx },
946
- idx
947
- (id ?? idx)
948
- }
904
+ {#each histogram_data as { id, bins, color, label, x_scale, y_scale, x_axis: srs_x_axis, y_axis, series_idx }, idx (id ?? idx)}
949
905
  <g
950
906
  class="histogram-series"
951
907
  data-series-idx={series_idx}
952
908
  clip-path="url(#{clip_path_id})"
953
- opacity={hovered_legend_series_idx !== null &&
954
- hovered_legend_series_idx !== series_idx
909
+ opacity={hovered_legend_series_idx !== null && hovered_legend_series_idx !== series_idx
955
910
  ? 0.25
956
911
  : 1}
957
912
  >
958
913
  {#each bins as bin, bin_idx (bin_idx)}
959
914
  {@const bar_x = x_scale(bin.x0!)}
960
915
  {@const bar_width = Math.max(1, Math.abs(x_scale(bin.x1!) - bar_x))}
961
- {@const bar_height = Math.max(0, (height - pad.b) - y_scale(bin.length))}
916
+ {@const bar_height = Math.max(0, height - pad.b - y_scale(bin.length))}
962
917
  {@const bar_y = y_scale(bin.length)}
963
918
  {@const value = (bin.x0! + bin.x1!) / 2}
964
919
  {#if bar_height > 0}
@@ -978,22 +933,22 @@
978
933
  role="button"
979
934
  tabindex="0"
980
935
  onmousemove={(evt) =>
981
- handle_mouse_move(
982
- evt,
983
- value,
984
- bin.length,
985
- label,
986
- (y_axis ?? `y1`) as `y1` | `y2`,
987
- series_idx,
988
- (srs_x_axis ?? `x1`) as `x1` | `x2`,
989
- )}
936
+ handle_mouse_move(
937
+ evt,
938
+ value,
939
+ bin.length,
940
+ label,
941
+ (y_axis ?? `y1`) as `y1` | `y2`,
942
+ series_idx,
943
+ (srs_x_axis ?? `x1`) as `x1` | `x2`,
944
+ )}
990
945
  onmouseleave={() => {
991
946
  hover_info = null
992
947
  change(null)
993
948
  on_bar_hover?.(null)
994
949
  }}
995
950
  onclick={(event) =>
996
- on_bar_click?.({ value, count: bin.length, property: label, event })}
951
+ on_bar_click?.({ value, count: bin.length, property: label, event })}
997
952
  onkeydown={(event: KeyboardEvent) => {
998
953
  if ([`Enter`, ` `].includes(event.key)) {
999
954
  event.preventDefault()
@@ -1009,6 +964,23 @@
1009
964
 
1010
965
  <!-- Reference lines: above all -->
1011
966
  {@render ref_lines_layer(ref_lines_by_z.above_all)}
967
+
968
+ <!-- Marginal distribution strips -->
969
+ <PlotMarginals
970
+ marginals={resolved_marginals}
971
+ series={marginal_series}
972
+ {width}
973
+ {height}
974
+ {pad}
975
+ has_axis={marginal_has_axis}
976
+ axes={{
977
+ x1: marginal_axis(scales.x, ranges.current.x, final_x_axis),
978
+ x2: marginal_axis(scales.x2, ranges.current.x2, final_x2_axis),
979
+ y1: marginal_axis(scales.y, ranges.current.y, final_y_axis),
980
+ y2: marginal_axis(scales.y2, ranges.current.y2, final_y2_axis),
981
+ }}
982
+ id={clip_path_id}
983
+ />
1012
984
  </svg>
1013
985
 
1014
986
  <!-- Tooltip (outside SVG for proper HTML rendering) -->
@@ -1026,8 +998,8 @@
1026
998
  {#if tooltip}
1027
999
  {@render tooltip({ ...hover_info, fullscreen })}
1028
1000
  {:else}
1029
- <div>Value: {format_value(value, hover_info.x_axis.format || `.3~s`)}</div>
1030
- <div>Count: {format_value(count, hover_info.y_axis.format || `.3~s`)}</div>
1001
+ <div>Value: {format_value_or_num(value, hover_info.x_axis.format)}</div>
1002
+ <div>Count: {format_value_or_num(count, hover_info.y_axis.format)}</div>
1031
1003
  {#if mode === `overlay`}<div>{property}</div>{/if}
1032
1004
  {/if}
1033
1005
  </PlotTooltip>
@@ -1065,29 +1037,29 @@
1065
1037
  />
1066
1038
  {/if}
1067
1039
 
1068
- {#if show_legend && legend != null && series.length > 1}
1040
+ {#if should_show_legend && legend}
1069
1041
  {@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
- )}
1042
+ decor.legend_outside,
1043
+ decor.legend_pos,
1044
+ legend_tween.placed(),
1045
+ legend_tween.coords.current,
1046
+ { x: pad.l + 10, y: pad.t + 10 },
1047
+ )}
1076
1048
  <PlotLegend
1077
1049
  bind:root_element={legend_element}
1078
1050
  {...legend}
1079
1051
  series_data={legend_data}
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
- })}
1052
+ on_toggle={legend?.on_toggle ??
1053
+ ((series_idx: number) => {
1054
+ if (series_idx < 0 || series_idx >= series.length) return
1055
+ legend_vis.on_toggle(series_idx)
1056
+ on_series_toggle(series_idx)
1057
+ })}
1085
1058
  on_double_click={legend?.on_double_click ?? legend_vis.on_double_click}
1086
1059
  on_hover_change={legend_tween.set_locked}
1087
1060
  on_item_hover={(item) =>
1088
- (hovered_legend_series_idx = item != null && item.series_idx >= 0
1089
- ? item.series_idx
1090
- : null)}
1061
+ (hovered_legend_series_idx =
1062
+ item != null && item.series_idx >= 0 ? item.series_idx : null)}
1091
1063
  active_series_idx={hover_info?.series_idx ?? hovered_legend_series_idx}
1092
1064
  style={`
1093
1065
  position: absolute;
@@ -1153,7 +1125,9 @@
1153
1125
  .histogram :global(.pane-toggle),
1154
1126
  .histogram .header-controls {
1155
1127
  opacity: 0;
1156
- transition: opacity 0.2s, background-color 0.2s;
1128
+ transition:
1129
+ opacity 0.2s,
1130
+ background-color 0.2s;
1157
1131
  }
1158
1132
  .histogram:hover :global(.pane-toggle),
1159
1133
  .histogram:hover .header-controls,