matterviz 0.4.1 → 0.4.2

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 (395) hide show
  1. package/dist/EmptyState.svelte +10 -4
  2. package/dist/FilePicker.svelte +12 -14
  3. package/dist/Icon.svelte +7 -4
  4. package/dist/MillerIndexInput.svelte +2 -2
  5. package/dist/app.css +53 -0
  6. package/dist/brillouin/BrillouinZone.svelte +75 -85
  7. package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
  8. package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
  9. package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
  10. package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
  11. package/dist/brillouin/BrillouinZoneScene.svelte +26 -39
  12. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  13. package/dist/chempot-diagram/ChemPotDiagram.svelte +33 -38
  14. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +62 -74
  15. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
  16. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +511 -547
  17. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
  18. package/dist/chempot-diagram/async-compute.svelte.js +1 -1
  19. package/dist/chempot-diagram/controls-state.svelte.js +1 -1
  20. package/dist/colors/index.d.ts +0 -4
  21. package/dist/colors/index.js +19 -20
  22. package/dist/composition/BarChart.svelte +37 -31
  23. package/dist/composition/BubbleChart.svelte +17 -17
  24. package/dist/composition/Composition.svelte +7 -11
  25. package/dist/composition/Formula.svelte +18 -25
  26. package/dist/composition/FormulaFilter.svelte +92 -95
  27. package/dist/composition/PieChart.svelte +55 -41
  28. package/dist/composition/format.js +1 -1
  29. package/dist/composition/index.d.ts +2 -0
  30. package/dist/composition/index.js +14 -0
  31. package/dist/composition/parse.d.ts +2 -2
  32. package/dist/composition/parse.js +11 -10
  33. package/dist/constants.js +11 -9
  34. package/dist/convex-hull/ConvexHull.svelte +12 -15
  35. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  36. package/dist/convex-hull/ConvexHull2D.svelte +93 -92
  37. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  38. package/dist/convex-hull/ConvexHull3D.svelte +109 -140
  39. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  40. package/dist/convex-hull/ConvexHull4D.svelte +75 -94
  41. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  42. package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
  43. package/dist/convex-hull/ConvexHullControls.svelte +46 -48
  44. package/dist/convex-hull/ConvexHullStats.svelte +193 -195
  45. package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
  46. package/dist/convex-hull/GasPressureControls.svelte +5 -12
  47. package/dist/convex-hull/StructurePopup.svelte +17 -21
  48. package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
  49. package/dist/convex-hull/TemperatureSlider.svelte +4 -10
  50. package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
  51. package/dist/convex-hull/helpers.d.ts +8 -10
  52. package/dist/convex-hull/helpers.js +36 -36
  53. package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
  54. package/dist/convex-hull/thermodynamics.js +18 -17
  55. package/dist/coordination/CoordinationBarPlot.svelte +38 -33
  56. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  57. package/dist/coordination/calc-coordination.d.ts +1 -1
  58. package/dist/coordination/calc-coordination.js +28 -25
  59. package/dist/element/BohrAtom.svelte +5 -8
  60. package/dist/element/ElementHeading.svelte +4 -1
  61. package/dist/element/ElementPhoto.svelte +11 -9
  62. package/dist/element/ElementStats.svelte +12 -10
  63. package/dist/element/ElementTile.svelte +24 -29
  64. package/dist/element/Nucleus.svelte +4 -2
  65. package/dist/element/data.d.ts +1 -2
  66. package/dist/element/helpers.d.ts +4 -0
  67. package/dist/element/helpers.js +18 -0
  68. package/dist/element/index.d.ts +1 -4
  69. package/dist/element/index.js +3 -18
  70. package/dist/feedback/DragOverlay.svelte +5 -5
  71. package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
  72. package/dist/feedback/StatusMessage.svelte +7 -4
  73. package/dist/fermi-surface/FermiSlice.svelte +21 -18
  74. package/dist/fermi-surface/FermiSurface.svelte +14 -22
  75. package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -7
  76. package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
  77. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  78. package/dist/fermi-surface/FermiSurfaceScene.svelte +37 -64
  79. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  80. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
  81. package/dist/fermi-surface/compute.js +3 -3
  82. package/dist/fermi-surface/parse.js +22 -24
  83. package/dist/heatmap-matrix/HeatmapMatrix.svelte +109 -148
  84. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
  85. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
  86. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  87. package/dist/heatmap-matrix/index.d.ts +5 -0
  88. package/dist/icons.d.ts +4 -0
  89. package/dist/icons.js +4 -0
  90. package/dist/io/ExportPane.svelte +10 -6
  91. package/dist/io/decompress.d.ts +2 -1
  92. package/dist/io/decompress.js +22 -35
  93. package/dist/io/export.d.ts +4 -0
  94. package/dist/io/export.js +5 -1
  95. package/dist/io/fetch.d.ts +0 -3
  96. package/dist/io/fetch.js +0 -11
  97. package/dist/io/index.d.ts +0 -1
  98. package/dist/io/index.js +2 -9
  99. package/dist/io/is-binary.d.ts +9 -0
  100. package/dist/io/is-binary.js +45 -1
  101. package/dist/io/url-drop.js +13 -25
  102. package/dist/isosurface/Isosurface.svelte +17 -23
  103. package/dist/isosurface/IsosurfaceControls.svelte +37 -42
  104. package/dist/isosurface/parse-vaspwave.d.ts +3 -0
  105. package/dist/isosurface/parse-vaspwave.js +138 -0
  106. package/dist/isosurface/parse.js +6 -6
  107. package/dist/labels.d.ts +6 -2
  108. package/dist/labels.js +43 -13
  109. package/dist/layout/FullscreenButton.svelte +2 -1
  110. package/dist/layout/FullscreenToggle.svelte +10 -4
  111. package/dist/layout/InfoCard.svelte +7 -18
  112. package/dist/layout/InfoTag.svelte +1 -3
  113. package/dist/layout/NumberRangeInput.svelte +46 -0
  114. package/dist/layout/NumberRangeInput.svelte.d.ts +12 -0
  115. package/dist/layout/PropertyFilter.svelte +2 -6
  116. package/dist/layout/SettingsSection.svelte +21 -17
  117. package/dist/layout/SubpageGrid.svelte +3 -1
  118. package/dist/layout/ViewerChrome.svelte +9 -10
  119. package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
  120. package/dist/layout/fullscreen.js +3 -5
  121. package/dist/layout/fullscreen.svelte.js +1 -2
  122. package/dist/layout/index.d.ts +1 -0
  123. package/dist/layout/index.js +1 -0
  124. package/dist/layout/json-tree/JsonNode.svelte +8 -11
  125. package/dist/layout/json-tree/JsonTree.svelte +53 -77
  126. package/dist/layout/json-tree/JsonValue.svelte +7 -15
  127. package/dist/layout/json-tree/utils.js +14 -23
  128. package/dist/math.d.ts +2 -0
  129. package/dist/math.js +19 -0
  130. package/dist/overlays/ContextMenu.svelte +4 -2
  131. package/dist/overlays/CopyButton.svelte +11 -5
  132. package/dist/overlays/DragControlTab.svelte +6 -1
  133. package/dist/overlays/DraggablePane.svelte +81 -26
  134. package/dist/overlays/DraggablePane.svelte.d.ts +1 -0
  135. package/dist/overlays/GlassChip.svelte +30 -0
  136. package/dist/overlays/GlassChip.svelte.d.ts +8 -0
  137. package/dist/overlays/InfoPaneCards.svelte +11 -23
  138. package/dist/overlays/index.d.ts +7 -0
  139. package/dist/overlays/index.js +26 -0
  140. package/dist/overlays/portal.d.ts +2 -0
  141. package/dist/overlays/portal.js +14 -0
  142. package/dist/periodic-table/PeriodicTable.svelte +113 -145
  143. package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
  144. package/dist/periodic-table/PeriodicTableControls.svelte +11 -63
  145. package/dist/periodic-table/TableInset.svelte +9 -4
  146. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +50 -71
  147. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
  148. package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
  149. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  150. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  151. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
  152. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
  153. package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
  154. package/dist/phase-diagram/build-diagram.js +3 -3
  155. package/dist/phase-diagram/diagram-input.d.ts +1 -2
  156. package/dist/phase-diagram/parse.js +15 -15
  157. package/dist/phase-diagram/svg-to-diagram.js +21 -15
  158. package/dist/phase-diagram/utils.js +2 -3
  159. package/dist/plot/bar/BarPlot.svelte +430 -391
  160. package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
  161. package/dist/plot/bar/BarPlotControls.svelte +1 -3
  162. package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
  163. package/dist/plot/bar/SpacegroupBarPlot.svelte +29 -36
  164. package/dist/plot/bar/data.d.ts +5 -4
  165. package/dist/plot/bar/data.js +2 -1
  166. package/dist/plot/box/BoxPlot.svelte +301 -209
  167. package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
  168. package/dist/plot/box/Violin.svelte.d.ts +1 -0
  169. package/dist/plot/box/box-plot.js +1 -1
  170. package/dist/plot/core/auto-place.js +1 -1
  171. package/dist/plot/core/axis-utils.d.ts +12 -7
  172. package/dist/plot/core/axis-utils.js +48 -48
  173. package/dist/plot/core/components/AxisLabel.svelte +82 -20
  174. package/dist/plot/core/components/AxisLabel.svelte.d.ts +1 -0
  175. package/dist/plot/core/components/ColorBar.svelte +101 -106
  176. package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
  177. package/dist/plot/core/components/FillArea.svelte +10 -17
  178. package/dist/plot/core/components/HierarchyControls.svelte +249 -0
  179. package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
  180. package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
  181. package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
  182. package/dist/plot/core/components/Line.svelte +18 -9
  183. package/dist/plot/core/components/Line.svelte.d.ts +2 -0
  184. package/dist/plot/core/components/PlotAxis.svelte +40 -17
  185. package/dist/plot/core/components/PlotControls.svelte +65 -41
  186. package/dist/plot/core/components/PlotLegend.svelte +31 -35
  187. package/dist/plot/core/components/PlotMarginals.svelte +616 -0
  188. package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
  189. package/dist/plot/core/components/PlotTooltip.svelte +1 -1
  190. package/dist/plot/core/components/PortalSelect.svelte +4 -8
  191. package/dist/plot/core/components/ReferenceLine.svelte +27 -24
  192. package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
  193. package/dist/plot/core/components/ReferencePlane.svelte +7 -8
  194. package/dist/plot/core/components/ZeroLines.svelte +4 -8
  195. package/dist/plot/core/components/ZoomRect.svelte +1 -2
  196. package/dist/plot/core/components/index.d.ts +1 -0
  197. package/dist/plot/core/components/index.js +1 -0
  198. package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
  199. package/dist/plot/core/data-cleaning-signal.js +467 -0
  200. package/dist/plot/core/data-cleaning.d.ts +2 -42
  201. package/dist/plot/core/data-cleaning.js +27 -474
  202. package/dist/plot/core/data-transform.js +9 -5
  203. package/dist/plot/core/fill-utils.d.ts +3 -1
  204. package/dist/plot/core/fill-utils.js +28 -6
  205. package/dist/plot/core/index.d.ts +1 -0
  206. package/dist/plot/core/index.js +1 -0
  207. package/dist/plot/core/interactions.js +0 -2
  208. package/dist/plot/core/layout.d.ts +3 -1
  209. package/dist/plot/core/layout.js +22 -13
  210. package/dist/plot/core/marginals.d.ts +170 -0
  211. package/dist/plot/core/marginals.js +436 -0
  212. package/dist/plot/core/reference-line.js +1 -1
  213. package/dist/plot/core/scales.d.ts +23 -0
  214. package/dist/plot/core/scales.js +39 -13
  215. package/dist/plot/core/types/fills.d.ts +101 -0
  216. package/dist/plot/core/types/fills.js +15 -0
  217. package/dist/plot/core/types/plot-3d.d.ts +87 -0
  218. package/dist/plot/core/types/plot-3d.js +4 -0
  219. package/dist/plot/core/types/reference-lines.d.ts +140 -0
  220. package/dist/plot/core/types/reference-lines.js +9 -0
  221. package/dist/plot/core/types.d.ts +29 -324
  222. package/dist/plot/core/types.js +23 -20
  223. package/dist/plot/core/utils/hierarchy-chart.d.ts +51 -0
  224. package/dist/plot/core/utils/hierarchy-chart.js +179 -0
  225. package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
  226. package/dist/plot/core/utils/hierarchy-labels.js +99 -0
  227. package/dist/plot/core/utils/label-placement.js +2 -2
  228. package/dist/plot/histogram/Histogram.svelte +304 -325
  229. package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
  230. package/dist/plot/histogram/HistogramControls.svelte +30 -28
  231. package/dist/plot/histogram/histogram.d.ts +31 -0
  232. package/dist/plot/histogram/histogram.js +70 -0
  233. package/dist/plot/histogram/index.d.ts +1 -0
  234. package/dist/plot/histogram/index.js +1 -0
  235. package/dist/plot/index.d.ts +1 -0
  236. package/dist/plot/index.js +1 -0
  237. package/dist/plot/sankey/Sankey.svelte +78 -69
  238. package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
  239. package/dist/plot/sankey/SankeyControls.svelte +22 -16
  240. package/dist/plot/scatter/BinnedScatterPlot.svelte +263 -109
  241. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
  242. package/dist/plot/scatter/ElementScatter.svelte +4 -1
  243. package/dist/plot/scatter/ScatterPlot.svelte +477 -385
  244. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
  245. package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
  246. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
  247. package/dist/plot/scatter/ScatterPoint.svelte +16 -9
  248. package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
  249. package/dist/plot/scatter/binned-scatter-types.js +6 -1
  250. package/dist/plot/scatter/scatter-data.d.ts +1 -1
  251. package/dist/plot/scatter/scatter-data.js +8 -7
  252. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
  253. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
  254. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
  255. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
  256. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
  257. package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
  258. package/dist/plot/sunburst/Sunburst.svelte +251 -329
  259. package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
  260. package/dist/plot/sunburst/index.d.ts +1 -1
  261. package/dist/plot/sunburst/index.js +2 -1
  262. package/dist/plot/sunburst/render.d.ts +2 -2
  263. package/dist/plot/sunburst/render.js +47 -22
  264. package/dist/plot/sunburst/sunburst.d.ts +5 -1
  265. package/dist/plot/sunburst/sunburst.js +10 -3
  266. package/dist/plot/treemap/Treemap.svelte +907 -0
  267. package/dist/plot/treemap/Treemap.svelte.d.ts +84 -0
  268. package/dist/plot/treemap/index.d.ts +3 -0
  269. package/dist/plot/treemap/index.js +4 -0
  270. package/dist/plot/treemap/treemap.d.ts +26 -0
  271. package/dist/plot/treemap/treemap.js +99 -0
  272. package/dist/rdf/RdfPlot.svelte +22 -26
  273. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  274. package/dist/rdf/calc-rdf.js +4 -8
  275. package/dist/sanitize.js +1 -1
  276. package/dist/scene/bind-renderer.svelte.js +14 -6
  277. package/dist/settings.d.ts +19 -9
  278. package/dist/settings.js +77 -50
  279. package/dist/spectral/Bands.svelte +223 -230
  280. package/dist/spectral/BandsAndDos.svelte +11 -9
  281. package/dist/spectral/BrillouinBandsDos.svelte +19 -27
  282. package/dist/spectral/Dos.svelte +70 -73
  283. package/dist/spectral/helpers.d.ts +3 -3
  284. package/dist/spectral/helpers.js +25 -35
  285. package/dist/state.svelte.js +5 -18
  286. package/dist/structure/Arrow.svelte +5 -18
  287. package/dist/structure/ArrowInstances.svelte +160 -0
  288. package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
  289. package/dist/structure/AtomLegend.svelte +115 -75
  290. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  291. package/dist/structure/Bond.svelte +31 -23
  292. package/dist/structure/CanvasTooltip.svelte +15 -3
  293. package/dist/structure/CellSelect.svelte +11 -26
  294. package/dist/structure/Cylinder.svelte +24 -40
  295. package/dist/structure/InstancedAtoms.svelte +114 -0
  296. package/dist/structure/InstancedAtoms.svelte.d.ts +15 -0
  297. package/dist/structure/Lattice.svelte +85 -149
  298. package/dist/structure/SiteLabels.svelte +122 -0
  299. package/dist/structure/SiteLabels.svelte.d.ts +18 -0
  300. package/dist/structure/Structure.svelte +473 -419
  301. package/dist/structure/Structure.svelte.d.ts +4 -2
  302. package/dist/structure/StructureCarousel.svelte +630 -0
  303. package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
  304. package/dist/structure/StructureControls.svelte +157 -399
  305. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  306. package/dist/structure/StructureExportPane.svelte +17 -21
  307. package/dist/structure/StructureInfoPane.svelte +72 -66
  308. package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
  309. package/dist/structure/StructureScene.svelte +467 -476
  310. package/dist/structure/StructureScene.svelte.d.ts +3 -1
  311. package/dist/structure/StructureViewport.svelte +386 -0
  312. package/dist/structure/StructureViewport.svelte.d.ts +63 -0
  313. package/dist/structure/atom-label-placement.d.ts +16 -12
  314. package/dist/structure/atom-label-placement.js +70 -37
  315. package/dist/structure/atom-properties.d.ts +2 -0
  316. package/dist/structure/atom-properties.js +104 -38
  317. package/dist/structure/bonding.d.ts +10 -3
  318. package/dist/structure/bonding.js +84 -28
  319. package/dist/structure/edit-history.d.ts +6 -0
  320. package/dist/structure/edit-history.js +27 -0
  321. package/dist/structure/export.d.ts +1 -0
  322. package/dist/structure/export.js +39 -31
  323. package/dist/structure/format-detect.d.ts +1 -0
  324. package/dist/structure/format-detect.js +22 -0
  325. package/dist/structure/geometry.d.ts +9 -0
  326. package/dist/structure/geometry.js +27 -0
  327. package/dist/structure/index.d.ts +16 -1
  328. package/dist/structure/index.js +13 -2
  329. package/dist/structure/measure.d.ts +1 -1
  330. package/dist/structure/measure.js +6 -12
  331. package/dist/structure/parse.d.ts +2 -1
  332. package/dist/structure/parse.js +243 -222
  333. package/dist/structure/partial-occupancy.js +4 -2
  334. package/dist/structure/pbc.js +78 -98
  335. package/dist/structure/polyhedra.d.ts +0 -1
  336. package/dist/structure/polyhedra.js +3 -18
  337. package/dist/structure/supercell.d.ts +1 -2
  338. package/dist/structure/supercell.js +7 -13
  339. package/dist/symmetry/SymmetryElements.svelte +11 -15
  340. package/dist/symmetry/SymmetryStats.svelte +56 -68
  341. package/dist/symmetry/WyckoffTable.svelte +5 -24
  342. package/dist/symmetry/cell-transform.js +1 -0
  343. package/dist/symmetry/index.d.ts +1 -0
  344. package/dist/symmetry/index.js +8 -2
  345. package/dist/symmetry/spacegroups.js +3 -2
  346. package/dist/table/HeatmapTable.svelte +1102 -365
  347. package/dist/table/HeatmapTable.svelte.d.ts +12 -2
  348. package/dist/table/ToggleMenu.svelte +5 -12
  349. package/dist/table/index.d.ts +21 -6
  350. package/dist/table/index.js +34 -21
  351. package/dist/theme/ThemeControl.svelte +16 -8
  352. package/dist/theme/index.d.ts +1 -5
  353. package/dist/theme/index.js +4 -10
  354. package/dist/theme/themes.mjs +14 -7
  355. package/dist/tooltip/KCoords.svelte +4 -1
  356. package/dist/tooltip/TooltipContent.svelte +2 -5
  357. package/dist/trajectory/Trajectory.svelte +515 -308
  358. package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
  359. package/dist/trajectory/TrajectoryError.svelte +11 -8
  360. package/dist/trajectory/TrajectoryExportPane.svelte +14 -31
  361. package/dist/trajectory/TrajectoryInfoPane.svelte +85 -97
  362. package/dist/trajectory/extract.js +56 -34
  363. package/dist/trajectory/format-detect.js +11 -9
  364. package/dist/trajectory/frame-reader.d.ts +2 -0
  365. package/dist/trajectory/frame-reader.js +33 -29
  366. package/dist/trajectory/helpers.d.ts +2 -0
  367. package/dist/trajectory/helpers.js +18 -7
  368. package/dist/trajectory/index.d.ts +1 -0
  369. package/dist/trajectory/index.js +3 -0
  370. package/dist/trajectory/parse/h5-utils.d.ts +13 -0
  371. package/dist/trajectory/parse/h5-utils.js +109 -0
  372. package/dist/trajectory/parse/hdf5.d.ts +1 -1
  373. package/dist/trajectory/parse/hdf5.js +116 -106
  374. package/dist/trajectory/parse/index.d.ts +2 -3
  375. package/dist/trajectory/parse/index.js +17 -20
  376. package/dist/trajectory/parse/lammps.js +8 -12
  377. package/dist/trajectory/parse/vasp.js +10 -7
  378. package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
  379. package/dist/trajectory/parse/vaspout-electronic.js +207 -0
  380. package/dist/trajectory/parse/vaspout-h5.d.ts +5 -0
  381. package/dist/trajectory/parse/vaspout-h5.js +284 -0
  382. package/dist/trajectory/parse/xyz.js +11 -7
  383. package/dist/trajectory/plotting.d.ts +8 -9
  384. package/dist/trajectory/plotting.js +99 -27
  385. package/dist/utils.d.ts +3 -0
  386. package/dist/utils.js +7 -0
  387. package/dist/xrd/XrdPlot.svelte +47 -61
  388. package/dist/xrd/broadening.js +2 -1
  389. package/dist/xrd/calc-xrd.js +2 -4
  390. package/dist/xrd/parse.d.ts +0 -1
  391. package/dist/xrd/parse.js +56 -73
  392. package/package.json +61 -33
  393. package/dist/api/mp.d.ts +0 -6
  394. package/dist/api/mp.js +0 -22
  395. package/dist/plot/sunburst/SunburstControls.svelte +0 -200
