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
@@ -3,7 +3,7 @@
3
3
  generics="Metadata extends Record<string, unknown> = Record<string, unknown>"
4
4
  >
5
5
  import type { D3InterpolateName } from '../../colors'
6
- import { format_value } from '../../labels'
6
+ import { format_value_or_num } from '../../labels'
7
7
  import { sanitize_html } from '../../sanitize'
8
8
  import { FullscreenToggle, set_fullscreen_bg } from '../../layout'
9
9
  import type { Point2D, Vec2 } from '../../math'
@@ -33,11 +33,19 @@
33
33
  compute_element_placement,
34
34
  PlotAxis,
35
35
  PlotLegend,
36
+ PlotMarginals,
36
37
  ReferenceLine,
37
38
  ScatterPoint,
38
39
  } from '..'
39
- import type { AxisChangeState } from '../core/axis-utils'
40
- import { create_axis_loader } from '../core/axis-utils'
40
+ import type { MarginalSeriesInput, MarginalsProp } from '../core/marginals'
41
+ import {
42
+ add_sides,
43
+ marginal_axis,
44
+ marginal_axis_presence,
45
+ normalize_marginals,
46
+ reserve_marginal_pad,
47
+ } from '../core/marginals'
48
+ import { type AxisChangeState, create_axis_loader } from '../core/axis-utils'
41
49
  import { create_placed_tween } from '../core/placed-tween.svelte'
42
50
  import { create_pan_zoom } from '../core/pan-zoom.svelte'
43
51
  import { create_legend_visibility } from '../core/utils/series-visibility'
@@ -49,13 +57,13 @@
49
57
  import type { IndexedRefLine } from '../core/reference-line'
50
58
  import { group_ref_lines_by_z, index_ref_lines } from '../core/reference-line'
51
59
  import {
60
+ create_axis_scales,
52
61
  create_color_scale,
53
- create_scale,
54
62
  create_size_scale,
55
63
  generate_ticks,
56
64
  get_tick_label,
57
65
  } from '../core/scales'
58
- import { DEFAULT_MARKERS } from '../core/types'
66
+ import { DEFAULT_MARKERS, SCALE_DEFAULTS } from '../core/types'
59
67
  import { DEFAULTS } from '../../settings'
60
68
  import { extent } from 'd3-array'
61
69
  import type { Snippet } from 'svelte'
@@ -71,7 +79,9 @@
71
79
  placed_coords,
72
80
  } from '../core/auto-place'
73
81
  import {
82
+ AXIS_TITLE_OFFSET,
74
83
  calc_auto_padding,
84
+ DEFAULT_PLOT_PADDING,
75
85
  filter_padding,
76
86
  LABEL_GAP_DEFAULT,
77
87
  y2_axis_label_x,
@@ -108,8 +118,8 @@
108
118
  y_axis = $bindable({}),
109
119
  y2_axis: y2_axis_prop = $bindable({}),
110
120
  display = $bindable(DEFAULTS.bar.display),
111
- range_padding = 0.05,
112
- padding = { t: 20, b: 60, l: 60, r: 20 },
121
+ range_padding = 0,
122
+ padding = DEFAULT_PLOT_PADDING,
113
123
  legend = {},
114
124
  show_legend,
115
125
  bar = {},
@@ -121,12 +131,8 @@
121
131
  on_bar_click,
122
132
  on_bar_hover,
123
133
  // Line marker props (matching ScatterPlot)
124
- color_scale = {
125
- type: `linear`,
126
- scheme: `interpolateViridis`,
127
- value_range: undefined,
128
- },
129
- size_scale = { type: `linear`, radius_range: [2, 10], value_range: undefined },
134
+ color_scale = SCALE_DEFAULTS.color,
135
+ size_scale = SCALE_DEFAULTS.size,
130
136
  point_tween,
131
137
  on_point_click,
132
138
  on_point_hover,
@@ -146,61 +152,65 @@
146
152
  on_axis_change,
147
153
  on_error,
148
154
  pan = {},
155
+ marginals = false,
149
156
  ...rest
150
- }: HTMLAttributes<HTMLDivElement> & BasePlotProps & PlotConfig & {
151
- series?: BarSeries<Metadata>[]
152
- // Component-specific props
153
- orientation?: Orientation
154
- mode?: BarMode
155
- legend?: LegendConfig | null
156
- show_legend?: boolean
157
- bar?: BarStyle
158
- line?: LineStyle
159
- tooltip?: Snippet<[BarHandlerProps<Metadata>]>
160
- user_content?: Snippet<[UserContentProps]>
161
- header_controls?: Snippet<
162
- [{ height: number; width: number; fullscreen: boolean }]
163
- >
164
- controls_extra?: Snippet<
165
- [{ orientation: Orientation; mode: BarMode } & Required<PlotConfig>]
166
- >
167
- change?: (data: BarHandlerProps<Metadata> | null) => void
168
- on_bar_click?: (
169
- data: BarHandlerProps<Metadata> & { event: MouseEvent | KeyboardEvent },
170
- ) => void
171
- on_bar_hover?: (
172
- data: (BarHandlerProps<Metadata> & { event: MouseEvent | FocusEvent | KeyboardEvent }) | null,
173
- ) => void
174
- // Line marker props (matching ScatterPlot)
175
- // Note: For line series with markers, BOTH on_bar_* AND on_point_* events fire.
176
- // Use on_point_* for marker-specific data (includes `point` with InternalPoint details)
177
- // or on_bar_* for backward compatibility with bar-style event handling.
178
- color_scale?: ColorScaleConfig | D3InterpolateName
179
- size_scale?: SizeScaleConfig
180
- point_tween?: TweenOptions<Point2D>
181
- on_point_click?: (
182
- data: LineMarkerHandlerProps & { event: MouseEvent | KeyboardEvent },
183
- ) => void
184
- on_point_hover?: (
185
- data:
186
- | (LineMarkerHandlerProps & {
187
- event: MouseEvent | FocusEvent | KeyboardEvent
188
- })
189
- | null,
190
- ) => void
191
- ref_lines?: RefLine[]
192
- on_ref_line_click?: (event: RefLineEvent) => void
193
- on_ref_line_hover?: (event: RefLineEvent | null) => void
194
- // Interactive axis props
195
- data_loader?: DataLoaderFn<Metadata, BarSeries<Metadata>>
196
- on_axis_change?: (
197
- axis: `x` | `x2` | `y` | `y2`,
198
- key: string,
199
- new_series: BarSeries<Metadata>[],
200
- ) => void
201
- on_error?: (error: AxisLoadError) => void
202
- pan?: PanConfig
203
- } = $props()
157
+ }: HTMLAttributes<HTMLDivElement> &
158
+ BasePlotProps &
159
+ PlotConfig & {
160
+ series?: BarSeries<Metadata>[]
161
+ // Component-specific props
162
+ orientation?: Orientation
163
+ mode?: BarMode
164
+ legend?: LegendConfig | null
165
+ show_legend?: boolean
166
+ bar?: BarStyle
167
+ line?: LineStyle
168
+ tooltip?: Snippet<[BarHandlerProps<Metadata>]>
169
+ user_content?: Snippet<[UserContentProps]>
170
+ header_controls?: Snippet<[{ height: number; width: number; fullscreen: boolean }]>
171
+ controls_extra?: Snippet<
172
+ [{ orientation: Orientation; mode: BarMode } & Required<PlotConfig>]
173
+ >
174
+ change?: (data: BarHandlerProps<Metadata> | null) => void
175
+ on_bar_click?: (
176
+ data: BarHandlerProps<Metadata> & { event: MouseEvent | KeyboardEvent },
177
+ ) => void
178
+ on_bar_hover?: (
179
+ data:
180
+ | (BarHandlerProps<Metadata> & { event: MouseEvent | FocusEvent | KeyboardEvent })
181
+ | null,
182
+ ) => void
183
+ // Line marker props (matching ScatterPlot)
184
+ // Note: For line series with markers, BOTH on_bar_* AND on_point_* events fire.
185
+ // Use on_point_* for marker-specific data (includes `point` with InternalPoint details)
186
+ // or on_bar_* for backward compatibility with bar-style event handling.
187
+ color_scale?: ColorScaleConfig | D3InterpolateName
188
+ size_scale?: SizeScaleConfig
189
+ point_tween?: TweenOptions<Point2D>
190
+ on_point_click?: (
191
+ data: LineMarkerHandlerProps & { event: MouseEvent | KeyboardEvent },
192
+ ) => void
193
+ on_point_hover?: (
194
+ data:
195
+ | (LineMarkerHandlerProps & {
196
+ event: MouseEvent | FocusEvent | KeyboardEvent
197
+ })
198
+ | null,
199
+ ) => void
200
+ ref_lines?: RefLine[]
201
+ on_ref_line_click?: (event: RefLineEvent) => void
202
+ on_ref_line_hover?: (event: RefLineEvent | null) => void
203
+ // Interactive axis props
204
+ data_loader?: DataLoaderFn<Metadata, BarSeries<Metadata>>
205
+ on_axis_change?: (
206
+ axis: `x` | `x2` | `y` | `y2`,
207
+ key: string,
208
+ new_series: BarSeries<Metadata>[],
209
+ ) => void
210
+ on_error?: (error: AxisLoadError) => void
211
+ pan?: PanConfig
212
+ marginals?: MarginalsProp
213
+ } = $props()
204
214
 
