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,8 +3,6 @@
3
3
  generics="Metadata extends Record<string, unknown> = Record<string, unknown>"
4
4
  >
5
5
  import type { D3InterpolateName } from '../../colors'
6
- import { pick_contrast_color } from '../../colors'
7
- import { export_svg_as_png, export_svg_as_svg } from '../../io/export'
8
6
  import { format_value } from '../../labels'
9
7
  import { FullscreenToggle, set_fullscreen_bg } from '../../layout'
10
8
  import { DEG_TO_RAD, type Vec2 } from '../../math'
@@ -21,15 +19,37 @@
21
19
  SunburstValueMode,
22
20
  } from '..'
23
21
  import { ColorBar, PlotLegend, PlotTooltip, SunburstControls } from '..'
24
- import { closest_data_idx, get_relative_coords } from '../core/interactions'
25
- import {
26
- compute_element_placement,
27
- filter_padding,
28
- measure_text_width,
29
- } from '../core/layout'
22
+ import { closest_data_idx } from '../core/interactions'
23
+ import { compute_element_placement, filter_padding } from '../core/layout'
30
24
  import type { Sides } from '../core/layout'
31
- import { create_color_scale } from '../core/scales'
32
- import { arc_label_transform, arrow_nav_target, project_arcs } from './render'
25
+ import { SCALE_DEFAULTS } from '../core/types'
26
+ import {
27
+ ancestor_chain,
28
+ compute_metric_colors,
29
+ compute_node_dim,
30
+ compute_node_infos,
31
+ handle_hierarchy_escape,
32
+ hierarchy_legend_items,
33
+ is_activation_key,
34
+ node_handler_props,
35
+ pointer_pos,
36
+ prune_muted_ids,
37
+ safe_hierarchy_layout,
38
+ selection_within,
39
+ svg_label_font,
40
+ toggle_muted,
41
+ } from '../core/utils/hierarchy-chart'
42
+ import {
43
+ export_hierarchy_chart,
44
+ make_cached_contrast,
45
+ make_cached_text_width,
46
+ node_display_name,
47
+ } from '../core/utils/hierarchy-labels'
48
+ import {
49
+ arc_label_transform,
50
+ arrow_nav_target,
51
+ project_arcs,
52
+ } from './render'
33
53
  import type { ScreenArc as ScreenArcOf } from './render'
34
54
  import { compute_sunburst_layout, type PositionedArc } from './sunburst'
35
55
  import { DEFAULTS } from '../../settings'
@@ -63,7 +83,7 @@
63
83
  zoom_root_id = $bindable(null),
64
84
  show_breadcrumbs = $bindable(DEFAULTS.sunburst.show_breadcrumbs),
65
85
  color_values,
66
- color_scale = `interpolateViridis`,
86
+ color_scale = SCALE_DEFAULTS.scheme,
67
87
  color_range,
68
88
  colorbar = {},
69
89
  export_buttons = true,
@@ -91,66 +111,69 @@
91
111
  header_controls,
92
112
  controls_extra,
93
113
  ...rest