@@ -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,28 +376,31 @@
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
+ const legend_footprint = $derived(
402
+ measured_footprint(legend_element, { width: 120, height: 60 }),
403
+ )
417
404
  const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
418
405
 
419
406
  // Obstacle field in normalized [0,1] plot coords (y=0 at top). Each filled bar is modeled as a
@@ -451,66 +438,64 @@
451
438
  height,
452
439
  obstacles_norm,
453
440
  // 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
- })
441
+ legend:
442
+ show_legend &&
443
+ legend != null &&
444
+ series.length > 1 &&
445
+ legend_element != null &&
446
+ !legend_has_explicit_pos
447
+ ? { footprint: legend_footprint, clearance: legend?.axis_clearance }
448
+ : null,
449
+ }),
450
+ )
451
+ // Resolve marginals (default: CDF strip on top) and reserve outer-band padding. Pass the
452
+ // histogram's `bins` as the marginal's histogram bin count (NOT `size`/thickness) so a
453
+ // `histogram` marginal inherits the main binning via normalize_marginals' merge; `cdf`
454
+ // ignores `bins`. Samples lie along x and equal series.y, so the adapter maps y -> x.
455
+ const resolved_marginals = $derived(
456
+ normalize_marginals(marginals, { top: { type: `cdf`, bins } }),
457
+ )
458
+ const pad = $derived(add_sides(decor.pad, reserve_marginal_pad(resolved_marginals)))
459
+ // a lone series uses the configured bar color; with multiple, each gets its own
460
+ const series_color = (series_data: DataSeries) =>
461
+ selected_series.length === 1 ? final_bar.color : extract_series_color(series_data)
462
+ const marginal_series = $derived<MarginalSeriesInput[]>(
463
+ selected_series_entries.map(({ series_data }) => ({
464
+ x: series_data.y ?? [],
465
+ color: series_color(series_data),
466
+ label: series_data.label,
467
+ visible: true,
468
+ x_axis: series_data.x_axis,
469
+ y_axis: series_data.y_axis,
470
+ })),
471
+ )
472
+ const marginal_has_axis = $derived(
473
+ marginal_axis_presence(x2_series.length > 0, y2_series.length > 0),
459
474
  )