205
215
  // Initialize bar, line, y2_axis with defaults - using $derived for reactivity
206
216
  let bar_state = $derived({ ...DEFAULTS.bar.bar, ...bar })
@@ -220,7 +230,7 @@
220
230
  format: ``,
221
231
  scale_type: `linear`,
222
232
  ticks: 5,
223
- label_shift: { x: 0, y: 40 },
233
+ label_shift: { x: 0, y: AXIS_TITLE_OFFSET },
224
234
  tick: { label: { shift: { x: 0, y: 0 } } },
225
235
  range: [null, null],
226
236
  ...x2_axis_prop,
@@ -238,13 +248,12 @@
238
248
  let axis_loading = $state<`x` | `x2` | `y` | `y2` | null>(null)
239
249
 
240
250
  // Compute ref_lines with index and group by z-index (using shared utilities)
241
- let indexed_ref_lines = $derived(index_ref_lines(ref_lines))
242
- let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines))
251
+ let ref_lines_by_z = $derived(group_ref_lines_by_z(index_ref_lines(ref_lines)))
243
252
 
244
253
  // === Categorical Normalization (string x values -> integer indices, see ./data) ===
245
- let cat_norm = $derived(normalize_categorical(series, x_axis.categories))
246
- let category_list = $derived(cat_norm.category_list)
247
- let internal_series = $derived(cat_norm.internal_series)
254
+ let { category_list, internal_series } = $derived(
255
+ normalize_categorical(series, x_axis.categories),
256
+ )
248
257
 
249
258
  let category_indices = $derived(
250
259
  category_list.length > 0 ? category_list.map((_, idx) => idx) : null,
@@ -257,47 +266,44 @@
257
266
  const axis_px = (orientation === `horizontal` ? height : width) || 0
258
267
  const max_ticks = Math.max(1, Math.floor(axis_px / 28)) // ~28px per category label
259
268
  const step = Math.ceil(category_indices.length / max_ticks)
260
- return step <= 1
261
- ? category_indices
262
- : category_indices.filter((_, idx) => idx % step === 0)
269
+ return step <= 1 ? category_indices : category_indices.filter((_, idx) => idx % step === 0)
263
270
  })
264
271
 
265
272
  // Compute auto ranges from visible series
266
- let visible_series = $derived(
267
- internal_series.filter((srs) => srs?.visible ?? true),
268
- )
273
+ let visible_series = $derived(internal_series.filter((srs) => srs?.visible ?? true))
269
274
 
270
275
  // Separate series by y-axis
271
- let y1_series = $derived(
272
- visible_series.filter((srs) => (srs.y_axis ?? `y1`) === `y1`),
273
- )
274
- let y2_series = $derived(
275
- visible_series.filter((srs) => srs.y_axis === `y2`),
276
+ let y1_series = $derived(visible_series.filter((srs) => (srs.y_axis ?? `y1`) === `y1`))
277
+ let y2_series = $derived(visible_series.filter((srs) => srs.y_axis === `y2`))
278
+ let x2_series = $derived(visible_series.filter((srs) => srs.x_axis === `x2`))
279
+ // Whether the secondary x2 (top) / y2 (right) axis actually renders: BarPlot only supports
280
+ // them in vertical orientation. Derive once so ticks, padding, axis rendering, and marginal
281
+ // placement stay in sync. (Data-existence checks below use the bare `*_series.length` instead.)
282
+ let show_x2 = $derived(x2_series.length > 0 && orientation === `vertical`)
283
+ let show_y2 = $derived(y2_series.length > 0 && orientation === `vertical`)
284
+
285
+ let auto_ranges = $derived(
286
+ compute_bar_auto_ranges({
287
+ visible_series,
288
+ y1_series,
289
+ y2_series,
290
+ x2_series,
291
+ mode,
292
+ orientation,
293
+ range_padding,
294
+ category_count: category_list.length,
295
+ x_range: x_axis.range ?? [null, null],
296
+ x_scale_type: x_axis.scale_type ?? `linear`,
297
+ x_is_time: x_axis.format?.startsWith(`%`) || false,
298
+ x2_range: x2_axis.range ?? [null, null],
299
+ x2_scale_type: x2_axis.scale_type ?? `linear`,
300
+ x2_is_time: x2_axis.format?.startsWith(`%`) || false,
301
+ y_range: y_axis.range ?? [null, null],
302
+ y_scale_type: y_axis.scale_type ?? `linear`,
303
+ y2_range: y2_axis.range ?? [null, null],
304
+ y2_scale_type: y2_axis.scale_type ?? `linear`,
305
+ }),
276
306
  )
277
- let x2_series = $derived(
278
- visible_series.filter((srs) => srs.x_axis === `x2`),
279
- )
280
-
281
- let auto_ranges = $derived(compute_bar_auto_ranges({
282
- visible_series,
283
- y1_series,
284
- y2_series,
285
- x2_series,
286
- mode,
287
- orientation,
288
- range_padding,
289
- category_count: category_list.length,
290
- x_range: x_axis.range ?? [null, null],
291
- x_scale_type: x_axis.scale_type ?? `linear`,
292
- x_is_time: x_axis.format?.startsWith(`%`) || false,
293
- x2_range: x2_axis.range ?? [null, null],
294
- x2_scale_type: x2_axis.scale_type ?? `linear`,
295
- x2_is_time: x2_axis.format?.startsWith(`%`) || false,
296
- y_range: y_axis.range ?? [null, null],
297
- y_scale_type: y_axis.scale_type ?? `linear`,
298
- y2_range: y2_axis.range ?? [null, null],
299
- y2_scale_type: y2_axis.scale_type ?? `linear`,
300
- }))
301
307
 
302
308
  // Initialize and current ranges
303
309
  let ranges = $state<{
@@ -308,10 +314,14 @@
308
314
  current: { x: [0, 1], x2: [0, 1], y: [0, 1], y2: [0, 1] },
309
315
  })
310
316
 
