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
@@ -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, 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'
@@ -40,6 +40,7 @@
40
40
  Line,
41
41
  PlotAxis,
42
42
  PlotLegend,
43
+ PlotMarginals,
43
44
  PlotTooltip,
44
45
  ReferenceLine,
45
46
  ScatterPlotControls,
@@ -47,20 +48,33 @@
47
48
  ZeroLines,
48
49
  ZoomRect,
49
50
  } from '..'
51
+ import type { MarginalSeriesInput, MarginalsProp } from '../core/marginals'
52
+ import {
53
+ add_sides,
54
+ marginal_axis,
55
+ marginal_axis_presence,
56
+ normalize_marginals,
57
+ reserve_marginal_pad,
58
+ } from '../core/marginals'
50
59
  import {
51
60
  build_obstacles_norm,
52
61
  has_explicit_position,
53
62
  measured_footprint,
54
63
  place_decorations,
55
64
  } from '../core/auto-place'
56
- import type { AxisChangeState } from '../core/axis-utils'
57
- import { AXIS_DEFAULTS, create_axis_loader } from '../core/axis-utils'
65
+ import {
66
+ AXIS_DEFAULTS,
67
+ type AxisChangeState,
68
+ create_axis_loader,
69
+ } from '../core/axis-utils'
58
70
  import { get_series_color, get_series_symbol } from '../core/data-transform'
59
71
  import { create_placed_tween } from '../core/placed-tween.svelte'
60
72
  import {
73
+ COLOR_BAR_DEFAULTS,
61
74
  DEFAULT_MARKERS,
62
75
  get_scale_type_name,
63
76
  is_time_scale,
77
+ SCALE_DEFAULTS,
64
78
  } from '../core/types'
65
79
  import { compute_label_positions } from '../core/utils/label-placement'
66
80
  import { create_legend_visibility } from '../core/utils/series-visibility'
@@ -88,6 +102,7 @@
88
102
  import { create_pan_zoom } from '../core/pan-zoom.svelte'
89
103
  import type { Rect, Sides } from '../core/layout'
90
104
  import {
105
+ AXIS_TITLE_OFFSET,
91
106
  calc_auto_padding,
92
107
  filter_padding,
93
108
  LABEL_GAP_DEFAULT,
@@ -106,11 +121,10 @@
106
121
  get_nice_data_range,
107
122
  } from '../core/scales'
108
123
  import { resolve_line_tween, unique_id } from '../core/utils'
109
- import {
110
- build_legend_data,
111
- filter_series_to_ranges,
112
- pick_tooltip_bg,
113
- } from './scatter-data'
124
+ import { build_legend_data, filter_series_to_ranges, pick_tooltip_bg } from './scatter-data'
125
+
126
+ // Plots with at least this many visible markers get smaller default point radii
127
+ const DENSE_MARKER_COUNT = 100
114
128
 
115
129
  let {
116
130
  series = $bindable([]),
@@ -122,7 +136,7 @@
122
136
  styles: styles_init = {},
123
137
  controls: controls_init = {},
124
138
  padding = {},
125
- range_padding = 0.05,
139
+ range_padding = 0,
126
140
  current_x_value = null,
127
141
  tooltip_point = $bindable(null),
128
142
  selected_point = null,
@@ -130,13 +144,9 @@
130
144
  tooltip,
131
145
  user_content,
132
146
  change = () => {},
133
- color_scale = {
134
- type: `linear`,
135
- scheme: `interpolateViridis`,
136
- value_range: undefined,
137
- },
147
+ color_scale = SCALE_DEFAULTS.color,
138
148
  color_bar = {},
139
- size_scale = { type: `linear`, radius_range: [2, 10], value_range: undefined },
149
+ size_scale = SCALE_DEFAULTS.size,
140
150
  label_placement_config = {},
141
151
  hover_config = {},
142
152
  legend = {},
@@ -163,100 +173,95 @@
163
173
  on_axis_change,
164
174
  on_error,
165
175
  pan = {},
176
+ marginals = false,
166
177
  ...rest
167
- }: HTMLAttributes<HTMLDivElement> & Omit<BasePlotProps, `change`> & PlotConfig & {
168
- series?: DataSeries<Metadata>[]
169
- styles?: StyleOverrides
170
- controls?: ControlsConfig
171
- current_x_value?: number | null
172
- tooltip_point?: InternalPoint<Metadata> | null
173
- selected_point?: { series_idx: number; point_idx: number } | null
174
- tooltip?: Snippet<[ScatterHandlerProps<Metadata>]>
175
- user_content?: Snippet<[UserContentProps]>
176
- header_controls?: Snippet<
177
- [{ height: number; width: number; fullscreen: boolean }]
178
- >
179
- controls_extra?: Snippet<
180
- [
181
- & { styles: StyleOverrides; selected_series_idx: number }
182
- & Required<PlotConfig>,
183
- ]
184
- >
185
- change?: (
186
- data: (Point<Metadata> & { series: DataSeries<Metadata> }) | null,
187
- ) => void
188
- color_scale?: ColorScaleConfig | D3InterpolateName
189
- size_scale?: SizeScaleConfig
190
- color_bar?:
191
- | (ComponentProps<typeof ColorBar> & {
192
- margin?: number | Sides
193
- tween?: TweenOptions<Point2D>
194
- responsive?: boolean // Allow colorbar to reposition if density changes (default: false)
195
- axis_clearance?: number // Min distance kept from plot edges/axes (default: 15)
196
- })
197
- | null
198
- label_placement_config?: Partial<LabelPlacementConfig>
199
- hover_config?: Partial<HoverConfig>
200
- legend?: LegendConfig | null
201
- point_tween?: TweenOptions<Point2D>
202
- line_tween?: TweenOptions<string>
203
- point_events?: Record<
204
- string,
205
- (payload: { point: InternalPoint<Metadata>; event: Event }) => void
206
- >
207
- on_point_click?: (data: ScatterHandlerEvent<Metadata>) => void
208
- on_point_hover?: (data: ScatterHandlerEvent<Metadata> | null) => void
209
- fill_regions?: FillRegion[] // Bindable for legend toggle support
210
- error_bands?: ErrorBand[]
211
- on_fill_click?: (event: FillHandlerEvent) => void
212
- on_fill_hover?: (event: FillHandlerEvent | null) => void
213
- ref_lines?: RefLine[] // Bindable for legend toggle support
214
- on_ref_line_click?: (event: RefLineEvent) => void
215
- on_ref_line_hover?: (event: RefLineEvent | null) => void
216
- selected_series_idx?: number
217
- wrapper?: HTMLDivElement
218
- // Interactive axis props
219
- data_loader?: DataLoaderFn<Metadata>
220
- on_axis_change?: (
221
- axis: `x` | `x2` | `y` | `y2`,
222
- key: string,
223
- new_series: DataSeries<Metadata>[],
224
- ) => void
225
- on_error?: (error: AxisLoadError) => void
226
- pan?: PanConfig
227
- } = $props()
178
+ }: HTMLAttributes<HTMLDivElement> &
179
+ Omit<BasePlotProps, `change`> &
180
+ PlotConfig & {
181
+ series?: DataSeries<Metadata>[]
182
+ styles?: StyleOverrides
183
+ controls?: ControlsConfig
184
+ current_x_value?: number | null
185
+ tooltip_point?: InternalPoint<Metadata> | null
186
+ selected_point?: { series_idx: number; point_idx: number } | null
187
+ tooltip?: Snippet<[ScatterHandlerProps<Metadata>]>
188
+ user_content?: Snippet<[UserContentProps]>
189
+ header_controls?: Snippet<[{ height: number; width: number; fullscreen: boolean }]>
190
+ controls_extra?: Snippet<
191
+ [{ styles: StyleOverrides; selected_series_idx: number } & Required<PlotConfig>]
192
+ >
193
+ change?: (data: (Point<Metadata> & { series: DataSeries<Metadata> }) | null) => void
194
+ color_scale?: ColorScaleConfig | D3InterpolateName
195
+ size_scale?: SizeScaleConfig
196
+ color_bar?:
197
+ | (ComponentProps<typeof ColorBar> & {
198
+ margin?: number | Sides
199
+ tween?: TweenOptions<Point2D>
200
+ responsive?: boolean // Allow colorbar to reposition if density changes (default: false)
201
+ axis_clearance?: number // Min distance kept from plot edges/axes (default: 15)
202
+ })
203
+ | null
204
+ label_placement_config?: Partial<LabelPlacementConfig>
205
+ hover_config?: Partial<HoverConfig>
206
+ legend?: LegendConfig | null
207
+ point_tween?: TweenOptions<Point2D>
208
+ line_tween?: TweenOptions<string>
209
+ point_events?: Record<
210
+ string,
211
+ (payload: { point: InternalPoint<Metadata>; event: Event }) => void
212
+ >
213
+ on_point_click?: (data: ScatterHandlerEvent<Metadata>) => void
214
+ on_point_hover?: (data: ScatterHandlerEvent<Metadata> | null) => void
215
+ fill_regions?: FillRegion[] // Bindable for legend toggle support
216
+ error_bands?: ErrorBand[]
217
+ on_fill_click?: (event: FillHandlerEvent) => void
218
+ on_fill_hover?: (event: FillHandlerEvent | null) => void
219
+ ref_lines?: RefLine[] // Bindable for legend toggle support
220
+ on_ref_line_click?: (event: RefLineEvent) => void
221
+ on_ref_line_hover?: (event: RefLineEvent | null) => void
222
+ selected_series_idx?: number
223
+ wrapper?: HTMLDivElement
224
+ // Interactive axis props
225
+ data_loader?: DataLoaderFn<Metadata>
226
+ on_axis_change?: (
227
+ axis: `x` | `x2` | `y` | `y2`,
228
+ key: string,
229
+ new_series: DataSeries<Metadata>[],
230
+ ) => void
231
+ on_error?: (error: AxisLoadError) => void
232
+ pan?: PanConfig
233
+ marginals?: MarginalsProp
234
+ } = $props()
228
235
 