94
- }: HTMLAttributes<HTMLDivElement> & Omit<BasePlotProps, `change`> & {
95
- data?: SunburstNode<Metadata> | SunburstNode<Metadata>[]
96
- shape?: SunburstShape // polar rings (sunburst) or stacked rows (icicle)
97
- value_mode?: SunburstValueMode
98
- sort?: SunburstSort
99
- level_lighten?: number
100
- // Aggregate sibling arcs below this fraction of the total into one 'Other' leaf
101
- // per parent (only when >= 2 qualify); 0 disables
102
- min_fraction?: number
103
- other_label?: string
104
- max_depth?: number // rings shown below the current zoom root (0 = all)
105
- inner_radius?: number // center hole as fraction of outer radius
106
- pad_angle?: number // degrees between sibling arcs
107
- show_labels?: boolean
108
- label_rotation?: SunburstLabelRotation
109
- label_text?: SunburstLabelText // what labels display (plotly textinfo equivalent)
110
- zoom_on_click?: boolean
111
- zoom_root_id?: string | number | null // id of the arc the view is rooted on
112
- show_breadcrumbs?: boolean // clickable ancestor trail when zoomed
113
- // Color arcs by a numeric metric (continuous colormap) instead of categorical
114
- // inheritance; return null to keep an arc's categorical color
115
- color_values?: (arc: PositionedArc<Metadata>) => number | null
116
- color_scale?: D3InterpolateName
117
- color_range?: Vec2 // defaults to the metric's [min, max]
118
- colorbar?: ComponentProps<typeof ColorBar> | null // null hides it
119
- export_buttons?: boolean // SVG/PNG download buttons in the controls pane
120
- export_filename?: string
121
- tween?: TweenOptions<{ x0: number; x1: number; y0: number; n_rings: number }>
122
- value_format?: string
123
- padding?: Sides
124
- legend?: LegendConfig | null
125
- show_legend?: boolean
126
- tooltip?: Snippet<[SunburstNodeHandlerProps<Metadata>]>
127
- // Fully replace the default arc path. NOTE: this also replaces the built-in
128
- // hover/focus/click + tooltip wiring, so re-implement any interactivity you
129
- // need inside the snippet.
130
- arc_content?: Snippet<
131
- [{ arc: PositionedArc<Metadata>; a0: number; a1: number; r0: number; r1: number }]
132
- >
133
- center_content?: Snippet<
134
- [{ root: PositionedArc<Metadata> | null; radius: number; zoomed: boolean }]
135
- >
136
- change?: (data: SunburstNodeHandlerProps<Metadata> | null) => void
137
- on_node_click?: (
138
- data: SunburstNodeHandlerProps<Metadata> & { event: MouseEvent | KeyboardEvent },
139
- ) => void
140
- on_node_hover?: (
141
- data:
142
- | (SunburstNodeHandlerProps<Metadata> & { event: MouseEvent | FocusEvent })
143
- | null,
144
- ) => void
145
- on_zoom?: (data: { root: SunburstNodeHandlerProps<Metadata> | null }) => void
146
- header_controls?: Snippet<[{ height: number; width: number; fullscreen: boolean }]>
147
- controls_extra?: Snippet<[{ zoom_root_id: string | number | null }]>
148
- } = $props()
114
+ }: HTMLAttributes<HTMLDivElement> &
115
+ Omit<BasePlotProps, `change`> & {
116
+ data?: SunburstNode<Metadata> | SunburstNode<Metadata>[]
117
+ shape?: SunburstShape // polar rings (sunburst) or stacked rows (icicle)
118
+ value_mode?: SunburstValueMode
119
+ sort?: SunburstSort
120
+ level_lighten?: number
121
+ // Aggregate sibling arcs below this fraction of the total into one 'Other' leaf
122
+ // per parent (only when >= 2 qualify); 0 disables
123
+ min_fraction?: number
124
+ other_label?: string
125
+ max_depth?: number // rings shown below the current zoom root (0 = all)
126
+ inner_radius?: number // center hole as fraction of outer radius
127
+ pad_angle?: number // degrees between sibling arcs
128
+ show_labels?: boolean
129
+ label_rotation?: SunburstLabelRotation
130
+ label_text?: SunburstLabelText // what labels display (plotly textinfo equivalent)
131
+ zoom_on_click?: boolean
132
+ zoom_root_id?: string | number | null // id of the arc the view is rooted on
133
+ show_breadcrumbs?: boolean // clickable ancestor trail when zoomed
134
+ // Color arcs by a numeric metric (continuous colormap) instead of categorical
135
+ // inheritance; return null to keep an arc's categorical color
136
+ color_values?: (arc: PositionedArc<Metadata>) => number | null
137
+ color_scale?: D3InterpolateName
138
+ color_range?: Vec2 // defaults to the metric's [min, max]
139
+ colorbar?: ComponentProps<typeof ColorBar> | null // null hides it
140
+ export_buttons?: boolean // SVG/PNG download buttons in the controls pane
141
+ export_filename?: string
142
+ tween?: TweenOptions<{ x0: number; x1: number; y0: number; n_rings: number }>
143
+ value_format?: string
144
+ padding?: Sides
145
+ legend?: LegendConfig | null
146
+ show_legend?: boolean
147
+ tooltip?: Snippet<[SunburstNodeHandlerProps<Metadata>]>
148
+ // Fully replace the default arc path. NOTE: this also replaces the built-in
149
+ // hover/focus/click + tooltip wiring, so re-implement any interactivity you
150
+ // need inside the snippet.
151
+ arc_content?: Snippet<
152
+ [{ arc: PositionedArc<Metadata>; a0: number; a1: number; r0: number; r1: number }]
153
+ >
154
+ center_content?: Snippet<
155
+ [{ root: PositionedArc<Metadata> | null; radius: number; zoomed: boolean }]
156
+ >
157
+ change?: (data: SunburstNodeHandlerProps<Metadata> | null) => void
158
+ on_node_click?: (
159
+ data: SunburstNodeHandlerProps<Metadata> & { event: MouseEvent | KeyboardEvent },
160
+ ) => void
161
+ on_node_hover?: (
162
+ data: (SunburstNodeHandlerProps<Metadata> & { event: MouseEvent | FocusEvent }) | null,
163
+ ) => void
164
+ on_zoom?: (data: { root: SunburstNodeHandlerProps<Metadata> | null }) => void
165
+ header_controls?: Snippet<[{ height: number; width: number; fullscreen: boolean }]>
166
+ controls_extra?: Snippet<[{ zoom_root_id: string | number | null }]>
167
+ } = $props()
149
168
 
150
169
  let [width, height] = $state([0, 0])
151
170
  let wrapper: HTMLDivElement | undefined = $state()
152
171
  let svg_element: SVGSVGElement | null = $state(null)
153
172
  let center_el: SVGCircleElement | null = $state(null)
173
+ // Unique per instance so multiple sunbursts on one page don't collide on
174
+ // the hatch pattern's SVG id.
175
+ const uid = $props.id()
176
+ const hatch_pattern_id = `sunburst-hatch-${uid}`
154
177
 
155
178
  let hovered_idx = $state<number | null>(null)
156
179
  let hover_info = $state<SunburstNodeHandlerProps<Metadata> | null>(null)
@@ -171,28 +194,18 @@
171
194
  )
172
195
  let inner_height = $derived(avail_height - colorbar_reserve)
173
196
 