311
- $effect(() => { // handle x_axis.range / x2_axis.range / y_axis.range / y2_axis.range changes
317
+ $effect(() => {
318
+ // handle x_axis.range / x2_axis.range / y_axis.range / y2_axis.range changes
312
319
  // resolve_axis_ranges returns null for transient non-finite bounds (skip: writing
313
320
  // NaN breaks scales and, since NaN !== NaN, loops the effect)
314
- const next = resolve_axis_ranges({ x: x_axis, x2: x2_axis, y: y_axis, y2: y2_axis }, auto_ranges)
321
+ const next = resolve_axis_ranges(
322
+ { x: x_axis, x2: x2_axis, y: y_axis, y2: y2_axis },
323
+ auto_ranges,
324
+ )
315
325
  if (!next) return
316
326
  // Only update if the initial (data-driven) ranges changed, not when user pans.
317
327
  // untrack the read of `ranges` so the assignment below can't re-trigger this effect
@@ -323,42 +333,33 @@
323
333
  })
324
334
 
325
335
  // Layout: dynamic padding based on tick label widths
326
- const default_padding = { t: 20, b: 60, l: 60, r: 20 }
327
336
  // base_pad reserves space for tick labels/axis titles; pad (below) adds decoration reservations
328
- let base_pad = $derived(filter_padding(padding, default_padding))
337
+ let base_pad = $derived(filter_padding(padding, DEFAULT_PLOT_PADDING))
329
338
 
330
339
  // Update padding when format or ticks change
331
340
  $effect(() => {
332
- const new_pad = width && height && ticks.y.length > 0
333
- ? calc_auto_padding({
334
- padding,
335
- default_padding,
336
- x2_axis: { ...x2_axis, tick_values: ticks.x2 },
337
- y_axis: { ...y_axis, tick_values: ticks.y },
338
- y2_axis: { ...y2_axis, tick_values: ticks.y2 },
339
- })
340
- : filter_padding(padding, default_padding)
341
+ const new_pad =
342
+ width && height && ticks.y.length > 0
343
+ ? calc_auto_padding({
344
+ padding,
345
+ default_padding: DEFAULT_PLOT_PADDING,
346
+ x2_axis: { ...x2_axis, tick_values: ticks.x2 },
347
+ y_axis: { ...y_axis, tick_values: ticks.y },
348
+ y2_axis: { ...y2_axis, tick_values: ticks.y2 },
349
+ })
350
+ : filter_padding(padding, DEFAULT_PLOT_PADDING)
341
351
  // Expand right padding if y2 ticks are shown (only for vertical orientation)
342
- if (
343
- width && height && y2_series.length > 0 && ticks.y2.length > 0 &&
344
- orientation === `vertical`
345
- ) {
352
+ if (width && height && show_y2 && ticks.y2.length > 0) {
346
353
  // Need space for: tick shift + tick width + gap (30px) + label space (20px if present)
347
354
  // When ticks are inside, they don't contribute to padding
348
355
  const inside = y2_axis.tick?.label?.inside ?? false
349
356
  const tick_shift = inside ? 0 : (y2_axis.tick?.label?.shift?.x ?? 0) + 8
350
357
  const tick_width_contribution = inside ? 0 : tick_label_widths.y2_max
351
358
  const label_space = y2_axis.label ? 20 : 0
352
- new_pad.r = Math.max(
353
- new_pad.r,
354
- tick_shift + tick_width_contribution + 30 + label_space,
355
- )
359
+ new_pad.r = Math.max(new_pad.r, tick_shift + tick_width_contribution + 30 + label_space)
356
360
  }
357
361
  // Expand top padding if x2 ticks are shown (only for vertical orientation)
358
- if (
359
- width && height && x2_series.length > 0 && ticks.x2.length > 0 &&
360
- orientation === `vertical`
361
- ) {
362
+ if (width && height && show_x2 && ticks.x2.length > 0) {
362
363
  const inside = x2_axis.tick?.label?.inside ?? false
363
364
  const tick_shift = inside ? 0 : Math.abs(x2_axis.tick?.label?.shift?.y ?? 0) + 5
364
365
  const tick_height = inside ? 0 : 16
@@ -368,14 +369,22 @@
368
369
 
369
370
  // Only update if padding actually changed (prevents infinite loop)
370
371
  if (
371
- base_pad.t !== new_pad.t || base_pad.b !== new_pad.b ||
372
- base_pad.l !== new_pad.l || base_pad.r !== new_pad.r
373
- ) base_pad = new_pad
372
+ base_pad.t !== new_pad.t ||
373
+ base_pad.b !== new_pad.b ||
374
+ base_pad.l !== new_pad.l ||
375
+ base_pad.r !== new_pad.r
376
+ )
377
+ base_pad = new_pad
374
378
  })
375
379
 
376
380
  let legend_element = $state<HTMLDivElement | undefined>()
377
- const legend_footprint = $derived(measured_footprint(legend_element, { width: 120, height: 60 }))
381
+ let legend_size_revision = $state(0)
382
+ const legend_footprint = $derived.by(() => {
383
+ void legend_size_revision
384
+ return measured_footprint(legend_element, { width: 120, height: 60 })
385
+ })
378
386
  const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
387
+ const should_show_legend = $derived(show_legend ?? series.length > 1)
379
388
 
380
389
  // Obstacle field in normalized [0,1] plot coords (y=0 at top). Each bar is modeled as a segment
381
390
  // from baseline to its tip so the legend can't hide inside a tall bar. Built from internal_series
@@ -403,8 +412,18 @@
403
412
  const value = base + srs.y[bar_idx]
404
413
  // vertical: category on x, value rises on y (inverted). horizontal: category on y, value on x
405
414
  const seg = vertical
406
- ? clip_bar(true, (x_val - ax0) / x_span, 1 - (value - vy0) / y_span, 1 - (base - vy0) / y_span)
407
- : clip_bar(false, 1 - (x_val - cy0) / cy_span, (value - ax0) / x_span, (base - ax0) / x_span)
415
+ ? clip_bar(
416
+ true,
417
+ (x_val - ax0) / x_span,
418
+ 1 - (value - vy0) / y_span,
419
+ 1 - (base - vy0) / y_span,
420
+ )
421
+ : clip_bar(
422
+ false,
423
+ 1 - (x_val - cy0) / cy_span,
424
+ (value - ax0) / x_span,
425
+ (base - ax0) / x_span,
426
+ )
408
427
  if (seg) bars.push(seg)
409
428
  })
410
429
  })
@@ -412,46 +431,62 @@
412
431
  })
413
432
 
414
433
  // Move the legend to the bottom margin when no interior spot avoids the bars
415
- const decor = $derived.by(() =>
434
+ const decor = $derived(
416
435
  place_decorations({
417
436
  base_pad,
418
437
  width,
419
438
  height,
420
439
  obstacles_norm,
421
440
  // gate on legend_element (the render signal) not legend_data, whose entries can read pad
422
- legend: legend != null &&
423
- (show_legend !== undefined ? show_legend : series.length > 1) &&
424
- legend_element != null && !legend_has_explicit_pos
425
- ? { footprint: legend_footprint, clearance: legend?.axis_clearance }
426
- : null,
427
- })
441
+ legend:
442
+ legend != null &&
443
+ should_show_legend &&
444
+ legend_element != null &&
445
+ !legend_has_explicit_pos
446
+ ? { footprint: legend_footprint, clearance: legend?.axis_clearance }
447
+ : null,
448
+ }),
449
+ )
450
+ // Resolve marginals: a cumulative/Pareto CDF over the CATEGORY axis weighted by bar height.
451
+ // Categories sit on x (vertical) or y (horizontal), so the default side and the value array
452
+ // flip with orientation. The value axis carries no marginal (a bar's height isn't a sample).
453
+ const marginal_is_vertical = $derived(orientation === `vertical`)
454
+ const resolved_marginals = $derived(
455
+ normalize_marginals(
456
+ marginals,
457
+ marginal_is_vertical ? { top: { type: `cdf` } } : { right: { type: `cdf` } },
458
+ ),
428
459
  )
429
- const pad = $derived(decor.pad)
430
- const legend_auto_outside = $derived(decor.legend_outside)
431
- const legend_outside_x = $derived(decor.legend_pos.x)
432
- const legend_outside_y = $derived(decor.legend_pos.y)
460
+ const pad = $derived(add_sides(decor.pad, reserve_marginal_pad(resolved_marginals)))
461
+ const marginal_series = $derived<MarginalSeriesInput[]>(
462
+ internal_series.map((srs) => ({
463
+ x: marginal_is_vertical ? (srs?.x ?? []) : undefined,
464
+ y: marginal_is_vertical ? undefined : (srs?.x ?? []),
465
+ // magnitude weights so negative bars still yield a monotonic cumulative (CDF) marginal
466
+ weight: srs?.y?.map((value) => Math.abs(value)) ?? [],
467
+ color:
468
+ srs?.color ??
469
+ (srs?.render_mode === `line` ? line_state.color : bar_state.color) ??
470
+ `steelblue`,
471
+ label: srs?.label,
472
+ visible: srs?.visible ?? true,
473
+ x_axis: srs?.x_axis,
474
+ y_axis: srs?.y_axis,
475
+ })),
476
+ )
477
+ const marginal_has_axis = $derived(marginal_axis_presence(show_x2, show_y2))
433
478
  const chart_width = $derived(Math.max(1, width - pad.l - pad.r))