229
236
  // Merged axis/display values with defaults (use $derived to avoid breaking $bindable)
230
237
  const final_x_axis = $derived({
231
238
  ...AXIS_DEFAULTS,
232
- label_shift: { x: 0, y: -40 }, // x-axis needs different label position
239
+ label_shift: { x: 0, y: 0 },
233
240
  ...x_axis,
234
241
  })
235
242
  const final_y_axis = $derived({ ...AXIS_DEFAULTS, ...y_axis })
236
243
  const final_x2_axis = $derived({
237
244
  ...AXIS_DEFAULTS,
238
- label_shift: { x: 0, y: 40 }, // x2-axis label above top edge
245
+ label_shift: { x: 0, y: AXIS_TITLE_OFFSET }, // x2-axis label above top edge
239
246
  ...x2_axis,
240
247
  })
241
248
  const final_y2_axis = $derived({ ...AXIS_DEFAULTS, ...y2_axis })
242
249
  // Cache time-axis check — used in ~10 places for scale/tick/tooltip logic
243
- let is_time_x = $derived(
244
- is_time_scale(final_x_axis.scale_type, final_x_axis.format),
245
- )
246
- let is_time_x2 = $derived(
247
- is_time_scale(final_x2_axis.scale_type, final_x2_axis.format),
248
- )
250
+ let is_time_x = $derived(is_time_scale(final_x_axis.scale_type, final_x_axis.format))
251
+ let is_time_x2 = $derived(is_time_scale(final_x2_axis.scale_type, final_x2_axis.format))
249
252
  const final_display = $derived({ ...DEFAULTS.scatter.display, ...display })
250
253
  // Local state for styles (initialized from prop, owned by this component for controls)
251
254
  // Using $state because styles has bindings in ScatterPlotControls
252
255
  // untrack() explicitly captures initial prop value (intentional - props provide initial config)
253
- let styles = $state(untrack(() => ({
254
- show_points: DEFAULTS.scatter.show_points,
255
- show_lines: DEFAULTS.scatter.show_lines,
256
- point: { ...DEFAULTS.scatter.point, ...styles_init?.point },
257
- line: { ...DEFAULTS.scatter.line, ...styles_init?.line },
258
- ...styles_init,
259
- })))
256
+ let styles = $state(
257
+ untrack(() => ({
258
+ show_points: DEFAULTS.scatter.show_points,
259
+ show_lines: DEFAULTS.scatter.show_lines,
260
+ point: { ...DEFAULTS.scatter.point, ...styles_init?.point },
261
+ line: { ...DEFAULTS.scatter.line, ...styles_init?.line },
262
+ ...styles_init,
263
+ })),
264
+ )
260
265
  let controls = $derived({ show: true, open: false, ...controls_init })
261
266
 
262
267
  let [width, height] = $state([0, 0])
@@ -287,7 +292,10 @@
287
292
  initial: { x: [0, 1], x2: [0, 1], y: [0, 1], y2: [0, 1] },
288
293
  current: { x: [0, 1], x2: [0, 1], y: [0, 1], y2: [0, 1] },
289
294
  })
290
- const legend_vis = create_legend_visibility(() => series, (next) => (series = next))
295
+ const legend_vis = create_legend_visibility(
296
+ () => series,
297
+ (next) => (series = next),
298
+ )
291
299
 
292
300
  // Y2 axis sync configuration
293
301
  let y2_sync_config = $derived(normalize_y2_sync(y2_axis?.sync))
@@ -350,8 +358,13 @@
350
358
 
351
359
  for (const srs of series_with_ids) {
352
360
  if (!srs) continue
353
- const { x: xs, y: ys, visible = true, y_axis: series_y_axis = `y1`, x_axis: x_ax = `x1` } =
354
- srs as DataSeries
361
+ const {
362
+ x: xs,
363
+ y: ys,
364
+ visible = true,
365
+ y_axis: series_y_axis = `y1`,
366
+ x_axis: x_ax = `x1`,
367
+ } = srs as DataSeries
355
368
  for (let idx = 0; idx < xs.length; idx++) {
356
369
  const point = { x: xs[idx], y: ys[idx] }
357
370
  all.push(point)
@@ -376,37 +389,44 @@
376
389
 
377
390
  // Update padding when format or ticks change
378
391
  $effect(() => {
379
- const new_pad = width && height &&
380
- (y_tick_values.length > 0 || y2_tick_values.length > 0 || x2_tick_values.length > 0)
381
- ? calc_auto_padding({
382
- padding,
383
- default_padding,
384
- x2_axis: { ...final_x2_axis, tick_values: x2_tick_values },
385
- y_axis: { ...final_y_axis, tick_values: y_tick_values },
386
- y2_axis: { ...final_y2_axis, tick_values: y2_tick_values },
387
- })
388
- : filter_padding(padding, default_padding)
392
+ const new_pad =
393
+ width &&
394
+ height &&
395
+ (y_tick_values.length > 0 || y2_tick_values.length > 0 || x2_tick_values.length > 0)
396
+ ? calc_auto_padding({
397
+ padding,
398
+ default_padding,
399
+ x2_axis: { ...final_x2_axis, tick_values: x2_tick_values },
400
+ y_axis: { ...final_y_axis, tick_values: y_tick_values },
401
+ y2_axis: { ...final_y2_axis, tick_values: y2_tick_values },
402
+ })
403
+ : filter_padding(padding, default_padding)
389
404
 
390
405
  if (
391
406
  base_pad.t !== new_pad.t ||
392
407
  base_pad.b !== new_pad.b ||
393
408
  base_pad.l !== new_pad.l ||
394
409
  base_pad.r !== new_pad.r
395
- ) base_pad = new_pad
410
+ )
411
+ base_pad = new_pad
396
412
  })
397
413
 
398
414
  // === Auto-move legend/colorbar outside the plot when interior overlap is unavoidable ===
399
415
  // (shared logic lives in auto-place.ts so every 2D plot reuses it)
400
416
  // ColorBar's orientation prop defaults to horizontal, so treat unset as horizontal too
401
- const colorbar_is_horizontal = $derived((color_bar?.orientation ?? `horizontal`) === `horizontal`)
417
+ const colorbar_is_horizontal = $derived(
418
+ (color_bar?.orientation ?? `horizontal`) === `horizontal`,
419
+ )
402
420
  const colorbar_footprint = $derived(
403
421
  colorbar_element?.offsetWidth && colorbar_element?.offsetHeight
404
422
  ? measure_full_footprint(colorbar_element)
405
423
  : colorbar_is_horizontal
406
- ? { width: 220, height: 56 }
407
- : { width: 56, height: 100 },
424
+ ? COLOR_BAR_DEFAULTS.horizontal_footprint
425
+ : COLOR_BAR_DEFAULTS.vertical_footprint,
426
+ )
427
+ const legend_footprint = $derived(
428
+ measured_footprint(legend_element, { width: 120, height: 80 }),
408
429
  )
409
- const legend_footprint = $derived(measured_footprint(legend_element, { width: 120, height: 80 }))
410
430
  const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
411
431
 
412
432
  // Plot-specific obstacle field: series points/lines normalized to [0,1] (y=0 at top)
@@ -416,7 +436,9 @@
416
436
  const base_h = height - base_pad.t - base_pad.b
417
437
  if (base_w <= 0 || base_h <= 0) return []
418
438
  const norm_x = is_time_x
419
- ? scaleTime().domain([new Date(x_min), new Date(x_max)]).range([0, 1])
439
+ ? scaleTime()
440
+ .domain([new Date(x_min), new Date(x_max)])
441
+ .range([0, 1])
420
442
  : create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [0, 1])