174
- // Degrade to an empty layout (instead of crashing the host page) on invalid data.
175
197
  // Layout depends only on data/value semantics - not on size or zoom.
176
- let layout = $derived.by(() => {
177
- try {
178
- return compute_sunburst_layout(data, {
179
- value_mode,
180
- sort,
181
- level_lighten,
182
- min_fraction,
183
- other_label,
184
- })
185
- } catch (err) {
186
- console.error(err)
187
- return { arcs: [], root: null, max_depth: 0 }
188
- }
189
- })
190
- let arc_by_id = $derived(new Map(layout.arcs.map((arc) => [arc.id, arc])))
191
-
192
- // Resolve the zoom root; stale ids (e.g. after a data swap) fall back to the root
193
- let zoom_root = $derived(
194
- (zoom_root_id != null ? arc_by_id.get(zoom_root_id) : null) ?? layout.root,
198
+ let layout = $derived(
199
+ safe_hierarchy_layout(data, {
200
+ value_mode,
201
+ sort,
202
+ level_lighten,
203
+ min_fraction,
204
+ other_label,
205
+ }),
195
206
  )
207
+ // Resolve the zoom root; stale ids (e.g. after a data swap) fall back to the root
208
+ let zoom_root = $derived(layout.arcs.find((arc) => arc.id === zoom_root_id) ?? layout.root)
196
209
  let zoomed = $derived((zoom_root?.depth ?? 0) > 0)
197
210
 
198
211
  // Drop muted ids that no longer exist when data changes (untrack avoids a
@@ -200,11 +213,9 @@
200
213
  // Hover/focus state is index-based, so a layout swap would otherwise leave a stale
201
214
  // tooltip and highlight whatever unrelated node now occupies the old index.
202
215
  $effect(() => {
203
- const valid = new Set(
204
- layout.arcs.filter((arc) => arc.depth === 1).map((arc) => arc.id),
205
- )
216
+ const { arcs } = layout
206
217
  untrack(() => {
207
- for (const id of muted_ids) if (!valid.has(id)) muted_ids.delete(id)
218
+ prune_muted_ids(arcs, muted_ids)
208
219
  set_arc_hover(null)
209
220
  focused_idx = null
210
221
  })
@@ -274,7 +285,7 @@
274
285
  const screen_path = (screen: ScreenArc): string =>
275
286
  shape === `icicle`
276
287
  ? `M${screen.a0},${screen.r0}H${screen.a1}V${screen.r1}H${screen.a0}Z`
277
- : arc_gen(screen) ?? ``
288
+ : (arc_gen(screen) ?? ``)
278
289
 
279
290
  // The chart group's transform: sunburst draws around the center, icicle from the
280
291
  // top-left of the padded plot area
@@ -293,25 +304,10 @@
293
304
  }
294
305
 
295
306
  // Continuous metric coloring: when color_values is given, arcs are colored by their
296
- // metric on a d3 colormap (arcs returning null keep their categorical color).
297
- // The user accessor runs exactly once per arc.
298
- let metric = $derived.by<{ range: Vec2; colors: string[] } | null>(() => {
299
- if (!color_values) return null
300
- const vals = layout.arcs.map((arc) => {
301
- const val = arc.depth === 0 ? null : color_values(arc)
302
- return val != null && Number.isFinite(val) ? val : null
303
- })
304
- const finite = vals.filter((val) => val != null)
305
- if (finite.length === 0) return null
306
- const range = color_range ?? [Math.min(...finite), Math.max(...finite)]
307
- const scale = create_color_scale({ scheme: color_scale, value_range: range }, range)
308
- return {
309
- range,
310
- colors: vals.map((val, idx) =>
311
- val == null ? layout.arcs[idx].color : `${scale(val)}`
312
- ),
313
- }
314
- })
307
+ // metric on a d3 colormap (arcs returning null keep their categorical color)
308
+ let metric = $derived(
309
+ compute_metric_colors(layout.arcs, color_values, color_scale, color_range),
310
+ )
315
311
  const arc_color = (arc: PositionedArc<Metadata>): string =>
316
312
  metric?.colors[arc.node_idx] ?? arc.color
317
313
  // release the colorbar's reserved chart space when it's not rendered
@@ -319,83 +315,30 @@
319
315
  if (!metric || colorbar == null) colorbar_height = 0
320
316
  })
321
317
 
322
- // Predicate keeping the hovered arc + its ancestors/descendants fully opaque.
323
- // Pre-order indexing makes both tests O(1): a subtree is the contiguous index
324
- // range [node_idx, subtree_end].
325
- let active = $derived.by(() => {
326
- if (hovered_idx == null) return null
327
- const hov = layout.arcs[hovered_idx]
328
- if (!hov) return null
329
- return (arc: PositionedArc<Metadata>): boolean =>
330
- (arc.node_idx >= hov.node_idx && arc.node_idx <= hov.subtree_end) ||
331
- (hov.node_idx >= arc.node_idx && hov.node_idx <= arc.subtree_end)
332
- })
333
-
334
- const is_muted = (arc: PositionedArc<Metadata>): boolean =>
335
- arc.path.length > 0 && muted_ids.has(arc.path[0])
336
-
337
- const MUTED_OPACITY = 0.12
338
- const arc_opacity = (arc: PositionedArc<Metadata>): number => {
339
- if (is_muted(arc)) return MUTED_OPACITY
340
- if (active && !active(arc)) return 0.3
341
- return 1
342
- }
318
+ // Hovered arc + its ancestors/descendants stay fully opaque, others dim
319
+ let arc_dim = $derived(compute_node_dim(layout.arcs, muted_ids, hovered_idx))
343
320
 