434
479
  const chart_height = $derived(Math.max(1, height - pad.t - pad.b))
435
480
 
436
- // Scales
437
- let scales = $derived({
438
- x: create_scale(x_axis.scale_type ?? `linear`, ranges.current.x, [
439
- pad.l,
440
- width - pad.r,
441
- ]),
442
- x2: create_scale(x2_axis.scale_type ?? `linear`, ranges.current.x2, [
443
- pad.l,
444
- width - pad.r,
445
- ]),
446
- y: create_scale(y_axis.scale_type ?? `linear`, ranges.current.y, [
447
- height - pad.b,
448
- pad.t,
449
- ]),
450
- y2: create_scale(y2_axis.scale_type ?? `linear`, ranges.current.y2, [
451
- height - pad.b,
452
- pad.t,
453
- ]),
454
- })
481
+ let scales = $derived(
482
+ create_axis_scales(
483
+ { x: x_axis, x2: x2_axis, y: y_axis, y2: y2_axis },
484
+ ranges.current,
485
+ pad,
486
+ width,
487
+ height,
488
+ ),
489
+ )
455
490
 
456
491
  // Compute plot center for point tweening origin
457
492
  let plot_center_x = $derived(pad.l + (width - pad.r - pad.l) / 2)
@@ -462,9 +497,7 @@
462
497
  visible_series
463
498
  .filter((srs: BarSeries<Metadata>) => srs.render_mode === `line`)
464
499
  .flatMap((srs: BarSeries<Metadata>) =>
465
- (srs.color_values ?? []).filter(
466
- (val): val is number => typeof val === `number`,
467
- )
500
+ (srs.color_values ?? []).filter((val): val is number => typeof val === `number`),
468
501
  ),
469
502
  )
470
503
 
@@ -480,9 +513,7 @@
480
513
  visible_series
481
514
  .filter((srs: BarSeries<Metadata>) => srs.render_mode === `line`)
482
515
  .flatMap((srs: BarSeries<Metadata>) =>
483
- [...(srs.size_values ?? [])].filter(
484
- (val): val is number => typeof val === `number`,
485
- )
516
+ [...(srs.size_values ?? [])].filter((val): val is number => typeof val === `number`),
486
517
  ),
487
518
  )
488
519
 
@@ -500,57 +531,37 @@
500
531
  // Only respect user ticks when they're a Record (custom label mapping),
501
532
  // not a number (tick count) or array (tick positions)
502
533
  const user_ticks = cat_axis === `x` ? x_axis.ticks : y_axis.ticks
503
- if (
504
- user_ticks != null && typeof user_ticks === `object` &&
505
- !Array.isArray(user_ticks)
506
- ) return user_ticks
507
- return Object.fromEntries(
508
- category_list.map((cat, idx) => [idx, cat]),
509
- ) as Record<number, string>
534
+ if (user_ticks != null && typeof user_ticks === `object` && !Array.isArray(user_ticks))
535
+ return user_ticks
536
+ return Object.fromEntries(category_list.map((cat, idx): [number, string] => [idx, cat]))
510
537
  })
511
538
 
512
- // Ticks
513
- let ticks = $derived({
514
- x: width && height
515
- ? (category_indices && cat_axis === `x` ? cat_tick_indices : generate_ticks(
516
- ranges.current.x,
517
- x_axis.scale_type ?? `linear`,
518
- x_axis.ticks,
519
- scales.x,
520
- { default_count: 8 },
521
- ))
522
- : [],
523
- y: width && height
524
- ? (category_indices && cat_axis === `y` ? cat_tick_indices : generate_ticks(
525
- ranges.current.y,
526
- y_axis.scale_type ?? `linear`,
527
- y_axis.ticks,
528
- scales.y,
529
- { default_count: 6 },
530
- ))
531
- : [],
532
- y2: width && height && y2_series.length > 0 && orientation === `vertical`
533
- ? generate_ticks(
534
- ranges.current.y2,
535
- y2_axis.scale_type ?? `linear`,
536
- y2_axis.ticks,
537
- scales.y2,
538
- {
539
- default_count: 6,
540
- },
541
- )
542
- : [],
543
- x2: width && height && x2_series.length > 0 && orientation === `vertical`
544
- ? generate_ticks(
545
- ranges.current.x2,
546
- x2_axis.scale_type ?? `linear`,
547
- x2_axis.ticks,
548
- scales.x2,
549
- {
550
- default_count: 8,
551
- },
552
- )
553
- : [],
539
+ let ticks = $derived.by(() => {
540
+ const axis_ticks = (
541
+ axis: typeof x_axis,
542
+ range: Vec2,
543
+ scale: typeof scales.x,
544
+ default_count: number,
545
+ show = true,
546
+ ) =>
547
+ width && height && show
548
+ ? generate_ticks(range, axis.scale_type ?? `linear`, axis.ticks, scale, {
549
+ default_count,
550
+ })
551
+ : []
552
+ // categorical axes show one tick per category instead of generated numeric ticks
553
+ return {
554
+ x:
555
+ category_indices && cat_axis === `x` && width && height
556
+ ? cat_tick_indices
557
+ : axis_ticks(x_axis, ranges.current.x, scales.x, 8),
558
+ y:
559
+ category_indices && cat_axis === `y` && width && height
560
+ ? cat_tick_indices
561
+ : axis_ticks(y_axis, ranges.current.y, scales.y, 6),
562
+ y2: axis_ticks(y2_axis, ranges.current.y2, scales.y2, 6, show_y2),
563
+ x2: axis_ticks(x2_axis, ranges.current.x2, scales.x2, 8, show_x2),
564
+ }
554
565
  })
555
566
 
556
567
  // Cache measured tick-label widths so expensive canvas text measurement
@@ -558,14 +569,19 @@
558
569
  let tick_label_widths = $derived({
559
570
  y_max: measure_max_tick_width(ticks.y, y_axis.format ?? ``),
560
571
  y2_max: measure_max_tick_width(ticks.y2, y2_axis.format ?? ``),
561
- x2_max: measure_max_tick_width(ticks.x2, x2_axis.format ?? ``),
562
572
  })
563
573
 
564
574
  // Shared pan/zoom/touch/drag-rect interaction controller
565
575
  const pan_zoom = create_pan_zoom({
566
576
  ranges: () => ranges.current,
567
- scale_type: (axis) => ({ x: x_axis, x2: x2_axis, y: y_axis, y2: y2_axis })[axis].scale_type,
568
- plot_dims: () => ({ width: chart_width, height: chart_height }),
577
+ scale_type: (axis) =>
578
+ ({ x: x_axis, x2: x2_axis, y: y_axis, y2: y2_axis })[axis].scale_type,
579
+ plot_bounds: () => ({
580
+ x: pad.l,
581
+ y: pad.t,
582
+ width: chart_width,
583
+ height: chart_height,
584
+ }),
569
585
  pan: () => pan,
570
586
  set_range: (axis, range) => (ranges.current[axis] = range),
571
587
  svg: () => svg_element,
@@ -574,13 +590,13 @@
574
590
  const next_x = invert_rect_range(scales.x, start.x, current.x)
575
591
  if (!next_x) return
576
592
  x_axis = { ...x_axis, range: next_x }
577
- // gate x2/y2 on series presence: their scales are [0, 1] sentinels otherwise,
578
- // so inverting would store a phantom range in the bindable prop
579
- const next_x2 = x2_series.length > 0 ? invert_rect_range(scales.x2, start.x, current.x) : null
593
+ // gate x2/y2 on whether they actually render (show_x2/show_y2 also require vertical);
594
+ // otherwise their [0, 1] sentinel scales would store a phantom range in the bindable prop
595
+ const next_x2 = show_x2 ? invert_rect_range(scales.x2, start.x, current.x) : null
580
596
  if (next_x2) x2_axis_prop = { ...x2_axis_prop, range: next_x2 }
581
597
  const next_y = invert_rect_range(scales.y, start.y, current.y)
582
598
  if (next_y) y_axis = { ...y_axis, range: next_y }
583
- const next_y2 = y2_series.length > 0 ? invert_rect_range(scales.y2, start.y, current.y) : null
599
+ const next_y2 = show_y2 ? invert_rect_range(scales.y2, start.y, current.y) : null
584
600
  if (next_y2) y2_axis_prop = { ...y2_axis_prop, range: next_y2 }
585
601
  },
586
602
  on_reset: () => {
@@ -606,8 +622,7 @@
606
622
  const is_line = srs.render_mode === `line`
607
623
  const series_markers = srs.markers ?? DEFAULT_MARKERS
608
624
  const has_line = series_markers === `line` || series_markers === `line+points`
609
- const has_points = series_markers === `points` ||
610
- series_markers === `line+points`
625
+ const has_points = series_markers === `points` || series_markers === `line+points`
611
626
  const series_color = srs.color ?? (is_line ? line_state.color : bar_state.color)
612
627
 
613
628
  // Get point style for symbol color (handle array or single object)
@@ -615,9 +630,10 @@
615
630
  ? srs.point_style[0]
616
631
  : srs.point_style
617
632
  const first_color_value = srs.color_values?.[0]
618
- const point_color = first_color_value != null
619
- ? color_scale_fn(first_color_value)
620
- : first_point_style?.fill ?? series_color
633
+ const point_color =
634
+ first_color_value != null
635
+ ? color_scale_fn(first_color_value)
636
+ : (first_point_style?.fill ?? series_color)
621
637
 
622
638
  if (is_line) {
623
639
  // Line series: show line and/or symbol based on markers
@@ -629,16 +645,15 @@
629
645
  display_style: {
630
646
  ...(has_line
631
647
  ? {
632
- line_color: series_color,
633
- line_dash: srs.line_style?.line_dash,
634
- }
648
+ line_color: series_color,
649
+ line_dash: srs.line_style?.line_dash,
650
+ }
635
651
  : {}),
636
652
  ...(has_points
637
653
  ? {
638
- symbol_type: first_point_style?.symbol_type ??
639
- DEFAULTS.scatter.symbol_type,
640
- symbol_color: point_color,
641
- }
654
+ symbol_type: first_point_style?.symbol_type ?? DEFAULTS.scatter.symbol_type,
655
+ symbol_color: point_color,
656
+ }
642
657
  : {}),
643
658
  },
644
659
  }
@@ -654,10 +669,13 @@
654
669
  symbol_color: series_color,
655
670
  },
656
671
  }
