matterviz 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (499) hide show
  1. package/dist/EmptyState.svelte +10 -4
  2. package/dist/FilePicker.svelte +48 -60
  3. package/dist/Icon.svelte +7 -4
  4. package/dist/MillerIndexInput.svelte +2 -2
  5. package/dist/api/optimade.js +10 -7
  6. package/dist/app.css +68 -0
  7. package/dist/brillouin/BrillouinZone.svelte +90 -91
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
  9. package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
  11. package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
  12. package/dist/brillouin/BrillouinZoneScene.svelte +46 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  14. package/dist/brillouin/compute.js +10 -16
  15. package/dist/brillouin/geometry.js +2 -2
  16. package/dist/chempot-diagram/ChemPotDiagram.svelte +48 -50
  17. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +61 -169
  18. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
  19. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +559 -755
  20. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
  21. package/dist/chempot-diagram/async-compute.svelte.js +13 -14
  22. package/dist/chempot-diagram/color.d.ts +21 -1
  23. package/dist/chempot-diagram/color.js +70 -1
  24. package/dist/chempot-diagram/compute.d.ts +7 -0
  25. package/dist/chempot-diagram/compute.js +85 -26
  26. package/dist/chempot-diagram/controls-state.svelte.js +1 -1
  27. package/dist/chempot-diagram/export.js +1 -6
  28. package/dist/chempot-diagram/temperature.js +9 -10
  29. package/dist/colors/index.d.ts +0 -4
  30. package/dist/colors/index.js +19 -20
  31. package/dist/composition/BarChart.svelte +30 -26
  32. package/dist/composition/BubbleChart.svelte +17 -17
  33. package/dist/composition/Composition.svelte +10 -19
  34. package/dist/composition/Formula.svelte +20 -27
  35. package/dist/composition/FormulaFilter.svelte +115 -184
  36. package/dist/composition/PieChart.svelte +55 -41
  37. package/dist/composition/format.js +7 -9
  38. package/dist/composition/index.d.ts +2 -0
  39. package/dist/composition/index.js +14 -0
  40. package/dist/composition/parse.d.ts +2 -2
  41. package/dist/composition/parse.js +61 -115
  42. package/dist/constants.d.ts +0 -1
  43. package/dist/constants.js +17 -26
  44. package/dist/convex-hull/ConvexHull.svelte +12 -15
  45. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHull2D.svelte +93 -94
  47. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  48. package/dist/convex-hull/ConvexHull3D.svelte +127 -186
  49. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  50. package/dist/convex-hull/ConvexHull4D.svelte +131 -180
  51. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  52. package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
  53. package/dist/convex-hull/ConvexHullControls.svelte +46 -48
  54. package/dist/convex-hull/ConvexHullStats.svelte +193 -195
  55. package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
  56. package/dist/convex-hull/GasPressureControls.svelte +5 -12
  57. package/dist/convex-hull/StructurePopup.svelte +18 -22
  58. package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
  59. package/dist/convex-hull/TemperatureSlider.svelte +4 -10
  60. package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
  61. package/dist/convex-hull/gas-thermodynamics.js +21 -53
  62. package/dist/convex-hull/helpers.d.ts +8 -10
  63. package/dist/convex-hull/helpers.js +37 -43
  64. package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
  65. package/dist/convex-hull/thermodynamics.js +37 -44
  66. package/dist/coordination/CoordinationBarPlot.svelte +49 -53
  67. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  68. package/dist/coordination/calc-coordination.d.ts +1 -1
  69. package/dist/coordination/calc-coordination.js +28 -25
  70. package/dist/element/BohrAtom.svelte +5 -9
  71. package/dist/element/ElementHeading.svelte +4 -1
  72. package/dist/element/ElementPhoto.svelte +11 -9
  73. package/dist/element/ElementStats.svelte +12 -10
  74. package/dist/element/ElementTile.svelte +44 -67
  75. package/dist/element/Nucleus.svelte +4 -2
  76. package/dist/element/data.d.ts +3 -2
  77. package/dist/element/data.js +5 -1
  78. package/dist/element/helpers.d.ts +4 -0
  79. package/dist/element/helpers.js +18 -0
  80. package/dist/element/index.d.ts +2 -5
  81. package/dist/element/index.js +4 -19
  82. package/dist/feedback/DragOverlay.svelte +5 -5
  83. package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
  84. package/dist/feedback/StatusMessage.svelte +7 -4
  85. package/dist/fermi-surface/FermiSlice.svelte +21 -18
  86. package/dist/fermi-surface/FermiSurface.svelte +35 -44
  87. package/dist/fermi-surface/FermiSurface.svelte.d.ts +7 -13
  88. package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
  89. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  90. package/dist/fermi-surface/FermiSurfaceScene.svelte +64 -136
  91. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  92. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
  93. package/dist/fermi-surface/compute.js +25 -58
  94. package/dist/fermi-surface/parse.js +54 -88
  95. package/dist/file-viewer/JsonBrowser.svelte +1043 -0
  96. package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
  97. package/dist/file-viewer/PlotPanel.svelte +350 -0
  98. package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
  99. package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
  100. package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
  101. package/dist/file-viewer/detect.d.ts +11 -0
  102. package/dist/file-viewer/detect.js +381 -0
  103. package/dist/file-viewer/eligibility.d.ts +4 -0
  104. package/dist/file-viewer/eligibility.js +53 -0
  105. package/dist/file-viewer/host-protocol.d.ts +50 -0
  106. package/dist/file-viewer/host-transfer.d.ts +25 -0
  107. package/dist/file-viewer/host-transfer.js +42 -0
  108. package/dist/file-viewer/index.d.ts +5 -0
  109. package/dist/file-viewer/index.js +5 -0
  110. package/dist/file-viewer/main.d.ts +44 -0
  111. package/dist/file-viewer/main.js +535 -0
  112. package/dist/file-viewer/parse.d.ts +9 -0
  113. package/dist/file-viewer/parse.js +170 -0
  114. package/dist/file-viewer/plot-utils.d.ts +24 -0
  115. package/dist/file-viewer/plot-utils.js +191 -0
  116. package/dist/file-viewer/types.d.ts +4 -0
  117. package/dist/file-viewer/types.js +5 -0
  118. package/dist/heatmap-matrix/HeatmapMatrix.svelte +153 -219
  119. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
  120. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
  121. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  122. package/dist/heatmap-matrix/index.d.ts +5 -0
  123. package/dist/icons.d.ts +4 -0
  124. package/dist/icons.js +4 -0
  125. package/dist/index.d.ts +2 -2
  126. package/dist/index.js +1 -1
  127. package/dist/io/ExportPane.svelte +11 -7
  128. package/dist/io/decompress.d.ts +4 -1
  129. package/dist/io/decompress.js +24 -36
  130. package/dist/io/export.d.ts +11 -4
  131. package/dist/io/export.js +75 -19
  132. package/dist/io/fetch.d.ts +0 -3
  133. package/dist/io/fetch.js +0 -11
  134. package/dist/io/file-drop.js +60 -35
  135. package/dist/io/index.d.ts +0 -1
  136. package/dist/io/index.js +2 -9
  137. package/dist/io/is-binary.d.ts +9 -0
  138. package/dist/io/is-binary.js +45 -1
  139. package/dist/io/url-drop.d.ts +2 -0
  140. package/dist/io/url-drop.js +40 -40
  141. package/dist/isosurface/Isosurface.svelte +700 -215
  142. package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
  143. package/dist/isosurface/IsosurfaceControls.svelte +590 -116
  144. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
  145. package/dist/isosurface/VolumeSlice.svelte +224 -0
  146. package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
  147. package/dist/isosurface/coloring.d.ts +20 -0
  148. package/dist/isosurface/coloring.js +118 -0
  149. package/dist/isosurface/geometry-worker-types.d.ts +38 -0
  150. package/dist/isosurface/geometry.worker.d.ts +1 -0
  151. package/dist/isosurface/geometry.worker.js +59 -0
  152. package/dist/isosurface/grid.d.ts +10 -0
  153. package/dist/isosurface/grid.js +31 -0
  154. package/dist/isosurface/index.d.ts +4 -0
  155. package/dist/isosurface/index.js +4 -0
  156. package/dist/isosurface/parse-vaspwave.d.ts +3 -0
  157. package/dist/isosurface/parse-vaspwave.js +138 -0
  158. package/dist/isosurface/parse.js +6 -6
  159. package/dist/isosurface/profile.d.ts +12 -0
  160. package/dist/isosurface/profile.js +19 -0
  161. package/dist/isosurface/sampling.d.ts +34 -0
  162. package/dist/isosurface/sampling.js +408 -0
  163. package/dist/isosurface/slice-rendering.d.ts +13 -0
  164. package/dist/isosurface/slice-rendering.js +79 -0
  165. package/dist/isosurface/slice.d.ts +22 -2
  166. package/dist/isosurface/slice.js +200 -116
  167. package/dist/isosurface/types.d.ts +24 -1
  168. package/dist/isosurface/types.js +120 -8
  169. package/dist/json-path.d.ts +4 -0
  170. package/dist/json-path.js +89 -0
  171. package/dist/labels.d.ts +6 -2
  172. package/dist/labels.js +44 -14
  173. package/dist/layout/FullscreenButton.svelte +2 -1
  174. package/dist/layout/FullscreenToggle.svelte +10 -4
  175. package/dist/layout/InfoCard.svelte +8 -21
  176. package/dist/layout/InfoTag.svelte +1 -3
  177. package/dist/layout/NumberRangeInput.svelte +46 -0
  178. package/dist/layout/NumberRangeInput.svelte.d.ts +12 -0
  179. package/dist/layout/PropertyFilter.svelte +2 -6
  180. package/dist/layout/SettingsSection.svelte +57 -62
  181. package/dist/layout/SettingsSection.svelte.d.ts +6 -7
  182. package/dist/layout/SubpageGrid.svelte +3 -1
  183. package/dist/layout/ViewerChrome.svelte +16 -11
  184. package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
  185. package/dist/layout/fullscreen.js +3 -5
  186. package/dist/layout/fullscreen.svelte.js +1 -2
  187. package/dist/layout/index.d.ts +1 -0
  188. package/dist/layout/index.js +1 -0
  189. package/dist/layout/json-tree/JsonNode.svelte +22 -59
  190. package/dist/layout/json-tree/JsonTree.svelte +101 -172
  191. package/dist/layout/json-tree/JsonValue.svelte +9 -32
  192. package/dist/layout/json-tree/utils.d.ts +0 -3
  193. package/dist/layout/json-tree/utils.js +50 -188
  194. package/dist/marching-cubes.d.ts +8 -1
  195. package/dist/marching-cubes.js +115 -48
  196. package/dist/math.d.ts +5 -2
  197. package/dist/math.js +48 -38
  198. package/dist/overlays/ContextMenu.svelte +19 -34
  199. package/dist/overlays/CopyButton.svelte +11 -5
  200. package/dist/overlays/DragControlTab.svelte +6 -1
  201. package/dist/overlays/DraggablePane.svelte +110 -74
  202. package/dist/overlays/DraggablePane.svelte.d.ts +2 -1
  203. package/dist/overlays/GlassChip.svelte +32 -0
  204. package/dist/overlays/GlassChip.svelte.d.ts +8 -0
  205. package/dist/overlays/InfoPaneCards.svelte +11 -23
  206. package/dist/overlays/index.d.ts +7 -0
  207. package/dist/overlays/index.js +26 -0
  208. package/dist/overlays/portal.d.ts +2 -0
  209. package/dist/overlays/portal.js +14 -0
  210. package/dist/periodic-table/PeriodicTable.svelte +113 -145
  211. package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
  212. package/dist/periodic-table/PeriodicTableControls.svelte +37 -115
  213. package/dist/periodic-table/TableInset.svelte +9 -4
  214. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +68 -105
  215. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
  216. package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
  217. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  218. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  219. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
  220. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
  221. package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
  222. package/dist/phase-diagram/build-diagram.js +4 -9
  223. package/dist/phase-diagram/diagram-input.d.ts +1 -2
  224. package/dist/phase-diagram/parse.js +15 -15
  225. package/dist/phase-diagram/svg-to-diagram.js +28 -27
  226. package/dist/phase-diagram/utils.js +2 -3
  227. package/dist/plot/bar/BarPlot.svelte +461 -426
  228. package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
  229. package/dist/plot/bar/BarPlotControls.svelte +1 -3
  230. package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
  231. package/dist/plot/bar/SpacegroupBarPlot.svelte +46 -77
  232. package/dist/plot/bar/data.d.ts +5 -4
  233. package/dist/plot/bar/data.js +2 -1
  234. package/dist/plot/box/BoxPlot.svelte +322 -226
  235. package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
  236. package/dist/plot/box/Violin.svelte.d.ts +1 -0
  237. package/dist/plot/box/box-plot.js +1 -1
  238. package/dist/plot/core/auto-place.d.ts +1 -1
  239. package/dist/plot/core/auto-place.js +2 -2
  240. package/dist/plot/core/axis-utils.d.ts +12 -7
  241. package/dist/plot/core/axis-utils.js +48 -48
  242. package/dist/plot/core/components/AxisLabel.svelte +82 -20
  243. package/dist/plot/core/components/AxisLabel.svelte.d.ts +1 -0
  244. package/dist/plot/core/components/ColorBar.svelte +101 -106
  245. package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
  246. package/dist/plot/core/components/FillArea.svelte +10 -17
  247. package/dist/plot/core/components/HierarchyControls.svelte +249 -0
  248. package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
  249. package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
  250. package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
  251. package/dist/plot/core/components/Line.svelte +18 -9
  252. package/dist/plot/core/components/Line.svelte.d.ts +2 -0
  253. package/dist/plot/core/components/PlotAxis.svelte +40 -17
  254. package/dist/plot/core/components/PlotControls.svelte +65 -41
  255. package/dist/plot/core/components/PlotLegend.svelte +31 -35
  256. package/dist/plot/core/components/PlotMarginals.svelte +616 -0
  257. package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
  258. package/dist/plot/core/components/PlotTooltip.svelte +1 -1
  259. package/dist/plot/core/components/PortalSelect.svelte +4 -8
  260. package/dist/plot/core/components/ReferenceLine.svelte +27 -24
  261. package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
  262. package/dist/plot/core/components/ReferencePlane.svelte +7 -8
  263. package/dist/plot/core/components/ZeroLines.svelte +4 -8
  264. package/dist/plot/core/components/ZoomRect.svelte +1 -2
  265. package/dist/plot/core/components/index.d.ts +1 -0
  266. package/dist/plot/core/components/index.js +1 -0
  267. package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
  268. package/dist/plot/core/data-cleaning-signal.js +467 -0
  269. package/dist/plot/core/data-cleaning.d.ts +2 -42
  270. package/dist/plot/core/data-cleaning.js +27 -474
  271. package/dist/plot/core/data-transform.d.ts +1 -2
  272. package/dist/plot/core/data-transform.js +9 -15
  273. package/dist/plot/core/fill-utils.d.ts +3 -1
  274. package/dist/plot/core/fill-utils.js +28 -6
  275. package/dist/plot/core/index.d.ts +1 -0
  276. package/dist/plot/core/index.js +1 -0
  277. package/dist/plot/core/interactions.js +0 -2
  278. package/dist/plot/core/layout.d.ts +15 -6
  279. package/dist/plot/core/layout.js +57 -38
  280. package/dist/plot/core/marginals.d.ts +170 -0
  281. package/dist/plot/core/marginals.js +436 -0
  282. package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
  283. package/dist/plot/core/pan-zoom.svelte.js +34 -17
  284. package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
  285. package/dist/plot/core/placed-tween.svelte.js +68 -21
  286. package/dist/plot/core/reference-line.js +1 -1
  287. package/dist/plot/core/scales.d.ts +23 -0
  288. package/dist/plot/core/scales.js +39 -13
  289. package/dist/plot/core/svg.js +0 -1
  290. package/dist/plot/core/types/fills.d.ts +101 -0
  291. package/dist/plot/core/types/fills.js +15 -0
  292. package/dist/plot/core/types/plot-3d.d.ts +87 -0
  293. package/dist/plot/core/types/plot-3d.js +4 -0
  294. package/dist/plot/core/types/reference-lines.d.ts +140 -0
  295. package/dist/plot/core/types/reference-lines.js +9 -0
  296. package/dist/plot/core/types.d.ts +29 -324
  297. package/dist/plot/core/types.js +23 -20
  298. package/dist/plot/core/utils/hierarchy-chart.d.ts +52 -0
  299. package/dist/plot/core/utils/hierarchy-chart.js +191 -0
  300. package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
  301. package/dist/plot/core/utils/hierarchy-labels.js +100 -0
  302. package/dist/plot/core/utils/label-placement.js +2 -2
  303. package/dist/plot/histogram/Histogram.svelte +314 -340
  304. package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
  305. package/dist/plot/histogram/HistogramControls.svelte +30 -28
  306. package/dist/plot/histogram/histogram.d.ts +31 -0
  307. package/dist/plot/histogram/histogram.js +70 -0
  308. package/dist/plot/histogram/index.d.ts +1 -0
  309. package/dist/plot/histogram/index.js +1 -0
  310. package/dist/plot/index.d.ts +1 -0
  311. package/dist/plot/index.js +1 -0
  312. package/dist/plot/sankey/Sankey.svelte +78 -69
  313. package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
  314. package/dist/plot/sankey/SankeyControls.svelte +22 -16
  315. package/dist/plot/sankey/sankey.js +13 -9
  316. package/dist/plot/scatter/BinnedScatterPlot.svelte +311 -141
  317. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
  318. package/dist/plot/scatter/ElementScatter.svelte +4 -1
  319. package/dist/plot/scatter/ScatterPlot.svelte +517 -410
  320. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
  321. package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
  322. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
  323. package/dist/plot/scatter/ScatterPoint.svelte +16 -9
  324. package/dist/plot/scatter/adaptive-density.d.ts +1 -1
  325. package/dist/plot/scatter/adaptive-density.js +36 -20
  326. package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
  327. package/dist/plot/scatter/binned-scatter-types.js +6 -1
  328. package/dist/plot/scatter/scatter-data.d.ts +1 -1
  329. package/dist/plot/scatter/scatter-data.js +8 -7
  330. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
  331. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
  332. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
  333. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
  334. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
  335. package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
  336. package/dist/plot/sunburst/Sunburst.svelte +269 -347
  337. package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
  338. package/dist/plot/sunburst/index.d.ts +1 -1
  339. package/dist/plot/sunburst/index.js +2 -1
  340. package/dist/plot/sunburst/render.d.ts +2 -2
  341. package/dist/plot/sunburst/render.js +47 -22
  342. package/dist/plot/sunburst/sunburst.d.ts +5 -1
  343. package/dist/plot/sunburst/sunburst.js +10 -3
  344. package/dist/plot/treemap/Treemap.svelte +1022 -0
  345. package/dist/plot/treemap/Treemap.svelte.d.ts +90 -0
  346. package/dist/plot/treemap/index.d.ts +4 -0
  347. package/dist/plot/treemap/index.js +4 -0
  348. package/dist/plot/treemap/labels.d.ts +38 -0
  349. package/dist/plot/treemap/labels.js +80 -0
  350. package/dist/plot/treemap/treemap.d.ts +28 -0
  351. package/dist/plot/treemap/treemap.js +102 -0
  352. package/dist/rdf/RdfPlot.svelte +37 -59
  353. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  354. package/dist/rdf/calc-rdf.js +63 -52
  355. package/dist/rdf/index.d.ts +0 -1
  356. package/dist/sanitize.js +85 -27
  357. package/dist/scene/SceneCamera.svelte +1 -1
  358. package/dist/scene/SceneCamera.svelte.d.ts +1 -1
  359. package/dist/scene/bind-renderer.svelte.js +14 -6
  360. package/dist/scene/index.d.ts +1 -2
  361. package/dist/scene/index.js +1 -1
  362. package/dist/scene/{props.js → props.svelte.js} +15 -1
  363. package/dist/settings.d.ts +19 -9
  364. package/dist/settings.js +229 -525
  365. package/dist/spectral/Bands.svelte +207 -221
  366. package/dist/spectral/BandsAndDos.svelte +11 -9
  367. package/dist/spectral/BrillouinBandsDos.svelte +19 -42
  368. package/dist/spectral/Dos.svelte +92 -93
  369. package/dist/spectral/helpers.d.ts +3 -3
  370. package/dist/spectral/helpers.js +65 -140
  371. package/dist/state.svelte.js +5 -18
  372. package/dist/structure/Arrow.svelte +5 -18
  373. package/dist/structure/ArrowInstances.svelte +160 -0
  374. package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
  375. package/dist/structure/AtomLegend.svelte +120 -85
  376. package/dist/structure/AtomLegend.svelte.d.ts +2 -3
  377. package/dist/structure/Bond.svelte +31 -23
  378. package/dist/structure/CanvasTooltip.svelte +15 -3
  379. package/dist/structure/CellSelect.svelte +45 -35
  380. package/dist/structure/Cylinder.svelte +24 -40
  381. package/dist/structure/InstancedAtoms.svelte +114 -0
  382. package/dist/structure/InstancedAtoms.svelte.d.ts +15 -0
  383. package/dist/structure/Lattice.svelte +85 -149
  384. package/dist/structure/SiteLabels.svelte +122 -0
  385. package/dist/structure/SiteLabels.svelte.d.ts +18 -0
  386. package/dist/structure/Structure.svelte +636 -504
  387. package/dist/structure/Structure.svelte.d.ts +4 -2
  388. package/dist/structure/StructureCarousel.svelte +637 -0
  389. package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
  390. package/dist/structure/StructureControls.svelte +165 -425
  391. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  392. package/dist/structure/StructureExportPane.svelte +17 -21
  393. package/dist/structure/StructureInfoPane.svelte +128 -133
  394. package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
  395. package/dist/structure/StructureScene.svelte +535 -578
  396. package/dist/structure/StructureScene.svelte.d.ts +6 -2
  397. package/dist/structure/StructureViewport.svelte +451 -0
  398. package/dist/structure/StructureViewport.svelte.d.ts +65 -0
  399. package/dist/structure/atom-label-placement.d.ts +16 -12
  400. package/dist/structure/atom-label-placement.js +70 -37
  401. package/dist/structure/atom-properties.d.ts +2 -0
  402. package/dist/structure/atom-properties.js +105 -38
  403. package/dist/structure/bond-order-perception.js +3 -8
  404. package/dist/structure/bonding.d.ts +9 -3
  405. package/dist/structure/bonding.js +95 -46
  406. package/dist/structure/edit-history.d.ts +6 -0
  407. package/dist/structure/edit-history.js +27 -0
  408. package/dist/structure/export.d.ts +1 -0
  409. package/dist/structure/export.js +89 -105
  410. package/dist/structure/format-detect.d.ts +1 -0
  411. package/dist/structure/format-detect.js +20 -0
  412. package/dist/structure/geometry.d.ts +9 -0
  413. package/dist/structure/geometry.js +27 -0
  414. package/dist/structure/index.d.ts +16 -1
  415. package/dist/structure/index.js +15 -10
  416. package/dist/structure/measure.d.ts +1 -1
  417. package/dist/structure/measure.js +6 -12
  418. package/dist/structure/parse.d.ts +2 -1
  419. package/dist/structure/parse.js +272 -313
  420. package/dist/structure/partial-occupancy.js +10 -5
  421. package/dist/structure/pbc.js +85 -107
  422. package/dist/structure/polyhedra.d.ts +0 -1
  423. package/dist/structure/polyhedra.js +7 -21
  424. package/dist/structure/prop-groups.d.ts +85 -0
  425. package/dist/structure/prop-groups.js +18 -0
  426. package/dist/structure/supercell.d.ts +1 -2
  427. package/dist/structure/supercell.js +7 -13
  428. package/dist/symmetry/SymmetryElements.svelte +11 -15
  429. package/dist/symmetry/SymmetryStats.svelte +55 -71
  430. package/dist/symmetry/WyckoffTable.svelte +10 -28
  431. package/dist/symmetry/cell-transform.js +6 -14
  432. package/dist/symmetry/index.d.ts +1 -0
  433. package/dist/symmetry/index.js +13 -10
  434. package/dist/symmetry/spacegroups.js +3 -2
  435. package/dist/table/HeatmapTable.svelte +1142 -447
  436. package/dist/table/HeatmapTable.svelte.d.ts +12 -2
  437. package/dist/table/ToggleMenu.svelte +168 -53
  438. package/dist/table/ToggleMenu.svelte.d.ts +1 -1
  439. package/dist/table/index.d.ts +21 -6
  440. package/dist/table/index.js +34 -21
  441. package/dist/theme/ThemeControl.svelte +16 -8
  442. package/dist/theme/embedded.d.ts +18 -0
  443. package/dist/theme/embedded.js +206 -0
  444. package/dist/theme/index.d.ts +5 -5
  445. package/dist/theme/index.js +5 -12
  446. package/dist/theme/themes.mjs +44 -66
  447. package/dist/tooltip/KCoords.svelte +4 -1
  448. package/dist/tooltip/TooltipContent.svelte +3 -6
  449. package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
  450. package/dist/tooltip/index.d.ts +8 -1
  451. package/dist/trajectory/Trajectory.svelte +613 -386
  452. package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
  453. package/dist/trajectory/TrajectoryError.svelte +12 -74
  454. package/dist/trajectory/TrajectoryExportPane.svelte +14 -32
  455. package/dist/trajectory/TrajectoryInfoPane.svelte +127 -164
  456. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
  457. package/dist/trajectory/extract.js +61 -49
  458. package/dist/trajectory/format-detect.d.ts +2 -0
  459. package/dist/trajectory/format-detect.js +17 -13
  460. package/dist/trajectory/frame-reader.d.ts +2 -0
  461. package/dist/trajectory/frame-reader.js +35 -32
  462. package/dist/trajectory/helpers.d.ts +2 -0
  463. package/dist/trajectory/helpers.js +34 -56
  464. package/dist/trajectory/index.d.ts +1 -0
  465. package/dist/trajectory/index.js +5 -8
  466. package/dist/trajectory/parse/ase.js +1 -1
  467. package/dist/trajectory/parse/h5-utils.d.ts +13 -0
  468. package/dist/trajectory/parse/h5-utils.js +109 -0
  469. package/dist/trajectory/parse/hdf5.d.ts +1 -1
  470. package/dist/trajectory/parse/hdf5.js +116 -106
  471. package/dist/trajectory/parse/index.d.ts +6 -6
  472. package/dist/trajectory/parse/index.js +25 -33
  473. package/dist/trajectory/parse/lammps.js +8 -12
  474. package/dist/trajectory/parse/vasp.js +10 -7
  475. package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
  476. package/dist/trajectory/parse/vaspout-electronic.js +207 -0
  477. package/dist/trajectory/parse/vaspout-h5.d.ts +4 -0
  478. package/dist/trajectory/parse/vaspout-h5.js +281 -0
  479. package/dist/trajectory/parse/xyz.js +59 -9
  480. package/dist/trajectory/plotting.d.ts +8 -9
  481. package/dist/trajectory/plotting.js +113 -64
  482. package/dist/utils.d.ts +3 -0
  483. package/dist/utils.js +7 -0
  484. package/dist/xrd/XrdPlot.svelte +79 -118
  485. package/dist/xrd/broadening.js +2 -1
  486. package/dist/xrd/calc-xrd.js +24 -62
  487. package/dist/xrd/parse.d.ts +0 -1
  488. package/dist/xrd/parse.js +69 -95
  489. package/package.json +94 -34
  490. package/readme.md +6 -5
  491. package/dist/api/mp.d.ts +0 -6
  492. package/dist/api/mp.js +0 -22
  493. package/dist/plot/sunburst/SunburstControls.svelte +0 -200
  494. package/dist/scene/types.d.ts +0 -26
  495. package/dist/tooltip/types.d.ts +0 -8
  496. package/dist/trajectory/constants.d.ts +0 -6
  497. package/dist/trajectory/constants.js +0 -11
  498. /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
  499. /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