421
443
  const norm_y = create_scale(final_y_axis.scale_type ?? `linear`, [y_min, y_max], [0, 1])
422
444
  return build_obstacles_norm(
@@ -442,23 +464,58 @@
442
464
  obstacles_norm,
443
465
  // gate on legend_element (the actual render signal) not legend_data, whose fill entries read
444
466
  // computed_fills -> pad and would make this derived reference itself
445
- legend: legend != null && legend_element != null &&
446
- !legend_has_explicit_pos && !legend_is_dragging && !legend_manual_position
447
- ? { footprint: legend_footprint, clearance: legend?.axis_clearance }
448
- : null,
467
+ legend:
468
+ legend != null &&
469
+ legend_element != null &&
470
+ !legend_has_explicit_pos &&
471
+ !legend_is_dragging &&
472
+ !legend_manual_position
473
+ ? { footprint: legend_footprint, clearance: legend?.axis_clearance }
474
+ : null,
449
475
  // gate on a measured colorbar: its outside style stretches it to full width, so deciding from
450
476
  // the (wide) pre-measure fallback would flip-flop placement between interior and outside
451
- colorbar: Boolean(color_bar) && all_color_values.length > 0 && !color_bar?.wrapper_style &&
452
- (colorbar_element?.offsetWidth ?? 0) > 0 && (colorbar_element?.offsetHeight ?? 0) > 0
453
- ? {
454
- footprint: colorbar_footprint,
455
- horizontal: colorbar_is_horizontal,
456
- clearance: color_bar?.axis_clearance,
457
- }
458
- : null,
459
- })
477
+ colorbar:
478
+ Boolean(color_bar) &&
479
+ all_color_values.length > 0 &&
480
+ !color_bar?.wrapper_style &&
481
+ (colorbar_element?.offsetWidth ?? 0) > 0 &&
482
+ (colorbar_element?.offsetHeight ?? 0) > 0
483
+ ? {
484
+ footprint: colorbar_footprint,
485
+ horizontal: colorbar_is_horizontal,
486
+ clearance: color_bar?.axis_clearance,
487
+ }
488
+ : null,
489
+ }),
490
+ )
491
+ // Resolve marginals and reserve outer-band padding so the plot shrinks to make room
492
+ const resolved_marginals = $derived(
493
+ normalize_marginals(marginals, { top: true, right: true }),
494
+ )
495
+ const pad = $derived(add_sides(decor.pad, reserve_marginal_pad(resolved_marginals)))
496
+ // Map series to the generic marginal input, reusing the line/legend color fallback
497
+ const marginal_series = $derived<MarginalSeriesInput[]>(
498
+ series_with_ids.map((srs, idx) => {
499
+ const point_fill = Array.isArray(srs?.point_style)
500
+ ? srs.point_style[0]?.fill
501
+ : srs?.point_style?.fill
502
+ return {
503
+ x: srs?.x ?? [],
504
+ y: srs?.y ?? [],
505
+ color:
506
+ srs?.line_style?.stroke ??
507
+ point_fill ??
508
+ get_series_color(srs?.orig_series_idx ?? idx),
509
+ label: srs?.label,
510
+ visible: srs?.visible ?? true,
511
+ x_axis: srs?.x_axis,
512
+ y_axis: srs?.y_axis,
513
+ }
514
+ }),
515
+ )
516
+ const marginal_has_axis = $derived(
517
+ marginal_axis_presence(x2_points.length > 0, y2_points.length > 0),
460
518
  )
461
- const pad = $derived(decor.pad)
462
519
  const legend_auto_outside = $derived(decor.legend_outside)
463
520
  const legend_outside_x = $derived(decor.legend_pos.x)
464
521
  const legend_outside_y = $derived(decor.legend_pos.y)
@@ -471,9 +528,7 @@
471
528
  x: pad.l || 0,
472
529
  y: pad.t || 0,
473
530
  width: isFinite(width - pad.l - pad.r) ? Math.max(1, width - pad.l - pad.r) : 1,
474
- height: isFinite(height - pad.t - pad.b)
475
- ? Math.max(1, height - pad.t - pad.b)
476
- : 1,
531
+ height: isFinite(height - pad.t - pad.b) ? Math.max(1, height - pad.t - pad.b) : 1,
477
532
  })
478
533
 
479
534
  // Calculate plot area center coordinates
@@ -487,8 +542,12 @@
487
542
  for (const srs of series_with_ids) {
488
543
  if (!srs) continue
489
544
  const { color_values: cvs, size_values: svs } = srs as DataSeries
490
- if (cvs) { for (const val of cvs) if (val != null) color_values.push(val) }
491
- if (svs) { for (const val of svs) if (val != null) size_values.push(val) }
545
+ if (cvs) {
546
+ for (const val of cvs) if (val != null) color_values.push(val)
547
+ }
548
+ if (svs) {
549
+ for (const val of svs) if (val != null) size_values.push(val)
550
+ }
492
551
  }
493
552
  return { color_values, size_values }
494
553
  })
@@ -567,7 +626,10 @@
567
626
  if (explicit) {
568
627
  ranges.current[axis] = range
569
628
  } else {
570
- const result = expand_range_if_needed(untrack(() => ranges.initial[axis]), range)
629
+ const result = expand_range_if_needed(
630
+ untrack(() => ranges.initial[axis]),
631
+ range,
632
+ )
571
633
  if (result.changed) {
572
634
  ranges.initial[axis] = result.range
573
635
  ranges.current[axis] = result.range
@@ -579,7 +641,10 @@
579
641
  if (resolved.y2.explicit) {
580
642
  ranges.current.y2 = resolved.y2.range
581
643
  } else {
582
- const result = expand_range_if_needed(untrack(() => ranges.initial.y2), resolved.y2.range)
644
+ const result = expand_range_if_needed(
645
+ untrack(() => ranges.initial.y2),
646
+ resolved.y2.range,
647
+ )
583
648
  if (result.changed) ranges.initial.y2 = result.range
584
649
  // Apply sync if enabled, otherwise use expanded range (or keep current if unchanged)
585
650
  if (y2_sync_config.mode !== `none`) {
@@ -605,10 +670,10 @@
605
670
  // Ensure we only calculate extent on actual numbers, filtering out nulls/undefined
606
671
  all_color_values.length > 0
607
672
  ? extent(
608
- all_color_values.filter((color_val: number | null): color_val is number =>
609
- typeof color_val === `number`
610
- ),
611
- )
673
+ all_color_values.filter(
674
+ (color_val: number | null): color_val is number => typeof color_val === `number`,
675
+ ),
676
+ )
612
677
  : [0, 1],
613
678
  ) as Vec2
614
679
 
@@ -617,37 +682,37 @@
617
682
  let x_scale_fn = $derived(
618
683
  is_time_x
619
684
  ? scaleTime()
620
- .domain([new Date(x_min), new Date(x_max)])
621
- .range([pad.l, width - pad.r])
622
- : create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [
623
- pad.l,
624
- width - pad.r,
625
- ]),
685
+ .domain([new Date(x_min), new Date(x_max)])
686
+ .range([pad.l, width - pad.r])
687
+ : create_scale(
688
+ final_x_axis.scale_type ?? `linear`,
689
+ [x_min, x_max],
690
+ [pad.l, width - pad.r],
691
+ ),
626
692
  )
627
693
 
628
694
  let x2_scale_fn = $derived(
629
695
  is_time_x2
630
696
  ? scaleTime()
631
- .domain([new Date(x2_min), new Date(x2_max)])
632
- .range([pad.l, width - pad.r])
633
- : create_scale(final_x2_axis.scale_type ?? `linear`, [x2_min, x2_max], [
634
- pad.l,
635
- width - pad.r,
636
- ]),
697
+ .domain([new Date(x2_min), new Date(x2_max)])
698
+ .range([pad.l, width - pad.r])
699
+ : create_scale(
700
+ final_x2_axis.scale_type ?? `linear`,
701
+ [x2_min, x2_max],
702
+ [pad.l, width - pad.r],
703
+ ),
637
704
  )
638
705
 
639
706
  let y_scale_fn = $derived(
640
- create_scale(final_y_axis.scale_type ?? `linear`, [y_min, y_max], [
641
- height - pad.b,
642
- pad.t,
643
- ]),
707
+ create_scale(final_y_axis.scale_type ?? `linear`, [y_min, y_max], [height - pad.b, pad.t]),
644
708
  )
645
709
 
646
710
  let y2_scale_fn = $derived(
647
- create_scale(final_y2_axis.scale_type ?? `linear`, [y2_min, y2_max], [
648
- height - pad.b,
649
- pad.t,
650
- ]),
711
+ create_scale(
712
+ final_y2_axis.scale_type ?? `linear`,
713
+ [y2_min, y2_max],
714
+ [height - pad.b, pad.t],
715
+ ),
651
716
  )
652
717
 
653
718
  // All size values from series (for size scale) - extracted in series_value_arrays
@@ -684,6 +749,22 @@
684
749
  })