460
- const pad = $derived(decor.pad)
461
475
  const legend_auto_outside = $derived(decor.legend_outside)
462
476
  const legend_outside_x = $derived(decor.legend_pos.x)
463
477
  const legend_outside_y = $derived(decor.legend_pos.y)
464
478
 
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],
479
+ // Scales and data (x/x2 share the horizontal pixel span, y/y2 the inverted vertical one)
480
+ let scales = $derived(
481
+ create_axis_scales(
482
+ { x: final_x_axis, x2: final_x2_axis, y: final_y_axis, y2: final_y2_axis },
483
+ ranges.current,
484
+ pad,
485
+ width,
486
+ height,
486
487
  ),
487
- })
488
+ )
488
489
 
489
490
  // Pad-independent binning (no pixel scales) so the auto-place obstacle field can reuse it
490
491
  let histogram_bins = $derived.by(() => {
491
492
  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
- }
493
+ return compute_histogram_bins(selected_series_entries, {
494
+ x_domain: ranges.current.x,
495
+ x2_domain: ranges.current.x2,
496
+ has_x2: x2_series.length > 0,
497
+ bin_count: bins,
498
+ series_color,
514
499
  })
515
500
  })
516
501
  // Render-time data adds the pixel scales (pad-dependent)
@@ -522,43 +507,25 @@
522
507
  })),