@@ -3,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,38 @@
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
+ observe_height,
36
+ pointer_pos,
37
+ prune_muted_ids,
38
+ safe_hierarchy_layout,
39
+ selection_within,
40
+ svg_label_font,
41
+ toggle_muted,
42
+ } from '../core/utils/hierarchy-chart'
43
+ import {
44
+ export_hierarchy_chart,
45
+ make_cached_contrast,
46
+ make_cached_text_width,
47
+ node_display_name,
48
+ } from '../core/utils/hierarchy-labels'
49
+ import {
50
+ arc_label_transform,
51
+ arrow_nav_target,
52
+ project_arcs,
53
+ } from './render'
33
54
  import type { ScreenArc as ScreenArcOf } from './render'
34
55
  import { compute_sunburst_layout, type PositionedArc } from './sunburst'
35
56
  import { DEFAULTS } from '../../settings'
@@ -40,6 +61,7 @@
40
61
  import { Tween, type TweenOptions } from 'svelte/motion'
41
62
  import { SvelteSet } from 'svelte/reactivity'
42
63
 
64
+ // Preserve the established outer inset; pass `padding` to override chart-edge space.
43
65
  const DEFAULT_PADDING: Required<Sides> = { t: 10, b: 10, l: 10, r: 10 }
44
66
 