657
- })
672
+ }),
658
673
  )
659
674
 
660
- const legend_vis = create_legend_visibility(() => series, (next) => (series = next))
675
+ const legend_vis = create_legend_visibility(
676
+ () => series,
677
+ (next) => (series = next),
678
+ )
661
679
 
662
680
  // Collect bar and line positions for legend placement
663
681
  let bar_points_for_placement = $derived.by(() => {
@@ -674,12 +692,11 @@
674
692
  return srs.x
675
693
  .map((x_val, bar_idx) => {
676
694
  const y_val = srs.y[bar_idx]
677
- const base = !is_line && mode === `stacked`
678
- ? (series_offsets[bar_idx] ?? 0)
679
- : 0
680
- const [bar_x, bar_y] = orientation === `vertical`
681
- ? [x_scale_pl(x_val), y_scale(base + y_val)]
682
- : [x_scale_pl(base + y_val), scales.y(x_val)]
695
+ const base = !is_line && mode === `stacked` ? (series_offsets[bar_idx] ?? 0) : 0
696
+ const [bar_x, bar_y] =
697
+ orientation === `vertical`
698
+ ? [x_scale_pl(x_val), y_scale(base + y_val)]
699
+ : [x_scale_pl(base + y_val), scales.y(x_val)]
683
700
  return { x: bar_x, y: bar_y }
684
701
  })
685
702
  .filter(({ x, y }) => isFinite(x) && isFinite(y))
@@ -690,11 +707,10 @@
690
707
  let hovered_legend_series_idx = $state<number | null>(null)
691
708
 
692
709
  // Calculate best legend placement using continuous grid sampling
693
- let legend_placement = $derived.by(() => {
694
- const should_show = show_legend !== undefined ? show_legend : series.length > 1
695
- if (!should_show || !width || !height) return null
710
+ const get_legend_placement = () => {
711
+ if (!should_show_legend || !width || !height) return null
696
712
 
697
- const result = compute_element_placement({
713
+ return compute_element_placement({
698
714
  plot_bounds: { x: pad.l, y: pad.t, width: chart_width, height: chart_height },
699
715
  element: legend_element,
700
716
  element_size: { width: 120, height: 60 }, // fallback before first render
@@ -702,17 +718,16 @@
702
718
  exclude_rects: [],
703
719
  points: bar_points_for_placement,
704
720
  })
705
-
706
- return result
707
- })
721
+ }
708
722
 
709
723
  // Tweened legend coordinates with shared placement stability gating
710
724
  const legend_tween = create_placed_tween({
711
- placement: () => legend_placement,
725
+ placement: get_legend_placement,
712
726
  dims: () => ({ width, height }),
713
727
  responsive: () => legend?.responsive ?? false,
714
728
  element: () => legend_element,
715
729
  tween: () => legend?.tween,
730
+ on_element_resize: () => (legend_size_revision += 1),
716
731
  })
717
732
 
718
733
  // Tooltip state
@@ -726,9 +741,7 @@
726
741
  const srs = internal_series[series_idx]
727
742
  const [x, y] = [srs.x[bar_idx], srs.y[bar_idx]]
728
743
  const [orient_x, orient_y] = orientation === `horizontal` ? [y, x] : [x, y]
729
- const metadata = Array.isArray(srs.metadata)
730
- ? srs.metadata[bar_idx]
731
- : srs.metadata
744
+ const metadata = Array.isArray(srs.metadata) ? srs.metadata[bar_idx] : srs.metadata
732
745
  const label = srs.labels?.[bar_idx] ?? null
733
746
  const active_y_axis = srs.y_axis ?? `y1`
734
747
  const active_x_axis = srs.x_axis ?? `x1`
@@ -783,7 +796,7 @@
783
796
  const line_point_fill = (pt: LineSeriesPoint, series_color: string): string =>
784
797
  pt.color_value != null
785
798
  ? color_scale_fn(pt.color_value)
786
- : pt.point_style?.fill ?? series_color
799
+ : (pt.point_style?.fill ?? series_color)
787
800
 
788
801
  const handle_bar_hover =
789
802
  (series_idx: number, bar_idx: number, color: string) => (event: MouseEvent) => {
@@ -793,6 +806,12 @@
793
806
  on_bar_hover?.({ ...hover_info, event })
794
807
  }
795
808
 
809
+ const clear_hover = () => {
810
+ hover_info = null
811
+ change(null)
812
+ on_bar_hover?.(null)
813
+ }
814
+
796
815
  // Stack offsets (only for bar series in stacked mode, grouped by y-axis)
797
816
  let stacked_offsets = $derived(compute_stacked_offsets(internal_series, mode))
798
817
 
@@ -805,31 +824,31 @@
805
824
  })
806
825
 
807
826
  // State accessors for shared axis change handler
827
+ // Secondary axes read the merged $derived (x2_axis/y2_axis) but write the raw $bindable props
828
+ // (x2_axis_prop/y2_axis_prop) so library defaults aren't pushed into the parent's bound state
808
829
  const axis_state: AxisChangeState<BarSeries<Metadata>> = {
809
- get_axis: (axis) => {
810
- if (axis === `x`) return x_axis
811
- if (axis === `x2`) return x2_axis
812
- if (axis === `y`) return y_axis
813
- return y2_axis
830
+ axes: {
831
+ x: { get: () => x_axis, set: (config) => (x_axis = { ...x_axis, ...config }) },
832
+ x2: {
833
+ get: () => x2_axis,
834
+ set: (config) => (x2_axis_prop = { ...x2_axis_prop, ...config }),
835
+ },
836
+ y: { get: () => y_axis, set: (config) => (y_axis = { ...y_axis, ...config }) },
837
+ y2: {
838
+ get: () => y2_axis,
839
+ set: (config) => (y2_axis_prop = { ...y2_axis_prop, ...config }),
840
+ },
814
841
  },
815
- set_axis: (axis, config) => {
816
- // Spread into existing state to preserve merged type structure
817
- if (axis === `x`) x_axis = { ...x_axis, ...config }
818
- else if (axis === `x2`) x2_axis_prop = { ...x2_axis_prop, ...config }
819
- else if (axis === `y`) y_axis = { ...y_axis, ...config }
820
- else y2_axis_prop = { ...y2_axis_prop, ...config }
821
- },
822
- get_series: () => series,
823
- set_series: (new_series) => (series = new_series),
824
- get_loading: () => axis_loading,
825
- set_loading: (axis) => (axis_loading = axis),
842
+ series: { get: () => series, set: (next) => (series = next) },
843
+ loading: { get: () => axis_loading, set: (axis) => (axis_loading = axis) },
826
844
  }
827
845
 
828
846
  // Shared handler + one-shot auto-load bound to this component's state
829
- const { handle_axis_change, try_auto_load } = create_axis_loader(
830
- axis_state,
831
- () => ({ data_loader, on_axis_change, on_error }),
832
- )
847
+ const { handle_axis_change, try_auto_load } = create_axis_loader(axis_state, () => ({
848
+ data_loader,
849
+ on_axis_change,
850
+ on_error,
851
+ }))
833
852
  $effect(try_auto_load)
834
853
  </script>
835
854
 
@@ -877,7 +896,7 @@
877
896
  bind:clientWidth={width}
878
897
  bind:clientHeight={height}
879
898
  {...rest}
880
- class="bar-plot {rest.class ?? ``}"
899
+ class={[`bar-plot`, rest.class]}
881
900
  class:fullscreen
882
901
  >
883
902
  {#if width && height}
@@ -893,7 +912,7 @@
893
912
  bind:this={svg_element}
894
913
  role="application"
895
914
  aria-label={rest[`aria-label`] ??
896
- ([x_axis.label, y_axis.label].filter(Boolean).join(` vs `) || `Bar chart`)}
915
+ ([x_axis.label, y_axis.label].filter(Boolean).join(` vs `) || `Bar chart`)}
897
916
  tabindex="0"
898
917
  onfocusin={() => pan_zoom.set_focused(true)}
899
918
  onfocusout={() => pan_zoom.set_focused(false)}
@@ -902,9 +921,7 @@
902
921
  onkeydown={pan_zoom.on_key_down}
903
922
  onmouseleave={() => {
904
923
  hovered = false
905
- hover_info = null
906
- change(null)
907
- on_bar_hover?.(null)
924
+ clear_hover()
908
925
  }}
909
926
  onwheel={pan_zoom.on_wheel}
910
927
  ontouchstart={pan_zoom.on_touch_start}
@@ -937,38 +954,37 @@
937
954
  <!-- X-axis -->
938
955
  <PlotAxis
939
956
  side="x"
940
- ticks={ticks.x as number[]}
957
+ ticks={ticks.x}
941
958
  place={scales.x}
942
959
  axis={x_axis}
943
- domain={ranges.current.x as Vec2}
960
+ domain={ranges.current.x}
944
961
  {pad}
945
962
  {width}
946
963
  {height}
947
964
  show_grid={display.x_grid}
948
965
  tick_label={(tick) =>
949
- get_tick_label(tick, cat_axis === `x` ? effective_cat_ticks : x_axis.ticks)}
966
+ get_tick_label(tick, cat_axis === `x` ? effective_cat_ticks : x_axis.ticks)}
950
967
  label_x={pad.l + chart_width / 2 + (x_axis.label_shift?.x ?? 0)}
951
- label_y={height - pad.b / 3 + (x_axis.label_shift?.y ?? 0)}
968
+ label_y={height - pad.b + AXIS_TITLE_OFFSET + (x_axis.label_shift?.y ?? 0)}
952
969
  axis_loading={axis_loading === `x`}
953
970
  on_axis_change={(key) => handle_axis_change(`x`, key)}
954
971
  />
955
972
 
956
- <!-- X2-axis (Top) -->
957
- <!-- Note: x2 axis is only supported for vertical orientation -->
958
- {#if x2_series.length > 0 && orientation === `vertical`}
973
+ <!-- X2-axis (Top): only rendered in vertical orientation -->
974
+ {#if show_x2}
959
975
  <PlotAxis
960
976
  side="x2"
961
- ticks={ticks.x2 as number[]}
977
+ ticks={ticks.x2}
962
978
  place={scales.x2}
963
979
  axis={x2_axis}
964
- domain={ranges.current.x2 as Vec2}
980
+ domain={ranges.current.x2}
965
981
  {pad}
966
982
  {width}
967
983
  {height}
968
984
  show_grid={display.x2_grid}
969
985
  tick_label={(tick) => get_tick_label(tick, x2_axis.ticks)}
970
986
  label_x={pad.l + chart_width / 2 + (x2_axis.label_shift?.x ?? 0)}
971
- label_y={Math.max(12, pad.t - (x2_axis.label_shift?.y ?? 40))}
987
+ label_y={Math.max(12, pad.t - (x2_axis.label_shift?.y ?? AXIS_TITLE_OFFSET))}
972
988
  axis_loading={axis_loading === `x2`}
973
989
  on_axis_change={(key) => handle_axis_change(`x2`, key)}
974
990
  />
@@ -977,19 +993,20 @@
977
993
  <!-- Y-axis -->
978
994
  <PlotAxis
979
995
  side="y"
980
- ticks={ticks.y as number[]}
996
+ ticks={ticks.y}
981
997
  place={scales.y}
982
998
  axis={y_axis}
983
- domain={ranges.current.y as Vec2}
999
+ domain={ranges.current.y}
984
1000
  {pad}
985
1001
  {width}
986
1002
  {height}
987
1003
  show_grid={display.y_grid}
988
1004
  tick_label={(tick) =>
989
- get_tick_label(tick, cat_axis === `y` ? effective_cat_ticks : y_axis.ticks)}
1005
+ get_tick_label(tick, cat_axis === `y` ? effective_cat_ticks : y_axis.ticks)}
990
1006
  label_x={Math.max(
991
1007
  12,
992
- pad.l - (y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
1008
+ pad.l -
1009
+ (y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
993
1010
  LABEL_GAP_DEFAULT,
994
1011
  ) + (y_axis.label_shift?.x ?? 0)}
995
1012
  label_y={pad.t + chart_height / 2 + (y_axis.label_shift?.y ?? 0)}
@@ -997,15 +1014,14 @@
997
1014
  on_axis_change={(key) => handle_axis_change(`y`, key)}
998
1015
  />
999
1016
 
1000
- <!-- Y2-axis (Right) -->
1001
- <!-- Note: y2 axis is only supported for vertical orientation. Implementing x2 for horizontal mode requires additional complexity. -->
1002
- {#if y2_series.length > 0 && orientation === `vertical`}
1017
+ <!-- Y2-axis (Right): only rendered in vertical orientation -->
1018
+ {#if show_y2}
1003
1019
  <PlotAxis
1004
1020
  side="y2"
1005
- ticks={ticks.y2 as number[]}
1021
+ ticks={ticks.y2}
1006
1022
  place={scales.y2}
1007
1023
  axis={y2_axis}
1008
- domain={ranges.current.y2 as Vec2}
1024
+ domain={ranges.current.y2}
1009
1025
  {pad}
1010
1026
  {width}
1011
1027
  {height}
@@ -1046,8 +1062,8 @@
1046
1062
  y2_scale_type={y2_axis.scale_type}
1047
1063
  x_is_time={x_axis.format?.startsWith(`%`) ?? false}
1048
1064
  x2_is_time={x2_axis.format?.startsWith(`%`) ?? false}
1049
- has_x2={x2_series.length > 0}
1050
- has_y2={y2_series.length > 0}
1065
+ has_x2={show_x2}
1066
+ has_y2={show_y2}
1051
1067
  {width}
1052
1068
  {height}
1053
1069
  {pad}
@@ -1065,7 +1081,7 @@
1065
1081
  class={is_line ? `line-series` : `bar-series`}
1066
1082
  data-series-idx={series_idx}
1067
1083
  opacity={hovered_legend_series_idx !== null &&
1068
- hovered_legend_series_idx !== series_idx
1084
+ hovered_legend_series_idx !== series_idx
1069
1085
  ? 0.25
1070
1086
  : 1}
1071
1087
  >
@@ -1079,21 +1095,22 @@
1079
1095
  {@const use_x2 = srs.x_axis === `x2`}
1080
1096
  {@const x_scale = use_x2 ? scales.x2 : scales.x}
1081
1097
  {@const series_markers = srs.markers ?? DEFAULT_MARKERS}
1082
- {@const show_line = series_markers === `line` ||
1083
- series_markers === `line+points`}
1084
- {@const show_points = series_markers === `points` ||
1085
- series_markers === `line+points`}
1098
+ {@const show_line =
1099
+ series_markers === `line` || series_markers === `line+points`}
1100
+ {@const show_points =
1101
+ series_markers === `points` || series_markers === `line+points`}
1086
1102
  {@const points = compute_line_points({
1087
- series: srs,
1088
- series_idx,
1089
- orientation,
1090
- x_scale,
1091
- y_scale,
1092
- cat_y_scale: scales.y,
1093
- })}
1094
- {@const polyline_str = show_line && points.length > 1
1095
- ? points.map((pt) => `${pt.x},${pt.y}`).join(` `)
1096
- : ``}
1103
+ series: srs,
1104
+ series_idx,
1105
+ orientation,
1106
+ x_scale,
1107
+ y_scale,
1108
+ cat_y_scale: scales.y,
1109
+ })}
1110
+ {@const polyline_str =
1111
+ show_line && points.length > 1
1112
+ ? points.map((pt) => `${pt.x},${pt.y}`).join(` `)
1113
+ : ``}
1097
1114
  {#if polyline_str}
1098
1115
  <polyline
1099
1116
  points={polyline_str}
@@ -1125,11 +1142,7 @@
1125
1142
  change(hover_info)
1126
1143
  on_bar_hover?.({ ...hover_info!, event: evt })
1127
1144
  }}
1128
- onmouseleave={() => {
1129
- change(null)
1130
- hover_info = null
1131
- on_bar_hover?.(null)
1132
- }}
1145
+ onmouseleave={clear_hover}
1133
1146
  onclick={(evt) => {
1134
1147
  const pt = find_closest_point(evt, points)
1135
1148
  if (!pt) return
@@ -1142,44 +1155,41 @@
1142
1155
  {#if show_points}
1143
1156
  {@const clickable = on_bar_click || on_point_click}
1144
1157
  {@const get_pt = (evt: Event) => {
1145
- const attr = evt.target instanceof Element
1146
- ? evt.target.closest(`[data-bar-idx]`)?.getAttribute(
1147
- `data-bar-idx`,
1148
- )
1149
- : null
1150
- return points.find((pt) => pt.idx === parseInt(attr ?? ``, 10))
1151
- }}
1158
+ const attr =
1159
+ evt.target instanceof Element
1160
+ ? evt.target.closest(`[data-bar-idx]`)?.getAttribute(`data-bar-idx`)
1161
+ : null
1162
+ return points.find((pt) => pt.idx === parseInt(attr ?? ``, 10))
1163
+ }}
1152
1164
  {@const set_hover = (
1153
- pt: LineSeriesPoint | null,
1154
- evt: MouseEvent | FocusEvent,
1155
- ) => {
1156
- if (pt) {
1157
- hovered = true
1158
- const fill = line_point_fill(pt, color)
1159
- hover_info = get_bar_data(series_idx, pt.idx, fill)
1160
- change(hover_info)
1161
- } else {
1162
- change(null)
1163
- hover_info = null
1164
- }
1165
- on_bar_hover?.(pt ? { ...hover_info!, event: evt } : null)
1166
- on_point_hover?.(
1167
- pt ? { ...hover_info!, event: evt, point: pt } : null,
1168
- )
1169
- }}
1165
+ pt: LineSeriesPoint | null,
1166
+ evt: MouseEvent | FocusEvent,
1167
+ ) => {
1168
+ if (pt) {
1169
+ hovered = true
1170
+ const fill = line_point_fill(pt, color)
1171
+ hover_info = get_bar_data(series_idx, pt.idx, fill)
1172
+ change(hover_info)
1173
+ } else {
1174
+ change(null)
1175
+ hover_info = null
1176
+ }
1177
+ on_bar_hover?.(pt ? { ...hover_info!, event: evt } : null)
1178
+ on_point_hover?.(pt ? { ...hover_info!, event: evt, point: pt } : null)
1179
+ }}
1170
1180
  {@const do_click = (
1171
- pt: LineSeriesPoint,
1172
- evt: MouseEvent | KeyboardEvent,
1173
- ) => {
1174
- const fill = line_point_fill(pt, color)
1175
- const bar_data = get_bar_data(series_idx, pt.idx, fill)
1176
- on_bar_click?.({ ...bar_data, event: evt })
1177
- on_point_click?.({ ...bar_data, event: evt, point: pt })
1178
- }}
1181
+ pt: LineSeriesPoint,
1182
+ evt: MouseEvent | KeyboardEvent,
1183
+ ) => {
1184
+ const fill = line_point_fill(pt, color)
1185
+ const bar_data = get_bar_data(series_idx, pt.idx, fill)
1186
+ on_bar_click?.({ ...bar_data, event: evt })
1187
+ on_point_click?.({ ...bar_data, event: evt, point: pt })
1188
+ }}
1179
1189
  {@const leaving = (evt: MouseEvent | FocusEvent) =>
1180
- (evt.relatedTarget instanceof Element
1181
- ? evt.relatedTarget.closest(`.line-points`)
1182
- : null) !== evt.currentTarget}
1190
+ (evt.relatedTarget instanceof Element
1191
+ ? evt.relatedTarget.closest(`.line-points`)
1192
+ : null) !== evt.currentTarget}
1183
1193
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_mouse_events_have_key_events -->
1184
1194
  <g