523
508
  )
524
509
 
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
- : [],
510
+ let ticks = $derived.by(() => {
511
+ // x/y always render; x2/y2 only when their series exist (else their scale is a [0,1] sentinel)
512
+ const axis_ticks = (
513
+ axis: typeof final_x_axis,
514
+ range: Vec2,
515
+ scale: typeof scales.x,
516
+ default_count: number,
517
+ show = true,
518
+ ) =>
519
+ width && height && show
520
+ ? generate_ticks(range, axis.scale_type ?? `linear`, axis.ticks, scale, {
521
+ default_count,
522
+ })
523
+ : []
524
+ const x = axis_ticks(final_x_axis, ranges.current.x, scales.x, 8)
525
+ const x2 = axis_ticks(final_x2_axis, ranges.current.x2, scales.x2, 8, x2_series.length > 0)
526
+ const y = axis_ticks(final_y_axis, ranges.current.y, scales.y, 6)
527
+ const y2 = axis_ticks(final_y2_axis, ranges.current.y2, scales.y2, 6, y2_series.length > 0)
528
+ return { x, x2, y, y2 }
562
529
  })
563
530
 
564
531
  // Cache measured tick-label widths so expensive text measurement only runs
@@ -644,11 +611,13 @@
644
611
  x_axis = { ...x_axis, range: next_x }