45
67
  // An arc with its current screen-space geometry (angles in radians, radii in px)
@@ -63,7 +85,7 @@
63
85
  zoom_root_id = $bindable(null),
64
86
  show_breadcrumbs = $bindable(DEFAULTS.sunburst.show_breadcrumbs),
65
87
  color_values,
66
- color_scale = `interpolateViridis`,
88
+ color_scale = SCALE_DEFAULTS.scheme,
67
89
  color_range,
68
90
  colorbar = {},
69
91
  export_buttons = true,
@@ -91,66 +113,69 @@
91
113
  header_controls,
92
114
  controls_extra,
93
115
  ...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()
116
+ }: HTMLAttributes<HTMLDivElement> &
117
+ Omit<BasePlotProps, `change`> & {
118
+ data?: SunburstNode<Metadata> | SunburstNode<Metadata>[]
119
+ shape?: SunburstShape // polar rings (sunburst) or stacked rows (icicle)
120
+ value_mode?: SunburstValueMode
121
+ sort?: SunburstSort
122
+ level_lighten?: number
123
+ // Aggregate sibling arcs below this fraction of the total into one 'Other' leaf
124
+ // per parent (only when >= 2 qualify); 0 disables
125
+ min_fraction?: number
126
+ other_label?: string
127
+ max_depth?: number // rings shown below the current zoom root (0 = all)
128
+ inner_radius?: number // center hole as fraction of outer radius
129
+ pad_angle?: number // degrees between sibling arcs
130
+ show_labels?: boolean
131
+ label_rotation?: SunburstLabelRotation
132
+ label_text?: SunburstLabelText // what labels display (plotly textinfo equivalent)
133
+ zoom_on_click?: boolean
134
+ zoom_root_id?: string | number | null // id of the arc the view is rooted on
135
+ show_breadcrumbs?: boolean // clickable ancestor trail when zoomed
136
+ // Color arcs by a numeric metric (continuous colormap) instead of categorical
137
+ // inheritance; return null to keep an arc's categorical color
138
+ color_values?: (arc: PositionedArc<Metadata>) => number | null
139
+ color_scale?: D3InterpolateName
140
+ color_range?: Vec2 // defaults to the metric's [min, max]
141
+ colorbar?: ComponentProps<typeof ColorBar> | null // null hides it
142
+ export_buttons?: boolean // SVG/PNG download buttons in the controls pane
143
+ export_filename?: string
144
+ tween?: TweenOptions<{ x0: number; x1: number; y0: number; n_rings: number }>
145
+ value_format?: string
146
+ padding?: Sides
147
+ legend?: LegendConfig | null
148
+ show_legend?: boolean
149
+ tooltip?: Snippet<[SunburstNodeHandlerProps<Metadata>]>
150
+ // Fully replace the default arc path. NOTE: this also replaces the built-in
151
+ // hover/focus/click + tooltip wiring, so re-implement any interactivity you
152
+ // need inside the snippet.
153
+ arc_content?: Snippet<
154
+ [{ arc: PositionedArc<Metadata>; a0: number; a1: number; r0: number; r1: number }]
155
+ >
156
+ center_content?: Snippet<
157
+ [{ root: PositionedArc<Metadata> | null; radius: number; zoomed: boolean }]
158
+ >
159
+ change?: (data: SunburstNodeHandlerProps<Metadata> | null) => void
160
+ on_node_click?: (
161
+ data: SunburstNodeHandlerProps<Metadata> & { event: MouseEvent | KeyboardEvent },
162
+ ) => void
163
+ on_node_hover?: (
164
+ data: (SunburstNodeHandlerProps<Metadata> & { event: MouseEvent | FocusEvent }) | null,
165
+ ) => void
166
+ on_zoom?: (data: { root: SunburstNodeHandlerProps<Metadata> | null }) => void
167
+ header_controls?: Snippet<[{ height: number; width: number; fullscreen: boolean }]>
168
+ controls_extra?: Snippet<[{ zoom_root_id: string | number | null }]>
169
+ } = $props()
149
170
 