344
321
  // Black/white label text, whichever contrasts with the arc's fill (light arcs from
345
- // explicit colors or level_lighten would hide white labels, esp. when highlighted).
346
- // Memoized per color string - parsing/luminance would otherwise run per label per
347
- // animation frame, and distinct arc colors are few.
348
- const contrast_cache = new Map<string, string>()
349
- const label_color = (arc: PositionedArc<Metadata>): string => {
350
- const fill = arc_color(arc)
351
- let contrast = contrast_cache.get(fill)
352
- if (contrast === undefined) {
353
- contrast = pick_contrast_color({ bg_color: fill })
354
- contrast_cache.set(fill, contrast)
355
- }
356
- return contrast
357
- }
322
+ // explicit colors or level_lighten would hide white labels, esp. when highlighted)
323
+ const contrast_for = make_cached_contrast()
358
324
 
359
- // Parent arc of an arc (null for the root) and its display name
360
- const parent_of = (arc: PositionedArc<Metadata>): PositionedArc<Metadata> | null =>
361
- arc.parent_idx != null ? layout.arcs[arc.parent_idx] : null
362
- const arc_name = (arc: PositionedArc<Metadata>): string => arc.label ?? `${arc.id}`
325
+ const make_node_props = (arc: PositionedArc<Metadata>): SunburstNodeHandlerProps<Metadata> =>
326
+ node_handler_props(layout.arcs, arc, arc_color(arc))
363
327
 