645
612
  // gate x2/y2 on series presence: their scales are [0, 1] sentinels otherwise,
646
613
  // 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
614
+ const next_x2 =
615
+ x2_series.length > 0 ? invert_rect_range(scales.x2, start.x, current.x) : null
648
616
  if (next_x2) x2_axis = { ...x2_axis, range: next_x2 }
649
617
  const next_y = invert_rect_range(scales.y, start.y, current.y)
650
618
  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
619
+ const next_y2 =
620
+ y2_series.length > 0 ? invert_rect_range(scales.y2, start.y, current.y) : null
652
621
  if (next_y2) y2_axis = { ...y2_axis, range: next_y2 }
653
622
  },
654
623
  on_reset: () => {
@@ -698,7 +667,10 @@
698
667
  on_bar_hover?.({ value, count, property, event: evt })
699
668
  }
700
669
 
701
- const legend_vis = create_legend_visibility(() => series, (next) => (series = next))
670
+ const legend_vis = create_legend_visibility(
671
+ () => series,
672
+ (next) => (series = next),
673
+ )
702
674
 
703
675
  // Set theme-aware background when entering fullscreen
704
676
  $effect(() => {
@@ -706,31 +678,24 @@
706
678
  })
707
679
 
708
680
  // State accessors for shared axis change handler