685
750
  let effective_line_tween = $derived(resolve_line_tween(line_tween, line_tween_load))
686
751
 
752
+ let visible_marker_count = $derived.by(() => {
753
+ if (!styles.show_points) return 0
754
+ let count = 0
755
+ for (const series_data of filtered_series ?? []) {
756
+ if ((series_data.markers ?? DEFAULT_MARKERS).includes(`points`)) {
757
+ count += series_data.filtered_data.length
758
+ }
759
+ }
760
+ return count
761
+ })
762
+
763
+ const default_point_radius = (markers: string): number => {
764
+ const [sparse_radius, dense_radius] = markers.includes(`line`) ? [2.5, 2] : [3, 2.5]
765
+ return visible_marker_count >= DENSE_MARKER_COUNT ? dense_radius : sparse_radius
766
+ }
767
+
687
768
  // Obstacle field for legend/colorbar auto-placement. Sampling only data points lets the
688
769
  // legend land on top of a steep connecting line whose markers are sparse (e.g. y=x^2), so
689
770
  // sample_series_obstacle_points also walks each drawn segment at a fixed pixel cadence.
@@ -699,14 +780,16 @@
699
780
  const active_x_scale = use_x2_scale ? x2_scale_fn : x_scale_fn
700
781
  const active_is_time_x = use_x2_scale ? is_time_x2 : is_time_x
701
782
  const active_y_scale = series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn
702
- const draws_line = styles.show_lines &&
703
- (series_data.markers ?? DEFAULT_MARKERS).includes(`line`)
783
+ const draws_line =
784
+ styles.show_lines && (series_data.markers ?? DEFAULT_MARKERS).includes(`line`)
704
785
 
705
786
  const pixel_points = series_data.filtered_data.map((point) => ({
706
787
  x: active_is_time_x ? active_x_scale(new Date(point.x)) : active_x_scale(point.x),
707
788
  y: active_y_scale(point.y),
708
789
  }))
709
- points.push(...sample_series_obstacle_points(pixel_points, draws_line, SEGMENT_SAMPLE_STEP))
790
+ points.push(
791
+ ...sample_series_obstacle_points(pixel_points, draws_line, SEGMENT_SAMPLE_STEP),
792
+ )
710
793
  }
711
794
  return points
712
795
  })
@@ -791,14 +874,16 @@
791
874
  }
792
875
 
793
876
  return all_regions
794
- .filter((
795
- entry,
796
- ): entry is {
797
- region: FillRegion
798
- source_type: `fill_region` | `error_band`
799
- source_idx: number
800
- hover_key: string
801
- } => entry.region !== null)
877
+ .filter(
878
+ (
879
+ entry,
880
+ ): entry is {
881
+ region: FillRegion
882
+ source_type: `fill_region` | `error_band`
883
+ source_idx: number
884
+ hover_key: string
885
+ } => entry.region !== null,
886
+ )
802
887
  .map(({ region, source_type, source_idx, hover_key }, idx) => {
803
888
  // Hidden fills keep their entry (with empty path_segments -> nothing renders) so the
804
889
  // legend item persists greyed-out and can be toggled back on.
@@ -806,15 +891,15 @@
806
891
  const path_segments = hidden
807
892
  ? []
808
893
  : compute_fill_segments(region, series_with_ids, domains)
809
- .map((seg) =>
810
- generate_fill_path(
811
- seg.upper.map(to_px),
812
- seg.lower.map(to_px),
813
- seg.upper_curve,
814
- seg.lower_curve,
894
+ .map((seg) =>
895
+ generate_fill_path(
896
+ seg.upper.map(to_px),
897
+ seg.lower.map(to_px),
898
+ seg.upper_curve,
899
+ seg.lower_curve,
900
+ ),
815
901
  )
816
- )
817
- .filter((path) => path.length > 0)
902
+ .filter((path) => path.length > 0)
818
903
 
819
904
  // Drop only visible fills with no geometry; keep hidden ones for the legend
820
905
  if (!hidden && path_segments.length === 0) return null
@@ -848,13 +933,12 @@
848
933
  })
849
934
 
850
935
  // Compute ref_lines with index and group by z-index (using shared utilities)
851
- let indexed_ref_lines = $derived(index_ref_lines(ref_lines))
852
- let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines))
936
+ let ref_lines_by_z = $derived(group_ref_lines_by_z(index_ref_lines(ref_lines)))
853
937
 
854
938
  // Calculate best legend placement using continuous grid sampling
855
939
  let legend_placement = $derived.by(() => {
856
- const should_place = legend != null &&
857
- (legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)
940
+ const should_place =
941
+ legend != null && (legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)
858
942
 
859
943
  if (!should_place || !width || !height) return null
860
944
 
@@ -882,10 +966,9 @@
882
966
 
883
967
  // Fallback estimate (with room for tick labels) used before the colorbar first
884
968
  // renders; compute_element_placement measures the real footprint once it's laid out
885
- const is_horizontal = (color_bar.orientation ?? `horizontal`) === `horizontal`
886
- const colorbar_size = is_horizontal
887
- ? { width: 220, height: 56 }
888
- : { width: 56, height: 100 }
969
+ const colorbar_size = colorbar_is_horizontal
970
+ ? COLOR_BAR_DEFAULTS.horizontal_footprint
971
+ : COLOR_BAR_DEFAULTS.vertical_footprint
889
972
 
890
973
  // Build exclusion rects (avoid legend if it's placed)
891
974
  const exclude_rects: Rect[] = []
@@ -910,20 +993,10 @@
910
993
  })
911
994
  })
912
995
 
913
- // Active legend placement (null if user set explicit position)
914
- let active_legend_placement = $derived.by(() => {
915
- if (!legend_placement) return null
916
-
917
- // Skip auto-placement if user set explicit position in style
918
- const legend_style = legend?.style ?? ``
919
- if (
920
- /(^|[;{]\s*)(top|bottom|left|right)\s*:|position\s*:\s*absolute/.test(
921
- legend_style,
922
- )
923
- ) return null
924
-
925
- return legend_placement
926
- })
996
+ // Active legend placement (null if user set explicit position in style)
997
+ let active_legend_placement = $derived(
998
+ legend_placement && !legend_has_explicit_pos ? legend_placement : null,
999
+ )
927
1000
 
928
1001
  // Tweened colorbar/legend coordinates with shared placement stability gating
929
1002
  const colorbar_tween = create_placed_tween({
@@ -966,15 +1039,16 @@
966
1039
  x_scale_for_ticks,
967
1040
  { format: final_x_axis.format },
968
1041
  ),
969
- x2: x2_points.length > 0
970
- ? generate_ticks(
971
- [x2_min, x2_max],
972
- final_x2_axis.scale_type ?? `linear`,
973
- final_x2_axis.ticks,
974
- x2_scale_for_ticks,
975
- { format: final_x2_axis.format },
976
- )
977
- : [],
1042
+ x2:
1043
+ x2_points.length > 0
1044
+ ? generate_ticks(
1045
+ [x2_min, x2_max],
1046
+ final_x2_axis.scale_type ?? `linear`,
1047
+ final_x2_axis.ticks,
1048
+ x2_scale_for_ticks,
1049
+ { format: final_x2_axis.format },
1050
+ )
1051
+ : [],
978
1052
  y: generate_ticks(
979
1053
  [y_min, y_max],
980
1054
  final_y_axis.scale_type ?? `linear`,
@@ -982,15 +1056,16 @@
982
1056
  y_scale_fn,
983
1057
  { default_count: 5 },
984
1058
  ),
985
- y2: y2_points.length > 0
986
- ? generate_ticks(
987
- [y2_min, y2_max],
988
- final_y2_axis.scale_type ?? `linear`,
989
- final_y2_axis.ticks,
990
- y2_scale_fn,
991
- { default_count: 5 },
992
- )
993
- : [],
1059
+ y2:
1060
+ y2_points.length > 0
1061
+ ? generate_ticks(
1062
+ [y2_min, y2_max],
1063
+ final_y2_axis.scale_type ?? `linear`,
1064
+ final_y2_axis.ticks,
1065
+ y2_scale_fn,
1066
+ { default_count: 5 },
1067
+ )
1068
+ : [],
994
1069
  }
995
1070
  })