1185
1195
  class="line-points"
@@ -1213,11 +1223,13 @@
1213
1223
  {#each points as pt (pt.idx)}
1214
1224
  {@const sty = pt.point_style}
1215
1225
  {@const fl = line_point_fill(pt, color)}
1216
- {@const rad = pt.size_value != null
1217
- ? size_scale_fn(pt.size_value)
1218
- : sty?.radius ?? 4}
1219
- {@const hov = hover_info?.series_idx === series_idx &&
1220
- hover_info?.bar_idx === pt.idx}
1226
+ {@const rad =
1227
+ pt.size_value != null
1228
+ ? size_scale_fn(pt.size_value)
1229
+ : (sty?.radius ?? 4)}
1230
+ {@const hov =
1231
+ hover_info?.series_idx === series_idx &&
1232
+ hover_info?.bar_idx === pt.idx}
1221
1233
  <ScatterPoint
1222
1234
  x={pt.x}
1223
1235
  y={pt.y}
@@ -1248,31 +1260,30 @@
1248
1260
  <!-- Render as bars -->
1249
1261
  {#each srs.x as x_val, bar_idx (bar_idx)}
1250
1262
  {@const y_val = srs.y[bar_idx]}
1251
- {@const base = mode === `stacked`
1252
- ? (stacked_offsets[series_idx]?.[bar_idx] ?? 0)
1253
- : 0}
1263
+ {@const base =
1264
+ mode === `stacked` ? (stacked_offsets[series_idx]?.[bar_idx] ?? 0) : 0}
1254
1265
  {@const color = srs.color ?? bar_state.color ?? `steelblue`}
1255
1266
  {@const bar_width_val = Array.isArray(srs.bar_width)
1256
- ? (srs.bar_width[bar_idx] ?? 0.5)
1257
- : (srs.bar_width ?? 0.5)}
1267
+ ? (srs.bar_width[bar_idx] ?? 0.5)
1268
+ : (srs.bar_width ?? 0.5)}
1258
1269
  {@const is_vertical = orientation === `vertical`}
1259
1270
  {@const x_scale_bar = srs.x_axis === `x2` ? scales.x2 : scales.x}
1260
1271
  {@const [cat_scale, val_scale] = is_vertical
1261
- ? [x_scale_bar, srs.y_axis === `y2` ? scales.y2 : scales.y]
1262
- : [scales.y, x_scale_bar]}
1272
+ ? [x_scale_bar, srs.y_axis === `y2` ? scales.y2 : scales.y]
1273
+ : [scales.y, x_scale_bar]}
1263
1274
  {@const { c0, c1, v0, v1, rect_x, rect_y, rect_w, rect_h } =
1264
- compute_bar_rect({
1265
- cat_val: x_val,
1266
- val: y_val,
1267
- base,
1268
- bar_width_val,
1269
- series_idx,
1270
- mode,
1271
- orientation,
1272
- group_info,
1273
- cat_scale,
1274
- val_scale,
1275
- })}
1275
+ compute_bar_rect({
1276
+ cat_val: x_val,
1277
+ val: y_val,
1278
+ base,
1279
+ bar_width_val,
1280
+ series_idx,
1281
+ mode,
1282
+ orientation,
1283
+ group_info,
1284
+ cat_scale,
1285
+ val_scale,
1286
+ })}
1276
1287
  {#if (is_vertical ? rect_h : rect_w) > 0}
1277
1288
  <path
1278
1289
  d={bar_path(
@@ -1293,16 +1304,12 @@
1293
1304
  aria-label={`bar ${bar_idx + 1} of ${srs.label ?? `series`}`}
1294
1305
  style:cursor={on_bar_click ? `pointer` : undefined}
1295
1306
  onmousemove={handle_bar_hover(series_idx, bar_idx, color)}
1296
- onmouseleave={() => {
1297
- hover_info = null
1298
- change(null)
1299
- on_bar_hover?.(null)
1300
- }}
1307
+ onmouseleave={clear_hover}
1301
1308
  onclick={(evt) =>
1302
- on_bar_click?.({
1303
- ...get_bar_data(series_idx, bar_idx, color),
1304
- event: evt,
1305
- })}
1309
+ on_bar_click?.({
1310
+ ...get_bar_data(series_idx, bar_idx, color),
1311
+ event: evt,
1312
+ })}
1306
1313
  onkeydown={(evt) => {
1307
1314
  if (evt.key === `Enter` || evt.key === ` `) {
1308
1315
  evt.preventDefault()
@@ -1334,17 +1341,44 @@
1334
1341
 
1335
1342
  {@render ref_lines_layer(ref_lines_by_z.below_points)}
1336
1343
  {@render ref_lines_layer(ref_lines_by_z.above_all)}
1344
+
1345
+ <!-- Marginal distribution strips -->
1346
+ <PlotMarginals
1347
+ marginals={resolved_marginals}
1348
+ series={marginal_series}
1349
+ {width}
1350
+ {height}
1351
+ {pad}
1352
+ has_axis={marginal_has_axis}
1353
+ axes={{
1354
+ x1: marginal_axis(
1355
+ scales.x,
1356
+ ranges.current.x,
1357
+ x_axis,
1358
+ cat_axis === `x` ? (pos) => category_list[Math.round(pos)] : undefined,
1359
+ ),
1360
+ x2: marginal_axis(scales.x2, ranges.current.x2, x2_axis),
1361
+ y1: marginal_axis(
1362
+ scales.y,
1363
+ ranges.current.y,
1364
+ y_axis,
1365
+ cat_axis === `y` ? (pos) => category_list[Math.round(pos)] : undefined,
1366
+ ),
1367
+ y2: marginal_axis(scales.y2, ranges.current.y2, y2_axis),
1368
+ }}
1369
+ id={clip_path_id}
1370
+ />
1337
1371
  </svg>
1338
1372
 
1339
1373
  <!-- Legend -->
1340
- {#if legend && (show_legend !== undefined ? show_legend : series.length > 1)}
1374
+ {#if legend && should_show_legend}
1341
1375
  {@const legend_pos = placed_coords(
1342
- legend_auto_outside,
1343
- { x: legend_outside_x, y: legend_outside_y },
1344
- legend_placement,
1345
- legend_tween.coords.current,
1346
- { x: pad.l + 10, y: pad.t + 10 },
1347
- )}
1376
+ decor.legend_outside,
1377
+ decor.legend_pos,
1378
+ legend_tween.placed(),
1379
+ legend_tween.coords.current,
1380
+ { x: pad.l + 10, y: pad.t + 10 },
1381
+ )}
1348
1382
  <PlotLegend
1349
1383
  bind:root_element={legend_element}
1350
1384
  {...legend}
@@ -1354,9 +1388,8 @@
1354
1388
  on_double_click={legend?.on_double_click ?? legend_vis.on_double_click}
1355
1389
  on_hover_change={legend_tween.set_locked}
1356
1390
  on_item_hover={(item) =>
1357
- (hovered_legend_series_idx = item != null && item.series_idx >= 0
1358
- ? item.series_idx
1359
- : null)}
1391
+ (hovered_legend_series_idx =
1392
+ item != null && item.series_idx >= 0 ? item.series_idx : null)}
1360
1393
  active_series_idx={hover_info?.series_idx ?? hovered_legend_series_idx}
1361
1394
  style={`
1362
1395
  position: absolute;
@@ -1370,11 +1403,11 @@
1370
1403
 
1371
1404
  {#if hover_info && hovered}
1372
1405
  {@const cx = (hover_info.active_x_axis === `x2` ? scales.x2 : scales.x)(
1373
- hover_info.orient_x,
1374
- )}
1406
+ hover_info.orient_x,
1407
+ )}
1375
1408
  {@const cy = (hover_info.active_y_axis === `y2` ? scales.y2 : scales.y)(
1376
- hover_info.orient_y,
1377
- )}
1409
+ hover_info.orient_y,
1410
+ )}
1378
1411
  <PlotTooltip
1379
1412
  x={cx}
1380
1413
  y={cy}
@@ -1391,16 +1424,16 @@
1391
1424
  <div><strong>{series_label}</strong></div>
1392
1425
  {/if}
1393
1426
  <div>
1394
- {@html sanitize_html(hover_info.x_axis.label || `x`)}: {
1395
- (cat_axis === `x` ? hover_info.category_label : undefined) ??
1396
- format_value(hover_info.orient_x, hover_info.x_axis.format || `.3~s`)
1397
- }
1427
+ {@html sanitize_html(hover_info.x_axis.label || `x`)}: {(cat_axis === `x`
1428
+ ? hover_info.category_label
1429
+ : undefined) ??
1430
+ format_value_or_num(hover_info.orient_x, hover_info.x_axis.format)}
1398
1431
  </div>
1399
1432
  <div>
1400
- {@html sanitize_html(hover_info.y_axis.label || `y`)}: {
1401
- (cat_axis === `y` ? hover_info.category_label : undefined) ??
1402
- format_value(hover_info.orient_y, hover_info.y_axis.format || `.3~s`)
1403
- }
1433
+ {@html sanitize_html(hover_info.y_axis.label || `y`)}: {(cat_axis === `y`
1434
+ ? hover_info.category_label
1435
+ : undefined) ??
1436
+ format_value_or_num(hover_info.orient_y, hover_info.y_axis.format)}
1404
1437
  </div>
1405
1438
  {/if}
1406
1439
  </PlotTooltip>
@@ -1424,12 +1457,12 @@
1424
1457
  bind:y_axis
1425
1458
  bind:y2_axis={y2_axis_prop}
1426
1459
  bind:display
1427
- auto_x_range={auto_ranges.x as Vec2}
1428
- auto_x2_range={auto_ranges.x2 as Vec2}
1429
- auto_y_range={auto_ranges.y as Vec2}
1430
- auto_y2_range={auto_ranges.y2 as Vec2}
1431
- has_x2_points={x2_series.length > 0}
1432
- has_y2_points={y2_series.length > 0}
1460
+ auto_x_range={auto_ranges.x}
1461
+ auto_x2_range={auto_ranges.x2}
1462
+ auto_y_range={auto_ranges.y}
1463
+ auto_y2_range={auto_ranges.y2}
1464
+ has_x2_points={show_x2}
1465
+ has_y2_points={show_y2}
1433
1466
  children={controls_extra}
1434
1467
  />
1435
1468
  {/if}
@@ -1489,7 +1522,9 @@
1489
1522
  .bar-plot :global(.pane-toggle),
1490
1523
  .bar-plot .header-controls {
1491
1524
  opacity: 0;
1492
- transition: opacity 0.2s, background-color 0.2s;
1525
+ transition:
1526
+ opacity 0.2s,
1527
+ background-color 0.2s;
1493
1528
  }
1494
1529
  .bar-plot:hover :global(.pane-toggle),
1495
1530
  .bar-plot:hover .header-controls,