150
171
  let [width, height] = $state([0, 0])
151
172
  let wrapper: HTMLDivElement | undefined = $state()
152
173
  let svg_element: SVGSVGElement | null = $state(null)
153
174
  let center_el: SVGCircleElement | null = $state(null)
175
+ // Unique per instance so multiple sunbursts on one page don't collide on
176
+ // the hatch pattern's SVG id.
177
+ const uid = $props.id()
178
+ const hatch_pattern_id = `sunburst-hatch-${uid}`
154
179
 
155
180
  let hovered_idx = $state<number | null>(null)
156
181
  let hover_info = $state<SunburstNodeHandlerProps<Metadata> | null>(null)
@@ -161,38 +186,28 @@
161
186
  let pad = $derived(filter_padding(padding, DEFAULT_PADDING))
162
187
  let inner_width = $derived(Math.max(0, width - pad.l - pad.r))
163
188
  let avail_height = $derived(Math.max(0, height - pad.t - pad.b))
164
- // measured height of the bottom colorbar, reserved from the chart so it never overlaps
165
- // the arcs (16px covers its bottom offset + a small gap). reset to 0 when the colorbar
166
- // is hidden (effect below) since bind:clientHeight doesn't clear on unmount; capped at
167
- // half the area so a bad measurement can't collapse the chart
189
+ // measured height of the bottom colorbar (via observe_height, which resets it
190
+ // to 0 on unmount), reserved from the chart so it never overlaps the arcs
191
+ // (16px covers its bottom offset + a small gap); capped at half the area so a
192
+ // bad measurement can't collapse the chart
168
193
  let colorbar_height = $state(0)