681
+ // Spread into existing state in each setter to preserve merged type structure
709
682
  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
683
+ axes: {
684
+ x: { get: () => x_axis, set: (config) => (x_axis = { ...x_axis, ...config }) },
685
+ x2: { get: () => x2_axis, set: (config) => (x2_axis = { ...x2_axis, ...config }) },
686
+ y: { get: () => y_axis, set: (config) => (y_axis = { ...y_axis, ...config }) },
687
+ y2: { get: () => y2_axis, set: (config) => (y2_axis = { ...y2_axis, ...config }) },
715
688
  },
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 }
722
- },
723
- get_series: () => series,
724
- set_series: (new_series) => (series = new_series),
725
- get_loading: () => axis_loading,
726
- set_loading: (axis) => (axis_loading = axis),
689
+ series: { get: () => series, set: (next) => (series = next) },
690
+ loading: { get: () => axis_loading, set: (axis) => (axis_loading = axis) },
727
691
  }
728
692
 
729
693
  // 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
- )
694
+ const { handle_axis_change, try_auto_load } = create_axis_loader(axis_state, () => ({
695
+ data_loader,
696
+ on_axis_change,
697
+ on_error,
698
+ }))
734
699
  $effect(try_auto_load)
735
700
  </script>
736
701
 
@@ -795,8 +760,7 @@
795
760
  bind:this={svg_element}