364
- function make_node_props(
365
- arc: PositionedArc<Metadata>,
366
- ): SunburstNodeHandlerProps<Metadata> {
367
- // Handler props are the arc minus its screen geometry, plus the parent id
368
- const { x0, x1, y0, y1, subtree_end, parent_idx, ...node } = arc
369
- return {
370
- ...node,
371
- type: `node`,
372
- color: arc_color(arc),
373
- parent_id: parent_of(arc)?.id ?? null,
374
- }
375
- }
376
-
377
- // Anchor the tooltip at the cursor (mouse hover) so it follows the pointer across
378
- // wide arcs; fall back to the arc centroid on keyboard focus (no cursor).
379
- const event_pos = (event?: MouseEvent | FocusEvent): { x: number; y: number } | null =>
380
- event instanceof MouseEvent ? get_relative_coords(event, svg_element) : null
381
-
382
- function set_arc_hover(
383
- screen: ScreenArc | null,
384
- event?: MouseEvent | FocusEvent,
385
- ) {
328
+ function set_arc_hover(screen: ScreenArc | null, event?: MouseEvent | FocusEvent) {
386
329
  // Same arc as before: only the cursor anchor moves - skip rebuilding the handler
387
330
  // payload and re-firing change/on_node_hover on every mousemove within an arc.
388
331
  // Requires hover_info: legend item hover sets hovered_idx alone (for dimming), and
389
332
  // skipping then would leave the arc's own tooltip permanently suppressed.
390
333
  if (screen && screen.arc.node_idx === hovered_idx && hover_info) {
391
- hover_pos = event_pos(event) ?? hover_pos
334
+ hover_pos = pointer_pos(event, svg_element) ?? hover_pos
392
335
  return
393
336
  }
394
337
  if (screen) {
395
338
  hovered = true
396
339
  hovered_idx = screen.arc.node_idx
397
340
  hover_info = make_node_props(screen.arc)
398
- hover_pos = event_pos(event) ?? arc_center(screen)
341
+ hover_pos = pointer_pos(event, svg_element) ?? arc_center(screen)
399
342
  change(hover_info)
400
343
  if (event) on_node_hover?.({ ...hover_info, event })
401
344
  } else {
@@ -412,7 +355,7 @@
412
355
 
413
356
  const screen_arc_from_event = (event: Event): ScreenArc | null => {
414
357
  const idx = closest_data_idx(event, `data-sunburst-node-idx`, svg_element)
415
- return idx == null ? null : screen_arcs[idx] ?? null
358
+ return idx == null ? null : (screen_arcs[idx] ?? null)
416
359
  }
417
360
 
418
361
  function handle_arc_hover_event(event: MouseEvent | FocusEvent) {
@@ -424,25 +367,15 @@
424
367
 
425
368
  // Re-root the view on the given arc (or the data root when null) and notify
426
369
  function zoom_to(arc: PositionedArc<Metadata> | null) {
427
- zoom_root_id = arc && arc.depth > 0 ? arc.id : null
370
+ const root = arc && arc.depth > 0 ? arc : null
371
+ zoom_root_id = root?.id ?? null
428
372
  // The clicked arc collapses into the hole - drop the now-stale hover/tooltip
429
373
  set_arc_hover(null)
430
- on_zoom?.({ root: arc && arc.depth > 0 ? make_node_props(arc) : null })
431
- }
432
-
433
- // True while the user has an uncollapsed text selection inside this chart. Labels
434
- // are selectable text, and the mouseup that ends a selection drag also fires a
435
- // click - selecting a label must not zoom or fire on_node_click.
436
- function selection_in_chart(): boolean {
437
- const selection = globalThis.getSelection?.()
438
- return Boolean(
439
- selection && !selection.isCollapsed && selection.anchorNode &&
440
- wrapper?.contains(selection.anchorNode),
441
- )
374
+ on_zoom?.({ root: root && make_node_props(root) })
442
375
  }
443
376
 
444
377
  function handle_arc_click(event: MouseEvent | KeyboardEvent) {
445
- if (event instanceof MouseEvent && selection_in_chart()) return
378
+ if (event instanceof MouseEvent && selection_within(wrapper)) return
446
379
  const screen = screen_arc_from_event(event)
447
380
  if (!screen) return
448
381
  const { arc } = screen
@@ -451,7 +384,7 @@
451
384
  }
452
385
 
453
386
  function zoom_out(event?: Event) {
454
- if (event instanceof MouseEvent && selection_in_chart()) return
387
+ if (event instanceof MouseEvent && selection_within(wrapper)) return
455
388
  if (!zoomed) return
456
389
  zoom_to(breadcrumb_arcs.at(-2) ?? null)
457
390
  }
@@ -461,7 +394,7 @@
461
394
  // the center zoom-out button already fired its own click action twice - compounding
462
395
  // a third full reset would teleport step-by-step zoom-outs straight to the root)
463
396
  function handle_dblclick(event: MouseEvent) {
464
- if (screen_arc_from_event(event) || selection_in_chart()) return
397
+ if (screen_arc_from_event(event) || selection_within(wrapper)) return
465
398
  const target = event.target as Element | null
466
399
  if (target?.closest?.(`.center-circle, .center-label`)) return
467
400
  if (zoomed) zoom_to(null)
@@ -489,8 +422,6 @@
489
422
  )
490
423
  }
491
424
 
492
- const is_activation_key = (evt: KeyboardEvent) => [`Enter`, ` `].includes(evt.key)
493
-
494
425
  function handle_arc_keydown(event: KeyboardEvent) {
495
426
  const nav_target = nav_target_from_event(event)
496
427
  if (nav_target != null) {
@@ -524,57 +455,48 @@
524
455
  const arc_clickable = (arc: PositionedArc<Metadata>): boolean =>
525
456
  Boolean(on_node_click) || (zoom_on_click && !arc.is_leaf)
526
457
 
527
- // Measure label fit in the font labels actually render in (respects the
528
- // --sunburst-font-size CSS var instead of assuming 11px). Memoized because canvas
529
- // measureText is far too slow to run for every visible arc on every tween frame.
530
- let label_font = $derived.by(() => {
531
- if (!svg_element) return `11px sans-serif`
532
- const { fontSize, fontFamily } = getComputedStyle(svg_element)
533
- return `${fontSize} ${fontFamily}`
534
- })
535
- const text_width_cache = new Map<string, number>()
536
- function cached_text_width(text: string, font: string): number {
537
- const key = `${font}|${text}`
538
- let text_width = text_width_cache.get(key)
539
- if (text_width === undefined) {
540
- if (text_width_cache.size > 10_000) text_width_cache.clear() // growth guard
541
- text_width = measure_text_width(text, font)
542
- text_width_cache.set(key, text_width)
543
- }
544
- return text_width
545
- }
546
-
547
- // What an arc's label displays, per the label_text mode (plotly textinfo equivalent)
548
- const arc_label_str = (arc: PositionedArc<Metadata>): string => {
549
- const name = arc_name(arc)
550
- if (label_text === `label`) return name
551
- const val = format_value(arc.value, value_format)
552
- if (label_text === `value`) return val
553
- const pct = format_value(arc.fraction, `.1%`)
554
- if (label_text === `percent`) return pct
555
- return label_text === `label+value` ? `${name} ${val}` : `${name} ${pct}`
556
- }
458
+ let label_font = $derived(svg_label_font(svg_element))
459
+ const cached_text_width = make_cached_text_width()
557
460
 
558
- // Per-arc label text, measured width and aria string - all view-independent, so
559
- // computed once per layout/label-option change instead of per animation frame
560
- // (format_value + canvas measureText would otherwise run per visible arc per frame)
461
+ // Per-arc label text, measured width, fill/label colors, clickability and aria
462
+ // string - all view-independent, so computed once per layout/option change
463
+ // instead of per animation frame
561
464
  let arc_info = $derived(
562
- layout.arcs.map((arc) => {
563
- const text = arc_label_str(arc)
564
- return {
565
- text,
566
- width: cached_text_width(text, label_font),
567
- aria: `${arc_name(arc)}: ${arc.value}`,
568
- }
465
+ compute_node_infos(layout.arcs, {
466
+ label_text,
467
+ value_format,
468
+ label_font,
469
+ color_for: arc_color,
470
+ text_width: cached_text_width,
471
+ contrast: contrast_for,
472
+ clickable: arc_clickable,
569
473
  }),
570
474
  )
571
475
 
572
- // Label text + placement transform for an arc; null = doesn't fit, hide the label
573
- function label_attrs(screen: ScreenArc): { transform: string; text: string } | null {
574
- const { text, width: text_w } = arc_info[screen.arc.node_idx]
575
- if (!text) return null
576
- const transform = arc_label_transform(screen, text_w, shape, label_rotation)
577
- return transform ? { transform, text } : null
476
+ // Downscale steps tried when no label variant fits at full size: narrow
477
+ // slices keep a (smaller) label instead of losing it entirely.
478
+ const LABEL_FONT_SCALES = [1, 0.85, 0.7]
479
+
480
+ // Label text + placement transform for an arc; null = no variant fits, hide
481
+ // the label. Full font size wins over richer text: within each scale the
482
+ // richest variant that fits is used (extended -> label -> label_short).
483
+ function label_attrs(
484
+ screen: ScreenArc,
485
+ ): { transform: string; text: string; font_scale: number } | null {
486
+ for (const font_scale of LABEL_FONT_SCALES) {
487
+ for (const { text, width: text_w } of arc_info[screen.arc.node_idx].variants) {
488
+ const transform = arc_label_transform(
489
+ screen,
490
+ text_w * font_scale,
491
+ shape,
492
+ label_rotation,
493
+ radius,
494
+ font_scale,
495
+ )
496
+ if (transform) return { transform, text, font_scale }
497
+ }
498
+ }
499
+ return null
578
500
  }
579
501
 
580
502
  // Legend: one item per depth-1 category, toggling mutes (dims) rather than removes.
@@ -596,21 +518,12 @@
596
518
  points: settled.map(arc_center),
597
519
  })
598
520
  })
599
- let legend_data = $derived.by<LegendItem[]>(() =>
600
- depth1_arcs.map((arc, idx) => ({
601
- series_idx: idx,
602
- label: arc_name(arc),
603
- visible: !muted_ids.has(arc.id),
604
- display_style: { symbol_type: `Square` as const, symbol_color: arc_color(arc) },
605
- }))
521
+ let legend_data: LegendItem[] = $derived(
522
+ hierarchy_legend_items(depth1_arcs, muted_ids, arc_color),
606
523
  )
607
524
 
608
- function toggle_category(series_idx: number) {
609
- const id = depth1_arcs[series_idx]?.id
610
- if (id === undefined) return
611
- if (muted_ids.has(id)) muted_ids.delete(id)
612
- else muted_ids.add(id)
613
- }
525
+ const toggle_category = (series_idx: number) =>
526
+ toggle_muted(muted_ids, depth1_arcs[series_idx]?.id)
614
527
 
615
528
  $effect(() => set_fullscreen_bg(wrapper, fullscreen, `--sunburst-fullscreen-bg`))
616
529
 
@@ -619,62 +532,24 @@
619
532
  let zoom_out_label = $derived.by(() => {
620
533
  const parent = breadcrumb_arcs.at(-2)
621
534
  if (!parent) return ``
622
- return parent.depth === 0 ? `full chart` : arc_name(parent)
623
- })
624
-
625
- // Ancestor chain from the root to the current zoom root (clickable breadcrumb trail)
626
- let breadcrumb_arcs = $derived.by(() => {
627
- if (!zoom_root || zoom_root.depth === 0) return []
628
- const chain: PositionedArc<Metadata>[] = []
629
- for (
630
- let cur: PositionedArc<Metadata> | null = zoom_root;
631
- cur;
632
- cur = parent_of(cur)
633
- ) chain.unshift(cur)
634
- return chain
535
+ return parent.depth === 0 ? `full chart` : node_display_name(parent)
635
536
  })
636
537
 
637
- // Styles the component applies via CSS that exported standalone SVGs must carry
638
- // as presentation attributes (inlined onto a clone by the io/export helpers)
639
- const export_inline_styles = [
640
- `fill`,
641
- `stroke`,
642
- `stroke-width`,
643
- `text-anchor`,
644
- `dominant-baseline`,
645
- `font-size`,
646
- `font-family`,
647
- `font-weight`,
648
- `opacity`,
649
- ]
538
+ let breadcrumb_arcs = $derived(ancestor_chain(layout.arcs, zoom_root))
650
539
 
651
- function export_chart(format: `svg` | `png`) {
652
- if (!svg_element) return
653
- if (format === `svg`) {
654
- export_svg_as_svg(svg_element, `${export_filename}.svg`, export_inline_styles)
655
- } else {
656
- export_svg_as_png(svg_element, `${export_filename}.png`, 150, export_inline_styles)
657
- }
658
- }
540
+ const export_chart = (format: `svg` | `png`) =>
541
+ export_hierarchy_chart(svg_element, export_filename, format)
659
542
  </script>
660
543
 
661
544
  <svelte:window
662
- onkeydown={(evt) => {
663
- if (evt.key !== `Escape`) return
664
- // only react when the user is interacting with this chart (pointer over it,
665
- // focus inside it, or fullscreen) - Escape zooms out one level, then exits
666
- // fullscreen once at the root
667
- const within = fullscreen || Boolean(wrapper?.matches(`:hover`)) ||
668
- Boolean(wrapper && document.activeElement && wrapper.contains(document.activeElement))
669
- if (!within) return
670
- if (zoomed) {
671
- evt.preventDefault()
672
- zoom_out()
673
- } else if (fullscreen) {
674
- evt.preventDefault()
675
- fullscreen = false
676
- }
677
- }}
545
+ onkeydown={(evt) =>
546
+ handle_hierarchy_escape(evt, {
547
+ wrapper,
548
+ fullscreen,
549
+ zoomed,
550
+ zoom_out: () => zoom_out(),
551
+ exit_fullscreen: () => (fullscreen = false),
552
+ })}
678
553
  />
679
554
 
680
555
  <div
@@ -682,7 +557,7 @@
682
557
  bind:clientWidth={width}
683
558
  bind:clientHeight={height}
684
559
  {...rest}
685
- class="sunburst {rest.class ?? ``}"
560
+ class={[`sunburst`, rest.class]}
686
561
  class:fullscreen
687
562
  class:icicle={shape === `icicle`}
688
563
  >
@@ -691,6 +566,7 @@
691
566
  {@render header_controls?.({ height, width, fullscreen })}
692
567
  {#if show_controls}
693
568
  <SunburstControls
569
+ chart="sunburst"
694
570
  toggle_props={{
695
571
  ...controls_toggle_props,
696
572
  // join the header flex row instead of absolute positioning (overrides
@@ -731,7 +607,7 @@
731
607
  disabled={crumb_idx === breadcrumb_arcs.length - 1}
732
608
  onclick={() => zoom_to(crumb)}
733
609
  >
734
- {crumb.depth === 0 ? `all` : crumb.label ?? crumb.id}
610
+ {crumb.depth === 0 ? `all` : (crumb.label ?? crumb.id)}
735
611
  </button>
736
612
  {/each}
737
613
  </nav>
@@ -744,6 +620,12 @@
744
620
  aria-label={rest[`aria-label`] ?? `${shape === `icicle` ? `Icicle` : `Sunburst`} chart`}
745
621
  onmouseleave={() => set_arc_hover(null)}
746
622
  >
623
+ <!-- inert unless some arc references it via fill -->
624
+ <defs>
625
+ <pattern id={hatch_pattern_id} patternUnits="userSpaceOnUse" width="8" height="8">
626
+ <path class="hatch-pattern-line" d="M-1,1 l2,-2 M0,8 l8,-8 M7,9 l2,-2" />
627
+ </pattern>
628
+ </defs>
747
629
  <!-- Hover/click delegation sits on the chart group (not the arcs group) so
748
630
  labels - which carry the same data-sunburst-node-idx and are selectable text -
749
631
  forward interactions to their arc instead of swallowing them -->
@@ -764,20 +646,37 @@
764
646
  {#if arc_content}
765
647
  {@render arc_content(screen)}
766
648
  {:else}
767
- {@const clickable = arc_clickable(screen.arc)}
649
+ <!-- @const so the path is generated (and info looked up) once per
650
+ arc per frame, shared by the base path and the hatch overlay -->
651
+ {@const info = arc_info[screen.arc.node_idx]}
652
+ {@const opacity = arc_dim[screen.arc.node_idx].opacity}
653
+ {@const path_d = screen_path(screen)}
768
654
  <!-- svelte-ignore a11y_no_static_element_interactions -->
769
655
  <path
770
- d={screen_path(screen)}
656
+ d={path_d}
771
657
  data-sunburst-node-idx={screen.arc.node_idx}
772
- fill={arc_color(screen.arc)}
773
- fill-opacity={arc_opacity(screen.arc)}
774
- role={clickable ? `button` : undefined}
775
- tabindex={clickable
776
- ? (screen.arc.node_idx === roving_idx ? 0 : -1)
658
+ fill={info.fill}
659
+ fill-opacity={opacity}
660
+ role={info.clickable ? `button` : undefined}
661
+ tabindex={info.clickable
662
+ ? screen.arc.node_idx === roving_idx
663
+ ? 0
664
+ : -1
777
665
  : undefined}
778
- aria-label={clickable ? arc_info[screen.arc.node_idx].aria : undefined}
779
- style:cursor={clickable ? `pointer` : `default`}
666
+ aria-label={info.clickable ? info.aria : undefined}
667
+ style:cursor={info.clickable ? `pointer` : `default`}
780
668
  />
669
+ {#if screen.arc.hatch}
670
+ <!-- Decorative texture overlay (e.g. preemptible jobs); rendered as
671
+ the base path's next sibling so the hover 'pull' can track it -->
672
+ <path
673
+ class="arc-hatch"
674
+ aria-hidden="true"
675
+ d={path_d}
676
+ fill="url(#{hatch_pattern_id})"
677
+ fill-opacity={opacity}
678
+ />
679
+ {/if}
781
680
  {/if}
782
681
  {/each}
783
682
  </g>
@@ -789,13 +688,15 @@
789
688
  {#each visible_arcs as screen (screen.arc.node_idx)}
790
689
  {@const lbl = label_attrs(screen)}
791
690
  {#if lbl}
691
+ {@const info = arc_info[screen.arc.node_idx]}
792
692
  <text
793
693
  class="arc-label"
794
694
  data-sunburst-node-idx={screen.arc.node_idx}
795
695
  transform={lbl.transform}
796
- fill={label_color(screen.arc)}
797
- fill-opacity={is_muted(screen.arc) ? MUTED_OPACITY : undefined}
798
- style:cursor={arc_clickable(screen.arc) ? `pointer` : `text`}
696
+ fill={info.label_fill}
697
+ fill-opacity={arc_dim[screen.arc.node_idx].label_opacity}
698
+ style:cursor={info.clickable ? `pointer` : `text`}
699
+ style:font-size={lbl.font_scale === 1 ? undefined : `${lbl.font_scale}em`}
799
700
  >
800
701
  {lbl.text}
801
702
  </text>
@@ -857,14 +758,13 @@
857
758
  {#if tooltip}
858
759
  {@render tooltip(hover_info)}
859
760
  {:else}
860
- <strong>{hover_info.label_path.join(` › `)}</strong>: {
861
- format_value(hover_info.value, value_format)
862
- }
863
- ({format_value(hover_info.fraction, `.1%`)} of total{
864
- hover_info.depth > 1
761
+ <strong>{hover_info.label_path.join(` › `)}</strong>: {format_value(
762
+ hover_info.value,
763
+ value_format,
764
+ )}
765
+ ({format_value(hover_info.fraction, `.1%`)} of total{hover_info.depth > 1
865
766
  ? `, ${format_value(hover_info.parent_fraction, `.1%`)} of parent`
866
- : ``
867
- })
767
+ : ``})
868
768
  {/if}
869
769
  </PlotTooltip>
870
770
  {/if}
@@ -878,9 +778,10 @@
878
778
  series_data={legend_data}
879
779
  on_toggle={legend?.on_toggle ?? toggle_category}
880
780
  on_item_hover={(item) =>
881
- (hovered_idx = item != null && item.series_idx >= 0
882
- ? depth1_arcs[item.series_idx]?.node_idx ?? null
883
- : null)}
781
+ (hovered_idx =
782
+ item != null && item.series_idx >= 0
783
+ ? (depth1_arcs[item.series_idx]?.node_idx ?? null)
784
+ : null)}
884
785
  style={`position: absolute; left: ${legend_left}px; top: ${legend_top}px; pointer-events: auto; ${
885
786
  legend?.style ?? ``
886
787
  }`}
@@ -890,10 +791,10 @@
890
791
  {#if metric && colorbar != null}
891
792
  <div
892
793
  bind:clientHeight={colorbar_height}
893
- style="position: absolute; bottom: var(--sunburst-colorbar-bottom, 8px); left: 50%; transform: translateX(-50%); width: var(--sunburst-colorbar-width, 40%); min-width: 120px; pointer-events: auto;"
794
+ style="position: absolute; bottom: var(--sunburst-colorbar-bottom, 8px); left: var(--sunburst-colorbar-left, 50%); transform: var(--sunburst-colorbar-transform, translateX(-50%)); width: var(--sunburst-colorbar-width, 40%); min-width: 120px; pointer-events: auto;"
894
795
  >
895
796
  <ColorBar
896
- color_scale={color_scale}
797
+ {color_scale}
897
798
  range={metric.range}
898
799
  {...colorbar}
899
800
  wrapper_style={`width: 100%; ${colorbar?.wrapper_style ?? ``}`}
@@ -986,7 +887,9 @@
986
887
  .sunburst :global(.pane-toggle),
987
888
  .sunburst .header-controls {
988
889
  opacity: 0;
989
- transition: opacity 0.2s, background-color 0.2s;
890
+ transition:
891
+ opacity 0.2s,
892
+ background-color 0.2s;
990
893
  }
991
894
  .sunburst:hover :global(.pane-toggle),
992
895
  .sunburst:hover .header-controls,
@@ -1009,13 +912,32 @@
1009
912
  presentation attributes is not reliably supported across browsers */
1010
913
  stroke: var(--sunburst-arc-stroke, var(--plot-bg, white));
1011
914
  stroke-width: var(--sunburst-arc-stroke-width, 0.25);
1012
- transition: fill-opacity 0.15s ease, transform 0.15s ease;
915
+ transition:
916
+ fill-opacity 0.15s ease,
917
+ transform 0.15s ease;
1013
918
  /* hover 'pull': scaling about the chart center offsets the arc radially */
1014
919
  transform-origin: 0 0;
1015
920
  }
1016
- .sunburst:not(.icicle) .arcs path:hover {
921
+ /* the hatch overlay (an arc's next sibling) rides along with its hover 'pull' */
922
+ .sunburst:not(.icicle) .arcs path:hover,
923
+ .sunburst:not(.icicle) .arcs path:hover + path.arc-hatch {
1017
924
  transform: scale(var(--sunburst-hover-scale, 1.02));
1018
925
  }
926
+ /* decorative overlay: never intercepts pointer events, no border of its own */
927
+ .arcs path.arc-hatch {
928
+ stroke: none;
929
+ pointer-events: none;
930
+ }
931
+ /* subtle by default: thin stripes inheriting the arc border color (itself
932
+ defaulting to the chart bg) at low opacity, so hatching matches the gaps
933
+ between slices instead of reading as solid white */
934
+ .hatch-pattern-line {
935
+ stroke: var(
936
+ --sunburst-hatch-stroke,
937
+ color-mix(in srgb, var(--sunburst-arc-stroke, var(--plot-bg, white)) 30%, transparent)
938
+ );
939
+ stroke-width: var(--sunburst-hatch-stroke-width, 0.35);
940
+ }
1019
941
  .center-circle {
1020
942
  fill: var(--sunburst-center-bg, transparent);
1021
943
  }