169
194
  let colorbar_reserve = $derived(
170
195
  colorbar_height > 0 ? Math.min(colorbar_height + 16, avail_height / 2) : 0,
171
196
  )
172
197
  let inner_height = $derived(avail_height - colorbar_reserve)
173
198
 
174
- // Degrade to an empty layout (instead of crashing the host page) on invalid data.
175
199
  // 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,
200
+ let layout = $derived(
201
+ safe_hierarchy_layout(data, {
202
+ value_mode,
203
+ sort,
204
+ level_lighten,
205
+ min_fraction,
206
+ other_label,
207
+ }),
195
208
  )
209
+ // Resolve the zoom root; stale ids (e.g. after a data swap) fall back to the root
210
+ let zoom_root = $derived(layout.arcs.find((arc) => arc.id === zoom_root_id) ?? layout.root)
196
211
  let zoomed = $derived((zoom_root?.depth ?? 0) > 0)
197
212
 
198
213
  // Drop muted ids that no longer exist when data changes (untrack avoids a
@@ -200,11 +215,9 @@
200
215
  // Hover/focus state is index-based, so a layout swap would otherwise leave a stale
201
216
  // tooltip and highlight whatever unrelated node now occupies the old index.
202
217
  $effect(() => {
203
- const valid = new Set(
204
- layout.arcs.filter((arc) => arc.depth === 1).map((arc) => arc.id),
205
- )
218
+ const { arcs } = layout
206
219
  untrack(() => {
207
- for (const id of muted_ids) if (!valid.has(id)) muted_ids.delete(id)
220
+ prune_muted_ids(arcs, muted_ids)
208
221
  set_arc_hover(null)
209
222
  focused_idx = null
210
223
  })
@@ -274,7 +287,7 @@
274
287
  const screen_path = (screen: ScreenArc): string =>
275
288
  shape === `icicle`
276
289
  ? `M${screen.a0},${screen.r0}H${screen.a1}V${screen.r1}H${screen.a0}Z`
277
- : arc_gen(screen) ?? ``
290
+ : (arc_gen(screen) ?? ``)
278
291
 
279
292
  // The chart group's transform: sunburst draws around the center, icicle from the
280
293
  // top-left of the padded plot area
@@ -293,109 +306,36 @@
293
306
  }
294
307
 
295
308
  // 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
- })
309
+ // metric on a d3 colormap (arcs returning null keep their categorical color)
310
+ let metric = $derived(
311
+ compute_metric_colors(layout.arcs, color_values, color_scale, color_range),
312
+ )
315
313
  const arc_color = (arc: PositionedArc<Metadata>): string =>