996
1071
 
@@ -1036,16 +1111,16 @@
1036
1111
  y_axis = { ...y_axis, range: next_y }
1037
1112
 
1038
1113
  // X2 axis: invert screen coords using x2 scale
1039
- const next_x2 = x2_points.length > 0
1040
- ? invert_rect_range(x2_scale_fn, start.x, current.x)
1041
- : null
1114
+ const next_x2 =
1115
+ x2_points.length > 0 ? invert_rect_range(x2_scale_fn, start.x, current.x) : null
1042
1116
  if (next_x2) x2_axis = { ...x2_axis, range: next_x2 }
1043
1117
 
1044
1118
  // Y2 axis: when sync is enabled the y_axis effect derives y2; with sync 'none'
1045
1119
  // y2 must zoom from the rect directly (parity with BarPlot/Histogram/BoxPlot)
1046
- const next_y2 = y2_points.length > 0 && y2_sync_config.mode === `none`
1047
- ? invert_rect_range(y2_scale_fn, start.y, current.y)
1048
- : null
1120
+ const next_y2 =
1121
+ y2_points.length > 0 && y2_sync_config.mode === `none`
1122
+ ? invert_rect_range(y2_scale_fn, start.y, current.y)
1123
+ : null
1049
1124
  if (next_y2) y2_axis = { ...y2_axis, range: next_y2 }
1050
1125
  },