796
761
  role="application"
797
762
  aria-label={rest[`aria-label`] ??
798
- ([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) ||
799
- `Histogram`)}
763
+ ([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) || `Histogram`)}
800
764
  tabindex="0"
801
765
  onfocusin={() => pan_zoom.set_focused(true)}
802
766
  onfocusout={() => pan_zoom.set_focused(false)}
@@ -863,17 +827,17 @@
863
827
  <!-- X-axis -->
864
828
  <PlotAxis
865
829
  side="x"
866
- ticks={ticks.x as number[]}
830
+ ticks={ticks.x}
867
831
  place={scales.x}
868
832
  axis={final_x_axis}
869
- domain={ranges.current.x as Vec2}
833
+ domain={ranges.current.x}
870
834
  {pad}
871
835
  {width}
872
836
  {height}
873
837
  show_grid={display.x_grid}
874
838
  tick_label={(tick) => get_tick_label(tick, final_x_axis.ticks)}
875
839
  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)}
840
+ label_y={height - pad.b + AXIS_TITLE_OFFSET + (final_x_axis.label_shift?.y ?? 0)}
877
841
  axis_loading={axis_loading === `x`}
878
842
  on_axis_change={(key) => handle_axis_change(`x`, key)}
879
843
  />
@@ -882,17 +846,17 @@
882
846
  {#if x2_series.length > 0}
883
847
  <PlotAxis
884
848
  side="x2"
885
- ticks={ticks.x2 as number[]}
849
+ ticks={ticks.x2}
886
850
  place={scales.x2}
887
851
  axis={final_x2_axis}
888
- domain={ranges.current.x2 as Vec2}
852
+ domain={ranges.current.x2}
889
853
  {pad}
890
854
  {width}
891
855
  {height}
892
856
  show_grid={display.x2_grid}
893
857
  tick_label={(tick) => get_tick_label(tick, final_x2_axis.ticks)}
894
858
  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))}
859
+ label_y={Math.max(12, pad.t - (final_x2_axis.label_shift?.y ?? AXIS_TITLE_OFFSET))}
896
860
  axis_loading={axis_loading === `x2`}
897
861
  on_axis_change={(key) => handle_axis_change(`x2`, key)}
898
862
  />
@@ -901,10 +865,10 @@
901
865
  <!-- Y-axis -->
902
866
  <PlotAxis
903
867
  side="y"
904
- ticks={ticks.y as number[]}
868
+ ticks={ticks.y}
905
869
  place={scales.y}
906
870
  axis={final_y_axis}
907
- domain={ranges.current.y as Vec2}
871
+ domain={ranges.current.y}
908
872
  {pad}
909
873
  {width}
910
874
  {height}
@@ -912,7 +876,8 @@
912
876
  tick_label={(tick) => get_tick_label(tick, final_y_axis.ticks)}
913
877
  label_x={Math.max(
914
878
  12,
915
- pad.l - (final_y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
879
+ pad.l -
880
+ (final_y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
916
881
  LABEL_GAP_DEFAULT,
917
882
  ) + (final_y_axis.label_shift?.x ?? 0)}
918
883
  label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y_axis.label_shift?.y ?? 0)}
@@ -924,10 +889,10 @@
924
889
  {#if y2_series.length > 0}
925
890
  <PlotAxis
926
891
  side="y2"
927
- ticks={ticks.y2 as number[]}
892
+ ticks={ticks.y2}
928
893
  place={scales.y2}
929
894
  axis={final_y2_axis}
930
- domain={ranges.current.y2 as Vec2}
895
+ domain={ranges.current.y2}
931
896
  {pad}
932
897
  {width}
933
898
  {height}
@@ -941,24 +906,19 @@
941
906
  {/if}
942
907
 
943
908
  <!-- 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
- }
909
+ {#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
910
  <g
950
911
  class="histogram-series"
951
912
  data-series-idx={series_idx}
952
913
  clip-path="url(#{clip_path_id})"
953
- opacity={hovered_legend_series_idx !== null &&
954
- hovered_legend_series_idx !== series_idx
914
+ opacity={hovered_legend_series_idx !== null && hovered_legend_series_idx !== series_idx
955
915
  ? 0.25
956
916
  : 1}
957
917
  >
958
918
  {#each bins as bin, bin_idx (bin_idx)}
959
919
  {@const bar_x = x_scale(bin.x0!)}
960
920
  {@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))}
921
+ {@const bar_height = Math.max(0, height - pad.b - y_scale(bin.length))}
962
922
  {@const bar_y = y_scale(bin.length)}
963
923
  {@const value = (bin.x0! + bin.x1!) / 2}
964
924
  {#if bar_height > 0}
@@ -978,22 +938,22 @@
978
938
  role="button"
979
939
  tabindex="0"
980
940
  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
- )}
941
+ handle_mouse_move(
942
+ evt,
943
+ value,
944
+ bin.length,
945
+ label,
946
+ (y_axis ?? `y1`) as `y1` | `y2`,
947
+ series_idx,
948
+ (srs_x_axis ?? `x1`) as `x1` | `x2`,
949
+ )}
990
950
  onmouseleave={() => {
991
951
  hover_info = null
992
952
  change(null)
993
953
  on_bar_hover?.(null)
994
954
  }}