316
314
  metric?.colors[arc.node_idx] ?? arc.color
317
- // release the colorbar's reserved chart space when it's not rendered
318
- $effect(() => {
319
- if (!metric || colorbar == null) colorbar_height = 0
320
- })
321
-
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
- }
315
+ // Hovered arc + its ancestors/descendants stay fully opaque, others dim
316
+ let arc_dim = $derived(compute_node_dim(layout.arcs, muted_ids, hovered_idx))
343
317
 
344
318
  // 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
- }
319
+ // explicit colors or level_lighten would hide white labels, esp. when highlighted)
320
+ const contrast_for = make_cached_contrast()
358
321
 
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}`
322
+ const make_node_props = (arc: PositionedArc<Metadata>): SunburstNodeHandlerProps<Metadata> =>
323
+ node_handler_props(layout.arcs, arc, arc_color(arc))
363
324
 
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
- ) {
325
+ function set_arc_hover(screen: ScreenArc | null, event?: MouseEvent | FocusEvent) {
386
326
  // Same arc as before: only the cursor anchor moves - skip rebuilding the handler
387
327
  // payload and re-firing change/on_node_hover on every mousemove within an arc.
388
328
  // Requires hover_info: legend item hover sets hovered_idx alone (for dimming), and
389
329
  // skipping then would leave the arc's own tooltip permanently suppressed.
390
330
  if (screen && screen.arc.node_idx === hovered_idx && hover_info) {
391
- hover_pos = event_pos(event) ?? hover_pos
331
+ hover_pos = pointer_pos(event, svg_element) ?? hover_pos
392
332
  return
393
333
  }
394
334
  if (screen) {
395
335
  hovered = true
396
336
  hovered_idx = screen.arc.node_idx
397
337
  hover_info = make_node_props(screen.arc)
398
- hover_pos = event_pos(event) ?? arc_center(screen)
338
+ hover_pos = pointer_pos(event, svg_element) ?? arc_center(screen)
399
339
  change(hover_info)
400
340
  if (event) on_node_hover?.({ ...hover_info, event })
401
341
  } else {
@@ -412,7 +352,7 @@
412
352
 
413
353
  const screen_arc_from_event = (event: Event): ScreenArc | null => {
414
354
  const idx = closest_data_idx(event, `data-sunburst-node-idx`, svg_element)
415
- return idx == null ? null : screen_arcs[idx] ?? null
355
+ return idx == null ? null : (screen_arcs[idx] ?? null)
416
356
  }
417
357
 
418
358
  function handle_arc_hover_event(event: MouseEvent | FocusEvent) {
@@ -424,25 +364,15 @@
424
364
 
425
365
  // Re-root the view on the given arc (or the data root when null) and notify
426
366
  function zoom_to(arc: PositionedArc<Metadata> | null) {
427
- zoom_root_id = arc && arc.depth > 0 ? arc.id : null
367
+ const root = arc && arc.depth > 0 ? arc : null
368
+ zoom_root_id = root?.id ?? null
428
369
  // The clicked arc collapses into the hole - drop the now-stale hover/tooltip
429
370
  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
- )
371
+ on_zoom?.({ root: root && make_node_props(root) })
442
372
  }
443
373
 
444
374
  function handle_arc_click(event: MouseEvent | KeyboardEvent) {
445
- if (event instanceof MouseEvent && selection_in_chart()) return
375
+ if (event instanceof MouseEvent && selection_within(wrapper)) return
446
376
  const screen = screen_arc_from_event(event)
447
377
  if (!screen) return
448
378
  const { arc } = screen
@@ -451,7 +381,7 @@
451
381
  }
452
382
 
453
383
  function zoom_out(event?: Event) {
454
- if (event instanceof MouseEvent && selection_in_chart()) return
384
+ if (event instanceof MouseEvent && selection_within(wrapper)) return
455
385
  if (!zoomed) return
456
386
  zoom_to(breadcrumb_arcs.at(-2) ?? null)
457
387
  }
@@ -461,7 +391,7 @@
461
391
  // the center zoom-out button already fired its own click action twice - compounding
462
392
  // a third full reset would teleport step-by-step zoom-outs straight to the root)
463
393
  function handle_dblclick(event: MouseEvent) {
464
- if (screen_arc_from_event(event) || selection_in_chart()) return
394
+ if (screen_arc_from_event(event) || selection_within(wrapper)) return
465
395
  const target = event.target as Element | null
466
396
  if (target?.closest?.(`.center-circle, .center-label`)) return
467
397
  if (zoomed) zoom_to(null)
@@ -489,8 +419,6 @@
489
419
  )
490
420
  }
491
421
 
492
- const is_activation_key = (evt: KeyboardEvent) => [`Enter`, ` `].includes(evt.key)
493
-
494
422
  function handle_arc_keydown(event: KeyboardEvent) {
495
423
  const nav_target = nav_target_from_event(event)
496
424
  if (nav_target != null) {
@@ -524,57 +452,48 @@
524
452
  const arc_clickable = (arc: PositionedArc<Metadata>): boolean =>
525
453
  Boolean(on_node_click) || (zoom_on_click && !arc.is_leaf)
526
454
 
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
- }
455
+ let label_font = $derived(svg_label_font(svg_element))
456
+ const cached_text_width = make_cached_text_width()
546
457
 
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
- }
557
-
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)
458
+ // Per-arc label text, measured width, fill/label colors, clickability and aria
459
+ // string - all view-independent, so computed once per layout/option change
460
+ // instead of per animation frame
561
461
  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
- }
462
+ compute_node_infos(layout.arcs, {
463
+ label_text,
464
+ value_format,
465
+ label_font,
466
+ color_for: arc_color,
467
+ text_width: cached_text_width,
468
+ contrast: contrast_for,
469
+ clickable: arc_clickable,
569
470
  }),
570
471
  )
571
472
 
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
473
+ // Downscale steps tried when no label variant fits at full size: narrow
474
+ // slices keep a (smaller) label instead of losing it entirely.
475
+ const LABEL_FONT_SCALES = [1, 0.85, 0.7]
476
+
477
+ // Label text + placement transform for an arc; null = no variant fits, hide
478
+ // the label. Full font size wins over richer text: within each scale the
479
+ // richest variant that fits is used (extended -> label -> label_short).
480
+ function label_attrs(
481
+ screen: ScreenArc,
482
+ ): { transform: string; text: string; font_scale: number } | null {
483
+ for (const font_scale of LABEL_FONT_SCALES) {
484
+ for (const { text, width: text_w } of arc_info[screen.arc.node_idx].variants) {
485
+ const transform = arc_label_transform(
486
+ screen,
487
+ text_w * font_scale,
488
+ shape,
489
+ label_rotation,
490
+ radius,
491
+ font_scale,
492
+ )
493
+ if (transform) return { transform, text, font_scale }
494
+ }
495
+ }
496
+ return null
578
497
  }
579
498
 
580
499
  // Legend: one item per depth-1 category, toggling mutes (dims) rather than removes.
@@ -596,21 +515,12 @@
596
515
  points: settled.map(arc_center),
597
516
  })
598
517
  })
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
- }))
518
+ let legend_data: LegendItem[] = $derived(
519
+ hierarchy_legend_items(depth1_arcs, muted_ids, arc_color),
606
520
  )
607
521
 
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
- }
522
+ const toggle_category = (series_idx: number) =>
523
+ toggle_muted(muted_ids, depth1_arcs[series_idx]?.id)
614
524
 
615
525
  $effect(() => set_fullscreen_bg(wrapper, fullscreen, `--sunburst-fullscreen-bg`))
616
526
 
@@ -619,62 +529,24 @@
619
529
  let zoom_out_label = $derived.by(() => {
620
530
  const parent = breadcrumb_arcs.at(-2)
621
531
  if (!parent) return ``
622
- return parent.depth === 0 ? `full chart` : arc_name(parent)
532
+ return parent.depth === 0 ? `full chart` : node_display_name(parent)
623
533
  })
624
534
 
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
635
- })
535
+ let breadcrumb_arcs = $derived(ancestor_chain(layout.arcs, zoom_root))
636
536
 
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
- ]
650
-
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
- }
537
+ const export_chart = (format: `svg` | `png`) =>
538
+ export_hierarchy_chart(svg_element, export_filename, format)
659
539
  </script>
660
540
 
661
541
  <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
- }}
542
+ onkeydown={(evt) =>
543
+ handle_hierarchy_escape(evt, {
544
+ wrapper,
545
+ fullscreen,
546
+ zoomed,
547
+ zoom_out: () => zoom_out(),
548
+ exit_fullscreen: () => (fullscreen = false),
549
+ })}
678
550
  />
679
551
 
680
552
  <div
@@ -682,7 +554,7 @@
682
554
  bind:clientWidth={width}
683
555
  bind:clientHeight={height}
684
556
  {...rest}
685
- class="sunburst {rest.class ?? ``}"
557
+ class={[`sunburst`, rest.class]}
686
558
  class:fullscreen
687
559
  class:icicle={shape === `icicle`}
688
560
  >
@@ -691,6 +563,7 @@
691
563
  {@render header_controls?.({ height, width, fullscreen })}
692
564
  {#if show_controls}
693
565
  <SunburstControls
566
+ chart="sunburst"
694
567
  toggle_props={{
695
568
  ...controls_toggle_props,
696
569
  // join the header flex row instead of absolute positioning (overrides
@@ -731,7 +604,7 @@
731
604
  disabled={crumb_idx === breadcrumb_arcs.length - 1}
732
605
  onclick={() => zoom_to(crumb)}
733
606
  >
734
- {crumb.depth === 0 ? `all` : crumb.label ?? crumb.id}
607
+ {crumb.depth === 0 ? `all` : (crumb.label ?? crumb.id)}
735
608
  </button>
736
609
  {/each}
737
610
  </nav>
@@ -744,6 +617,12 @@
744
617
  aria-label={rest[`aria-label`] ?? `${shape === `icicle` ? `Icicle` : `Sunburst`} chart`}
745
618
  onmouseleave={() => set_arc_hover(null)}
746
619
  >
620
+ <!-- inert unless some arc references it via fill -->
621
+ <defs>
622
+ <pattern id={hatch_pattern_id} patternUnits="userSpaceOnUse" width="8" height="8">
623
+ <path class="hatch-pattern-line" d="M-1,1 l2,-2 M0,8 l8,-8 M7,9 l2,-2" />
624
+ </pattern>
625
+ </defs>
747
626
  <!-- Hover/click delegation sits on the chart group (not the arcs group) so
748
627
  labels - which carry the same data-sunburst-node-idx and are selectable text -
749
628
  forward interactions to their arc instead of swallowing them -->
@@ -764,20 +643,37 @@
764
643
  {#if arc_content}
765
644
  {@render arc_content(screen)}
766
645
  {:else}
767
- {@const clickable = arc_clickable(screen.arc)}
646
+ <!-- @const so the path is generated (and info looked up) once per
647
+ arc per frame, shared by the base path and the hatch overlay -->
648
+ {@const info = arc_info[screen.arc.node_idx]}
649
+ {@const opacity = arc_dim[screen.arc.node_idx].opacity}
650
+ {@const path_d = screen_path(screen)}
768
651
  <!-- svelte-ignore a11y_no_static_element_interactions -->
769
652
  <path
770
- d={screen_path(screen)}
653
+ d={path_d}
771
654
  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)
655
+ fill={info.fill}
656
+ fill-opacity={opacity}
657
+ role={info.clickable ? `button` : undefined}
658
+ tabindex={info.clickable
659
+ ? screen.arc.node_idx === roving_idx
660
+ ? 0
661
+ : -1
777
662
  : undefined}
778
- aria-label={clickable ? arc_info[screen.arc.node_idx].aria : undefined}
779
- style:cursor={clickable ? `pointer` : `default`}
663
+ aria-label={info.clickable ? info.aria : undefined}
664
+ style:cursor={info.clickable ? `pointer` : `default`}
780
665
  />
666
+ {#if screen.arc.hatch}
667
+ <!-- Decorative texture overlay (e.g. preemptible jobs); rendered as
668
+ the base path's next sibling so the hover 'pull' can track it -->
669
+ <path
670
+ class="arc-hatch"
671
+ aria-hidden="true"
672
+ d={path_d}
673
+ fill="url(#{hatch_pattern_id})"
674
+ fill-opacity={opacity}
675
+ />
676
+ {/if}
781
677
  {/if}
782
678
  {/each}
783
679
  </g>
@@ -789,13 +685,15 @@
789
685
  {#each visible_arcs as screen (screen.arc.node_idx)}
790
686
  {@const lbl = label_attrs(screen)}
791
687
  {#if lbl}
688
+ {@const info = arc_info[screen.arc.node_idx]}
792
689
  <text
793
690
  class="arc-label"
794
691
  data-sunburst-node-idx={screen.arc.node_idx}
795
692
  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`}