1051
1126
  on_reset: () => {
@@ -1100,9 +1175,7 @@
1100
1175
  const point_cx = tooltip_is_time_x
1101
1176
  ? tooltip_x_scale(new Date(point.x))
1102
1177
  : tooltip_x_scale(point.x)
1103
- const point_cy = (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(
1104
- point.y,
1105
- )
1178
+ const point_cy = (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(point.y)
1106
1179
 
1107
1180
  // Calculate squared screen distance between mouse and point
1108
1181
  const screen_dx = x_rel - point_cx
@@ -1119,11 +1192,7 @@
1119
1192
  }
1120
1193
 
1121
1194
  // Check if the closest point is within the hover threshold
1122
- if (
1123
- closest_point &&
1124
- closest_series &&
1125
- min_screen_dist_sq <= hover_threshold_px_sq
1126
- ) {
1195
+ if (closest_point && closest_series && min_screen_dist_sq <= hover_threshold_px_sq) {
1127
1196
  // Construct handler props synchronously to avoid stale derived reads
1128
1197
  const props = construct_handler_props(closest_point)
1129
1198
  tooltip_point = closest_point
@@ -1198,8 +1267,8 @@
1198
1267
  const new_y = event.clientY - svg_rect.top - legend_drag_offset.y
1199
1268
 
1200
1269
  // Get actual legend dimensions for accurate bounds checking using the bound element reference
1201
- const { width: legend_width, height: legend_height } = legend_element
1202
- .getBoundingClientRect()
1270
+ const { width: legend_width, height: legend_height } =
1271
+ legend_element.getBoundingClientRect()
1203
1272
 
1204
1273
  // Constrain to plot bounds using measured legend size
1205
1274
  const constrained_x = Math.max(0, Math.min(width - legend_width, new_x))
@@ -1245,9 +1314,7 @@
1245
1314
  const cx = handler_is_time_x ? handler_x_scale(new Date(x)) : handler_x_scale(x)
1246
1315
  const cy = (hovered_series.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(y)
1247
1316
  const active_x_config = handler_use_x2 ? final_x2_axis : final_x_axis
1248
- const active_y_config = hovered_series.y_axis === `y2`
1249
- ? final_y2_axis
1250
- : final_y_axis
1317
+ const active_y_config = hovered_series.y_axis === `y2` ? final_y2_axis : final_y_axis
1251
1318
  const coords = {
1252
1319
  x,
1253
1320
  y,
@@ -1264,8 +1331,8 @@
1264
1331
  metadata,
1265
1332
  label: hovered_series.label ?? null,
1266
1333
  series_idx,
1267
- x_formatted: format_value(x, active_x_config.format || `.3~s`),
1268
- y_formatted: format_value(y, active_y_config.format || `.3~s`),
1334
+ x_formatted: format_value_or_num(x, active_x_config.format),
1335
+ y_formatted: format_value_or_num(y, active_y_config.format),
1269
1336
  color_value: color_value ?? null,
1270
1337
  colorbar: {
1271
1338
  value: color_value ?? null,
@@ -1287,9 +1354,7 @@
1287
1354
  // Precompute non-click event names from point_events so we don't rebuild
1288
1355
  // the entries array on every point render.
1289
1356
  let point_event_names = $derived(
1290
- point_events
1291
- ? Object.keys(point_events).filter((name) => name !== `onclick`)
1292
- : [],
1357
+ point_events ? Object.keys(point_events).filter((name) => name !== `onclick`) : [],
1293
1358
  )
1294
1359
 
1295
1360
  // Set theme-aware background when entering fullscreen
@@ -1298,41 +1363,30 @@
1298
1363
  })
1299
1364
 
1300
1365
  // State accessors for shared axis change handler
1366
+ // Spread into existing state in each setter to preserve merged type structure
1301
1367
  const axis_state: AxisChangeState<DataSeries<Metadata>> = {
1302
- get_axis: (axis) => {
1303
- if (axis === `x`) return x_axis
1304
- if (axis === `x2`) return x2_axis
1305
- if (axis === `y`) return y_axis
1306
- return y2_axis
1368
+ axes: {
1369
+ x: { get: () => x_axis, set: (config) => (x_axis = { ...x_axis, ...config }) },
1370
+ x2: { get: () => x2_axis, set: (config) => (x2_axis = { ...x2_axis, ...config }) },
1371
+ y: { get: () => y_axis, set: (config) => (y_axis = { ...y_axis, ...config }) },
1372
+ y2: { get: () => y2_axis, set: (config) => (y2_axis = { ...y2_axis, ...config }) },
1307
1373
  },
1308
- set_axis: (axis, config) => {
1309
- // Spread into existing state to preserve merged type structure
1310
- if (axis === `x`) x_axis = { ...x_axis, ...config }
1311
- else if (axis === `x2`) x2_axis = { ...x2_axis, ...config }
1312
- else if (axis === `y`) y_axis = { ...y_axis, ...config }
1313
- else y2_axis = { ...y2_axis, ...config }
1314
- },
1315
- get_series: () => series,
1316
- set_series: (new_series) => (series = new_series),
1317
- get_loading: () => axis_loading,
1318
- set_loading: (axis) => (axis_loading = axis),
1374
+ series: { get: () => series, set: (next) => (series = next) },
1375
+ loading: { get: () => axis_loading, set: (axis) => (axis_loading = axis) },
1319
1376
  }
1320
1377
 
1321
1378
  // Shared handler + one-shot auto-load bound to this component's state
1322
- const { handle_axis_change, try_auto_load } = create_axis_loader(
1323
- axis_state,
1324
- () => ({ data_loader, on_axis_change, on_error }),
1325
- )
1379
+ const { handle_axis_change, try_auto_load } = create_axis_loader(axis_state, () => ({
1380
+ data_loader,
1381
+ on_axis_change,
1382
+ on_error,
1383
+ }))
1326
1384
  $effect(try_auto_load)
1327
1385
  </script>
1328
1386
 
1329
1387
  {#snippet fill_regions_layer(fills: typeof computed_fills)}
1330
1388
  {#each fills as fill (fill.hover_key)}
1331
- {#each fill.path_segments as
1332
- path_d,
1333
- segment_idx
1334
- (`${fill.id ?? fill.idx}-${segment_idx}`)
1335
- }
1389
+ {#each fill.path_segments as path_d, segment_idx (`${fill.id ?? fill.idx}-${segment_idx}`)}
1336
1390
  <FillArea
1337
1391
  region={fill}
1338
1392
  region_idx={fill.idx}
@@ -1399,7 +1453,7 @@
1399
1453
  bind:clientWidth={width}
1400
1454
  bind:clientHeight={height}
1401
1455
  {...rest}
1402
- class="scatter {rest.class ?? ``}"
1456
+ class={[`scatter`, rest.class]}
1403
1457
  class:fullscreen
1404
1458
  >
1405
1459
  {#if width && height}
@@ -1415,8 +1469,8 @@
1415
1469
  bind:this={svg_element}
1416
1470
  role="application"
1417
1471
  aria-label={rest[`aria-label`] ??
1418
- ([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) ||
1419
- `Scatter plot`)}
1472
+ ([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) ||
1473
+ `Scatter plot`)}
1420
1474
  tabindex="0"
1421
1475
  onfocusin={() => pan_zoom.set_focused(true)}
1422
1476
  onfocusout={() => pan_zoom.set_focused(false)}
@@ -1473,7 +1527,7 @@
1473
1527
  domain={[x_min, x_max]}
1474
1528
  tick_label={(tick) => get_tick_label(tick, final_x_axis.ticks)}
1475
1529
  label_x={width / 2 + (final_x_axis.label_shift?.x ?? 0)}
1476
- label_y={height - pad.b - (final_x_axis.label_shift?.y ?? -40)}
1530
+ label_y={height - pad.b + AXIS_TITLE_OFFSET + (final_x_axis.label_shift?.y ?? 0)}
1477
1531
  axis_loading={axis_loading === `x`}
1478
1532
  on_axis_change={(key) => handle_axis_change(`x`, key)}
1479
1533
  />
@@ -1481,8 +1535,8 @@
1481
1535
  <!-- Current frame indicator -->
1482
1536
  {#if current_x_value != null}
1483
1537
  {@const current_pos_raw = is_time_x
1484
- ? x_scale_fn(new Date(current_x_value))
1485
- : x_scale_fn(current_x_value)}
1538
+ ? x_scale_fn(new Date(current_x_value))
1539
+ : x_scale_fn(current_x_value)}
1486
1540
  {#if isFinite(current_pos_raw)}
1487
1541
  {@const current_pos = current_pos_raw}
1488
1542
  {#if current_pos >= pad.l && current_pos <= width - pad.r}
@@ -1516,7 +1570,8 @@
1516
1570
  tick_label={(tick) => get_tick_label(tick, final_y_axis.ticks)}
1517
1571
  label_x={Math.max(
1518
1572
  12,
1519
- pad.l - (final_y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
1573
+ pad.l -
1574
+ (final_y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
1520
1575
  LABEL_GAP_DEFAULT,
1521
1576
  ) + (final_y_axis.label_shift?.x ?? 0)}
1522
1577
  label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y_axis.label_shift?.y ?? 0)}
@@ -1561,7 +1616,7 @@
1561
1616
  domain={[x2_min, x2_max]}
1562
1617
  tick_label={(tick) => get_tick_label(tick, final_x2_axis.ticks)}
1563
1618
  label_x={width / 2 + (final_x2_axis.label_shift?.x ?? 0)}
1564
- label_y={Math.max(12, pad.t - (final_x2_axis.label_shift?.y ?? 40))}
1619
+ label_y={Math.max(12, pad.t - (final_x2_axis.label_shift?.y ?? AXIS_TITLE_OFFSET))}
1565
1620
  axis_loading={axis_loading === `x2`}
1566
1621
  on_axis_change={(key) => handle_axis_change(`x2`, key)}
1567
1622
  />
@@ -1619,30 +1674,32 @@
1619
1674
  data-series-id={series_data._id}
1620
1675
  clip-path="url(#{clip_path_id})"
1621
1676
  opacity={hovered_legend_series_idx !== null &&
1622
- hovered_legend_series_idx !== series_data.orig_series_idx
1677
+ hovered_legend_series_idx !== series_data.orig_series_idx
1623
1678
  ? 0.25
1624
1679
  : 1}
1625
1680
  >
1626
- {#if series_markers?.includes(`line`)}
1681
+ {#if series_markers.includes(`line`)}
1627
1682
  {@const all_line_points = series_data.x.map((x, idx) => ({
1628
- x,
1629
- y: series_data.y[idx],
1630
- }))}
1683
+ x,
1684
+ y: series_data.y[idx],
1685
+ }))}
1631
1686
  {@const finite_screen_points = all_line_points
1632
- .map((point) => get_screen_coords(point, series_data))
1633
- .filter(([sx, sy]) => isFinite(sx) && isFinite(sy))}
1634
- {@const apply_line_controls = controls.show &&
1635
- (!has_multiple_series ||
1636
- series_data._id === series_with_ids[selected_series_idx]?._id)}
1687
+ .map((point) => get_screen_coords(point, series_data))
1688
+ .filter(([sx, sy]) => isFinite(sx) && isFinite(sy))}
1689
+ {@const apply_line_controls =
1690
+ controls.show &&
1691
+ (!has_multiple_series ||
1692
+ series_data._id === series_with_ids[selected_series_idx]?._id)}
1637
1693
  {@const ls = series_data.line_style}
1638
1694
  {@const tc = (key: string) => apply_line_controls && touched.has(key)}
1639
- {@const color_fallback = ls?.stroke ??
1640
- (Array.isArray(series_data.point_style)
1641
- ? series_data.point_style[0]?.fill
1642
- : series_data.point_style?.fill) ??
1643
- (series_data.color_values?.[0] != null
1644
- ? color_scale_fn(series_data.color_values[0])
1645
- : series_default_color)}
1695
+ {@const color_fallback =
1696
+ ls?.stroke ??
1697
+ (Array.isArray(series_data.point_style)
1698
+ ? series_data.point_style[0]?.fill
1699
+ : series_data.point_style?.fill) ??
1700
+ (series_data.color_values?.[0] != null
1701
+ ? color_scale_fn(series_data.color_values[0])
1702
+ : series_default_color)}
1646
1703
  <Line
1647
1704
  points={finite_screen_points}
1648
1705
  origin={[
@@ -1650,8 +1707,11 @@
1650
1707
  series_data.y_axis === `y2` ? y2_scale_fn(y2_min) : y_scale_fn(y_min),
1651
1708
  ]}
1652
1709
  line_color={(tc(`line.color`) ? styles.line?.color : null) ?? color_fallback}
1653
- line_width={(tc(`line.width`) ? styles.line?.width : null) ?? ls?.stroke_width ?? 2}
1710
+ line_width={(tc(`line.width`) ? styles.line?.width : null) ??
1711
+ ls?.stroke_width ??
1712
+ 2}
1654
1713
  line_dash={(tc(`line.dash`) ? styles.line?.dash : null) ?? ls?.line_dash}
1714
+ curve={ls?.curve}
1655
1715
  area_color="transparent"
1656
1716
  line_tween={effective_line_tween}
1657
1717
  />
@@ -1672,53 +1732,60 @@
1672
1732
  {@const series_default_color = get_series_color(series_data.orig_series_idx ?? 0)}
1673
1733
  {@const series_default_symbol = get_series_symbol(series_data.orig_series_idx ?? 0)}
1674
1734
  <g data-series-id={series_data._id}>
1675
- {#if series_markers?.includes(`points`)}
1676
- {#each series_data.filtered_data as
1677
- point
1678
- (`${point.series_idx}-${point.point_idx}`)
1679
- }
1735
+ {#if series_markers.includes(`points`)}
1736
+ {#each series_data.filtered_data as point (`${point.series_idx}-${point.point_idx}`)}
1680
1737
  {@const label_id = `${point.series_idx}-${point.point_idx}`}
1681
1738
  {@const calculated_label_pos = label_positions[label_id]}
1682
1739
  {@const point_label = point.point_label ?? {}}
1683
- {@const label_style = point_label.auto_placement &&
1684
- actual_label_config.max_neighbors &&
1685
- !calculated_label_pos
1686
- ? {}
1687
- : point_label}
1740
+ {@const label_style =
1741
+ point_label.auto_placement &&
1742
+ actual_label_config.max_neighbors &&
1743
+ !calculated_label_pos
1744
+ ? {}
1745
+ : point_label}
1688
1746
  {@const final_label = calculated_label_pos
1689
- ? {
1690
- ...label_style,
1691
- offset: {
1692
- x: calculated_label_pos.x -
1693
- (is_time_x ? x_scale_fn(new Date(point.x)) : x_scale_fn(point.x)),
1694
- y: calculated_label_pos.y - (series_data.y_axis === `y2`
1695
- ? y2_scale_fn(point.y)
1696
- : y_scale_fn(point.y)),
1697
- },
1698
- }
1699
- : label_style}
1747
+ ? {
1748
+ ...label_style,
1749
+ offset: {
1750
+ x:
1751
+ calculated_label_pos.x -
1752
+ (is_time_x ? x_scale_fn(new Date(point.x)) : x_scale_fn(point.x)),
1753
+ y:
1754
+ calculated_label_pos.y -
1755
+ (series_data.y_axis === `y2`
1756
+ ? y2_scale_fn(point.y)
1757
+ : y_scale_fn(point.y)),
1758
+ },
1759
+ }
1760
+ : label_style}
1700
1761
  {@const [raw_screen_x, raw_screen_y] = get_screen_coords(point, series_data)}
1701
- {@const screen_x = isFinite(raw_screen_x) ? raw_screen_x : x_scale_fn.range()[0]}
1762
+ {@const screen_x = isFinite(raw_screen_x)
1763
+ ? raw_screen_x
1764
+ : x_scale_fn.range()[0]}
1702
1765
  {@const screen_y = isFinite(raw_screen_y)
1703
- ? raw_screen_y
1704
- : (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn).range()[0]}
1705
- {@const apply_controls = controls.show &&
1706
- (!has_multiple_series ||
1707
- series_data._id === series_with_ids[selected_series_idx]?._id)}
1766
+ ? raw_screen_y
1767
+ : (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn).range()[0]}
1768
+ {@const apply_controls =
1769
+ controls.show &&
1770
+ (!has_multiple_series ||
1771
+ series_data._id === series_with_ids[selected_series_idx]?._id)}
1708
1772
  {@const pt = point.point_style}
1709
1773
  {@const tc = (key: string) => apply_controls && touched.has(key)}
1710
- {@const computed_radius = point.size_value != null
1711
- ? size_scale_fn(point.size_value)
1712
- : (tc(`point.size`) ? styles.point?.size : null) ?? pt?.radius ?? 4}
1774
+ {@const computed_radius =
1775
+ point.size_value != null
1776
+ ? size_scale_fn(point.size_value)
1777
+ : ((tc(`point.size`) ? styles.point?.size : null) ??
1778
+ pt?.radius ??
1779
+ default_point_radius(series_markers))}
1713
1780
  <ScatterPoint
1714
1781
  x={screen_x}
1715
1782
  y={screen_y}
1716
1783
  is_dimmed={hovered_legend_series_idx !== null &&
1717
1784
  hovered_legend_series_idx !== point.series_idx}
1718
1785
  is_hovered={tooltip_point?.series_idx === point.series_idx &&
1719
- tooltip_point?.point_idx === point.point_idx}
1786
+ tooltip_point?.point_idx === point.point_idx}
1720
1787
  is_selected={selected_point?.series_idx === point.series_idx &&
1721
- selected_point?.point_idx === point.point_idx}
1788
+ selected_point?.point_idx === point.point_idx}
1722
1789
  leader_line_threshold={actual_label_config.leader_line_threshold}
1723
1790
  style={{
1724
1791
  symbol_type: pt?.symbol_type ?? series_default_symbol,
@@ -1726,18 +1793,21 @@
1726
1793
  radius: computed_radius,
1727
1794
  stroke_width:
1728
1795
  (tc(`point.stroke_width`) ? styles.point?.stroke_width : null) ??
1729
- pt?.stroke_width ?? 1,
1796
+ pt?.stroke_width ??
1797
+ 1,
1730
1798
  stroke:
1731
1799
  (tc(`point.stroke_color`) ? styles.point?.stroke_color : null) ??
1732
- pt?.stroke ?? `#000`,
1800
+ pt?.stroke ??
1801
+ `#000`,
1733
1802
  stroke_opacity:
1734
1803
  (tc(`point.stroke_opacity`) ? styles.point?.stroke_opacity : null) ??
1735
- pt?.stroke_opacity ?? 1,
1736
- fill_opacity: (tc(`point.opacity`) ? styles.point?.opacity : null) ??
1737
- pt?.fill_opacity ?? 1,
1738
- cursor: (on_point_click || point_events?.onclick)
1739
- ? `pointer`
1740
- : undefined,
1804
+ pt?.stroke_opacity ??
1805
+ 1,
1806
+ fill_opacity:
1807
+ (tc(`point.opacity`) ? styles.point?.opacity : null) ??
1808
+ pt?.fill_opacity ??
1809
+ 1,
1810
+ cursor: on_point_click || point_events?.onclick ? `pointer` : undefined,
1741
1811
  }}
1742
1812
  hover={point.point_hover ?? {}}
1743
1813
  label={final_label}
@@ -1745,15 +1815,17 @@
1745
1815
  {point_tween}
1746
1816
  origin={{ x: plot_center_x, y: plot_center_y }}
1747
1817
  --point-fill-color={point.color_value != null
1748
- ? color_scale_fn(point.color_value)
1749
- : (tc(`point.color`) ? styles.point?.color : null) ?? pt?.fill ??
1750
- series_default_color}
1818
+ ? color_scale_fn(point.color_value)
1819
+ : ((tc(`point.color`) ? styles.point?.color : null) ??
1820
+ pt?.fill ??
1821
+ series_default_color)}
1751
1822
  {...point_events &&
1752
- Object.fromEntries(
1753
- point_event_names.map((name) => [name, (event: Event) =>
1754
- point_events?.[name]?.({ point, event })]
1755
- ),
1756
- )}
1823
+ Object.fromEntries(
1824
+ point_event_names.map((name) => [
1825
+ name,
1826
+ (event: Event) => point_events?.[name]?.({ point, event }),
1827
+ ]),
1828
+ )}
1757
1829
  onclick={(event: MouseEvent) => {
1758
1830
  // Call user-provided onclick handler first if it exists
1759
1831
  point_events?.onclick?.({ point, event })
@@ -1773,16 +1845,33 @@
1773
1845
  {@render fill_regions_layer(fills_by_z.above_all)}
1774
1846
  <!-- Reference lines: above all -->
1775
1847
  {@render ref_lines_layer(ref_lines_by_z.above_all)}
1848
+
1849
+ <!-- Marginal distribution strips -->
1850
+ <PlotMarginals
1851
+ marginals={resolved_marginals}
1852
+ series={marginal_series}
1853
+ {width}
1854
+ {height}
1855
+ {pad}
1856
+ has_axis={marginal_has_axis}
1857
+ axes={{
1858
+ x1: marginal_axis(x_scale_fn, [x_min, x_max], final_x_axis),
1859
+ x2: marginal_axis(x2_scale_fn, [x2_min, x2_max], final_x2_axis),
1860
+ y1: marginal_axis(y_scale_fn, [y_min, y_max], final_y_axis),
1861
+ y2: marginal_axis(y2_scale_fn, [y2_min, y2_max], final_y2_axis),
1862
+ }}
1863
+ id={component_id}
1864
+ />
1776
1865
  </svg>
1777
1866
 
1778
1867
  <!-- Tooltip overlay above all plot overlays (legend, colorbar) -->
1779
1868
  {#if handler_props && hovered && tooltip_point}
1780
1869
  {@const { point_label, series_idx } = tooltip_point}
1781
1870
  {@const tooltip_bg_color = pick_tooltip_bg(
1782
- tooltip_point,
1783
- series_with_ids[series_idx],
1784
- color_scale_fn,
1785
- )}
1871
+ tooltip_point,
1872
+ series_with_ids[series_idx],
1873
+ color_scale_fn,
1874
+ )}
1786
1875
  <PlotTooltip
1787
1876
  x={handler_props.cx}
1788
1877
  y={handler_props.cy}
@@ -1800,9 +1889,10 @@
1800
1889
  {@html sanitize_html(hp.x_axis.label || `x`)}: {hp.x_formatted}<br />
1801
1890
  {@html sanitize_html(hp.y_axis.label || `y`)}: {hp.y_formatted}
1802
1891
  {#if hp.colorbar?.value != null}
1803
- <br />{@html sanitize_html(hp.colorbar.title || `Color`)}: {
1804
- format_value(hp.colorbar.value, hp.colorbar.tick_format || `.3~g`)
1805
- }
1892
+ <br />{@html sanitize_html(hp.colorbar.title || `Color`)}: {format_value(
1893
+ hp.colorbar.value,
1894
+ hp.colorbar.tick_format || `.3~g`,
1895
+ )}
1806
1896
  {/if}
1807
1897
  {/if}
1808
1898
  </PlotTooltip>
@@ -1813,10 +1903,9 @@
1813
1903
  <ScatterPlotControls
1814
1904
  toggle_props={{
1815
1905
  ...controls.toggle_props,
1816
- style:
1817
- `--ctrl-btn-right: var(--fullscreen-btn-offset, 30px); top: var(--ctrl-btn-top, 5pt); ${
1818
- controls.toggle_props?.style ?? ``
1819
- }`,
1906
+ style: `--ctrl-btn-right: var(--fullscreen-btn-offset, 30px); top: var(--ctrl-btn-top, 5pt); ${
1907
+ controls.toggle_props?.style ?? ``
1908
+ }`,
1820
1909
  }}
1821
1910
  pane_props={controls.pane_props}
1822
1911
  bind:x_axis
@@ -1841,11 +1930,11 @@
1841
1930
  <!-- Color Bar -->
1842
1931
  {#if color_bar && all_color_values.length > 0 && color_bar_placement}
1843
1932
  {@const color_domain = [
1844
- (typeof color_scale === `string` ? undefined : color_scale.value_range)?.[0] ??
1845
- auto_color_range[0],
1846
- (typeof color_scale === `string` ? undefined : color_scale.value_range)?.[1] ??
1847
- auto_color_range[1],
1848
- ] as Vec2}
1933
+ (typeof color_scale === `string` ? undefined : color_scale.value_range)?.[0] ??
1934
+ auto_color_range[0],
1935
+ (typeof color_scale === `string` ? undefined : color_scale.value_range)?.[1] ??
1936
+ auto_color_range[1],
1937
+ ] as Vec2}
1849
1938
  <div
1850
1939
  bind:this={colorbar_element}
1851
1940
  onmouseenter={() => colorbar_tween.set_locked(true)}
@@ -1854,9 +1943,10 @@
1854
1943
  role="img"
1855
1944
  aria-label="Color scale legend"
1856
1945
  style={`${
1857
- // explicit wrapper_style or auto-outside places the colorbar; else auto-placement coords
1858
- effective_cbar_wrapper_style ??
1859
- `position: absolute; left: ${colorbar_tween.coords.current.x}px; top: ${colorbar_tween.coords.current.y}px`}; pointer-events: auto;`}
1946
+ // explicit wrapper_style or auto-outside places the colorbar; else auto-placement coords
1947
+ effective_cbar_wrapper_style ??
1948
+ `position: absolute; left: ${colorbar_tween.coords.current.x}px; top: ${colorbar_tween.coords.current.y}px`
1949
+ }; pointer-events: auto;`}
1860
1950
  >
1861
1951
  <ColorBar
1862
1952
  tick_labels={4}
@@ -1865,7 +1955,8 @@
1865
1955
  color_scale_domain={color_domain}
1866
1956
  scale_type={typeof color_scale === `string` ? undefined : color_scale.type}
1867
1957
  range={color_domain?.every((val) => val != null) ? color_domain : undefined}
1868
- bar_style="width: 220px; height: 16px; {color_bar?.style ?? ``}"
1958
+ bar_style="width: {COLOR_BAR_DEFAULTS.width}px; height: {COLOR_BAR_DEFAULTS.horizontal_bar_height}px; {color_bar?.style ??
1959
+ ``}"
1869
1960
  {...color_bar}
1870
1961
  wrapper_style={effective_cbar_wrapper_style ? `height: 100%; width: 100%;` : ``}
1871
1962
  />
@@ -1874,24 +1965,25 @@
1874
1965
 
1875
1966
  <!-- Legend -->
1876
1967
  <!-- Only render if multiple series or if legend prop was explicitly provided by user (even if empty object) -->
1877
- {#if legend != null && legend_data.length > 0 &&
1878
- (legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)}
1968
+ {#if legend != null && legend_data.length > 0 && (legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)}
1879
1969
  {@const default_x = pad.l + 10}
1880
1970
  {@const default_y = pad.t + 10}
1881
- {@const current_x = legend_is_dragging && legend_manual_position
1882
- ? legend_manual_position.x
1883
- : legend_auto_outside
1884
- ? legend_outside_x
1885
- : legend_placement
1886
- ? legend_tween.coords.current.x
1887
- : default_x}
1888
- {@const current_y = legend_is_dragging && legend_manual_position
1889
- ? legend_manual_position.y
1890
- : legend_auto_outside
1891
- ? legend_outside_y
1892
- : legend_placement
1893
- ? legend_tween.coords.current.y
1894
- : default_y}
1971
+ {@const current_x =
1972
+ legend_is_dragging && legend_manual_position
1973
+ ? legend_manual_position.x
1974
+ : legend_auto_outside
1975
+ ? legend_outside_x
1976
+ : legend_placement
1977
+ ? legend_tween.coords.current.x
1978
+ : default_x}
1979
+ {@const current_y =
1980
+ legend_is_dragging && legend_manual_position
1981
+ ? legend_manual_position.y
1982
+ : legend_auto_outside
1983
+ ? legend_outside_y
1984
+ : legend_placement
1985
+ ? legend_tween.coords.current.y
1986
+ : default_y}
1895
1987
  <PlotLegend
1896
1988
  bind:root_element={legend_element}
1897
1989
  series_data={legend_data}
@@ -1907,15 +1999,14 @@
1907
1999
  hovered_fill_key = fill && fill.visible !== false ? fill.hover_key : null
1908
2000
  hovered_legend_series_idx = null
1909
2001
  } else {
1910
- hovered_legend_series_idx = item != null && item.series_idx >= 0
1911
- ? item.series_idx
1912
- : null
2002
+ hovered_legend_series_idx =
2003
+ item != null && item.series_idx >= 0 ? item.series_idx : null
1913
2004
  hovered_fill_key = null
1914
2005
  }
1915
2006
  }}
1916
2007
  active_series_idx={tooltip_point?.series_idx ?? hovered_legend_series_idx}
1917
- active_fill_idx={computed_fills.find((fill) => fill.hover_key === hovered_fill_key)?.idx ??
1918
- null}
2008
+ active_fill_idx={computed_fills.find((fill) => fill.hover_key === hovered_fill_key)
2009
+ ?.idx ?? null}
1919
2010
  draggable={legend?.draggable ?? true}
1920
2011
  {...legend}
1921
2012
  on_toggle={legend?.on_toggle ?? legend_vis.on_toggle}
@@ -1927,7 +2018,7 @@
1927
2018
  fill_regions = fill_regions.map((region, idx) =>
1928
2019
  idx === source_idx
1929
2020
  ? { ...region, visible: !(region.visible !== false) }
1930
- : region
2021
+ : region,
1931
2022
  )
1932
2023
  }
1933
2024
  }}
@@ -1938,7 +2029,9 @@
1938
2029
  // Only fill_regions can be toggled (error_bands are not bindable)
1939
2030
  if (source_type !== `fill_region`) return
1940
2031
  // Toggle: if only this fill is visible, show all; otherwise show only this one
1941
- const visible_count = fill_regions.filter((region) => region.visible !== false).length
2032
+ const visible_count = fill_regions.filter(
2033
+ (region) => region.visible !== false,
2034
+ ).length
1942
2035
  const this_visible = fill_regions[source_idx]?.visible !== false
1943
2036
  if (visible_count === 1 && this_visible) {
1944
2037
  // Show all fills
@@ -2024,7 +2117,9 @@
2024
2117
  div.scatter :global(.pane-toggle),
2025
2118
  div.scatter .header-controls {
2026
2119
  opacity: 0;
2027
- transition: opacity 0.2s, background-color 0.2s;
2120
+ transition:
2121
+ opacity 0.2s,
2122
+ background-color 0.2s;
2028
2123
  }
2029
2124
  div.scatter:hover :global(.pane-toggle),
2030
2125
  div.scatter:hover .header-controls,
@@ -2051,10 +2146,7 @@
2051
2146
  color: var(--text-color);
2052
2147
  white-space: nowrap;
2053
2148
  /* Use line-height to center text vertically without flexbox */
2054
- line-height: var(
2055
- --scatter-axis-label-line-height,
2056
- 20px
2057
- ); /* Match foreignObject height */
2149
+ line-height: var(--scatter-axis-label-line-height, 20px); /* Match foreignObject height */
2058
2150
  display: block;
2059
2151
  }
2060
2152
  .current-frame-indicator {