995
955
  onclick={(event) =>
996
- on_bar_click?.({ value, count: bin.length, property: label, event })}
956
+ on_bar_click?.({ value, count: bin.length, property: label, event })}
997
957
  onkeydown={(event: KeyboardEvent) => {
998
958
  if ([`Enter`, ` `].includes(event.key)) {
999
959
  event.preventDefault()
@@ -1009,6 +969,23 @@
1009
969
 
1010
970
  <!-- Reference lines: above all -->
1011
971
  {@render ref_lines_layer(ref_lines_by_z.above_all)}
972
+
973
+ <!-- Marginal distribution strips -->
974
+ <PlotMarginals
975
+ marginals={resolved_marginals}
976
+ series={marginal_series}
977
+ {width}
978
+ {height}
979
+ {pad}
980
+ has_axis={marginal_has_axis}
981
+ axes={{
982
+ x1: marginal_axis(scales.x, ranges.current.x, final_x_axis),
983
+ x2: marginal_axis(scales.x2, ranges.current.x2, final_x2_axis),
984
+ y1: marginal_axis(scales.y, ranges.current.y, final_y_axis),
985
+ y2: marginal_axis(scales.y2, ranges.current.y2, final_y2_axis),
986
+ }}
987
+ id={clip_path_id}
988
+ />
1012
989
  </svg>
1013
990
 
1014
991
  <!-- Tooltip (outside SVG for proper HTML rendering) -->
@@ -1026,8 +1003,8 @@
1026
1003
  {#if tooltip}
1027
1004
  {@render tooltip({ ...hover_info, fullscreen })}
1028
1005
  {: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>
1006
+ <div>Value: {format_value_or_num(value, hover_info.x_axis.format)}</div>
1007
+ <div>Count: {format_value_or_num(count, hover_info.y_axis.format)}</div>
1031
1008
  {#if mode === `overlay`}<div>{property}</div>{/if}
1032
1009
  {/if}
1033
1010
  </PlotTooltip>
@@ -1067,27 +1044,27 @@
1067
1044
 
1068
1045
  {#if show_legend && legend != null && series.length > 1}
1069
1046
  {@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
- )}
1047
+ legend_auto_outside,
1048
+ { x: legend_outside_x, y: legend_outside_y },
1049
+ legend_placement,
1050
+ legend_tween.coords.current,
1051
+ { x: pad.l + 10, y: pad.t + 10 },
1052
+ )}
1076
1053
  <PlotLegend
1077
1054
  bind:root_element={legend_element}
1078
1055
  {...legend}
1079
1056
  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
- })}
1057
+ on_toggle={legend?.on_toggle ??
1058
+ ((series_idx: number) => {
1059
+ if (series_idx < 0 || series_idx >= series.length) return
1060
+ legend_vis.on_toggle(series_idx)
1061
+ on_series_toggle(series_idx)
1062
+ })}
1085
1063
  on_double_click={legend?.on_double_click ?? legend_vis.on_double_click}
1086
1064
  on_hover_change={legend_tween.set_locked}
1087
1065
  on_item_hover={(item) =>
1088
- (hovered_legend_series_idx = item != null && item.series_idx >= 0
1089
- ? item.series_idx
1090
- : null)}
1066
+ (hovered_legend_series_idx =
1067
+ item != null && item.series_idx >= 0 ? item.series_idx : null)}
1091
1068
  active_series_idx={hover_info?.series_idx ?? hovered_legend_series_idx}
1092
1069
  style={`
1093
1070
  position: absolute;
@@ -1153,7 +1130,9 @@
1153
1130
  .histogram :global(.pane-toggle),
1154
1131
  .histogram .header-controls {
1155
1132
  opacity: 0;
1156
- transition: opacity 0.2s, background-color 0.2s;
1133
+ transition:
1134
+ opacity 0.2s,
1135
+ background-color 0.2s;
1157
1136
  }
1158
1137
  .histogram:hover :global(.pane-toggle),
1159
1138
  .histogram:hover .header-controls,