693
+ fill={info.label_fill}
694
+ fill-opacity={arc_dim[screen.arc.node_idx].label_opacity}
695
+ style:cursor={info.clickable ? `pointer` : `text`}
696
+ style:font-size={lbl.font_scale === 1 ? undefined : `${lbl.font_scale}em`}
799
697
  >
800
698
  {lbl.text}
801
699
  </text>
@@ -857,14 +755,13 @@
857
755
  {#if tooltip}
858
756
  {@render tooltip(hover_info)}
859
757
  {: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
758
+ <strong>{hover_info.label_path.join(` › `)}</strong>: {format_value(
759
+ hover_info.value,
760
+ value_format,
761
+ )}
762
+ ({format_value(hover_info.fraction, `.1%`)} of total{hover_info.depth > 1
865
763
  ? `, ${format_value(hover_info.parent_fraction, `.1%`)} of parent`
866
- : ``
867
- })
764
+ : ``})
868
765
  {/if}
869
766
  </PlotTooltip>
870
767
  {/if}
@@ -878,9 +775,10 @@
878
775
  series_data={legend_data}
879
776
  on_toggle={legend?.on_toggle ?? toggle_category}
880
777
  on_item_hover={(item) =>
881
- (hovered_idx = item != null && item.series_idx >= 0
882
- ? depth1_arcs[item.series_idx]?.node_idx ?? null
883
- : null)}
778
+ (hovered_idx =
779
+ item != null && item.series_idx >= 0
780
+ ? (depth1_arcs[item.series_idx]?.node_idx ?? null)
781
+ : null)}
884
782
  style={`position: absolute; left: ${legend_left}px; top: ${legend_top}px; pointer-events: auto; ${
885
783
  legend?.style ?? ``
886
784
  }`}
@@ -888,17 +786,19 @@
888
786
  {/if}
889
787
 
890
788
  {#if metric && colorbar != null}
891
- <div
892
- 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;"
894
- >
895
- <ColorBar
896
- color_scale={color_scale}
897
- range={metric.range}
898
- {...colorbar}
899
- wrapper_style={`width: 100%; ${colorbar?.wrapper_style ?? ``}`}
900
- />
901
- </div>
789
+ <!-- positioning + height measurement live on ColorBar's own root (via rest
790
+ props + attachment) instead of an extra wrapper div -->
791
+ <ColorBar
792
+ {color_scale}
793
+ range={metric.range}
794
+ {...colorbar}
795
+ wrapper_style="{colorbar?.orientation === `vertical`
796
+ ? `--cbar-height: var(--sunburst-colorbar-height, 150px);`
797
+ : ``} {colorbar?.wrapper_style ?? ``}"
798
+ 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; {colorbar?.style ??
799
+ ``}"
800
+ {@attach observe_height((px) => (colorbar_height = px))}
801
+ />
902
802
  {/if}
903
803
 
904
804
  {@render children?.({ height, width, fullscreen })}
@@ -917,6 +817,7 @@
917
817
  flex: var(--sunburst-flex, 1 1 auto);
918
818
  display: var(--sunburst-display, flex);
919
819
  flex-direction: column;
820
+ /* Use the plot background by default; set --sunburst-bg to override it. */
920
821
  background: var(--sunburst-bg, var(--plot-bg));
921
822
  border-radius: var(--sunburst-border-radius, var(--border-radius, 3pt));
922
823
  }
@@ -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
  }