matterviz 0.3.7 → 0.4.1

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 (486) hide show
  1. package/dist/Icon.svelte +7 -4
  2. package/dist/MillerIndexInput.svelte +1 -1
  3. package/dist/api/optimade.js +32 -26
  4. package/dist/app.css +0 -3
  5. package/dist/brillouin/BrillouinZone.svelte +76 -148
  6. package/dist/brillouin/BrillouinZone.svelte.d.ts +6 -14
  7. package/dist/brillouin/BrillouinZoneExportPane.svelte +43 -96
  8. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneInfoPane.svelte +9 -32
  10. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
  11. package/dist/brillouin/BrillouinZoneScene.svelte +97 -205
  12. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +4 -23
  13. package/dist/brillouin/BrillouinZoneTooltip.svelte +16 -25
  14. package/dist/brillouin/ReciprocalVectors.svelte +39 -0
  15. package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
  16. package/dist/brillouin/compute.d.ts +2 -0
  17. package/dist/brillouin/compute.js +89 -90
  18. package/dist/brillouin/geometry.d.ts +8 -0
  19. package/dist/brillouin/geometry.js +57 -0
  20. package/dist/brillouin/index.d.ts +2 -0
  21. package/dist/brillouin/index.js +2 -0
  22. package/dist/brillouin/types.d.ts +2 -2
  23. package/dist/chempot-diagram/ChemPotDiagram.svelte +14 -13
  24. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
  25. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +109 -203
  26. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
  27. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +180 -470
  28. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
  29. package/dist/chempot-diagram/async-compute.svelte.js +3 -1
  30. package/dist/chempot-diagram/chempot-worker.js +2 -1
  31. package/dist/chempot-diagram/color.d.ts +3 -6
  32. package/dist/chempot-diagram/color.js +5 -5
  33. package/dist/chempot-diagram/compute.d.ts +4 -4
  34. package/dist/chempot-diagram/compute.js +20 -20
  35. package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
  36. package/dist/chempot-diagram/controls-state.svelte.js +42 -0
  37. package/dist/chempot-diagram/export.d.ts +47 -0
  38. package/dist/chempot-diagram/export.js +133 -0
  39. package/dist/chempot-diagram/index.d.ts +1 -0
  40. package/dist/chempot-diagram/index.js +1 -0
  41. package/dist/chempot-diagram/pointer.d.ts +0 -10
  42. package/dist/chempot-diagram/pointer.js +4 -4
  43. package/dist/chempot-diagram/types.d.ts +3 -3
  44. package/dist/colors/index.js +8 -7
  45. package/dist/composition/FormulaFilter.svelte +18 -11
  46. package/dist/composition/PieChart.svelte +11 -10
  47. package/dist/composition/chem-sys.d.ts +8 -0
  48. package/dist/composition/chem-sys.js +86 -0
  49. package/dist/composition/format.js +7 -4
  50. package/dist/composition/index.d.ts +1 -0
  51. package/dist/composition/index.js +1 -0
  52. package/dist/composition/parse.d.ts +0 -1
  53. package/dist/composition/parse.js +41 -31
  54. package/dist/controls.d.ts +1 -0
  55. package/dist/controls.js +0 -1
  56. package/dist/convex-hull/ConvexHull.svelte +8 -10
  57. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
  58. package/dist/convex-hull/ConvexHull2D.svelte +106 -185
  59. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  60. package/dist/convex-hull/ConvexHull3D.svelte +179 -683
  61. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  62. package/dist/convex-hull/ConvexHull4D.svelte +183 -687
  63. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  64. package/dist/convex-hull/ConvexHullChrome.svelte +268 -0
  65. package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
  66. package/dist/convex-hull/ConvexHullControls.svelte +88 -7
  67. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
  68. package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
  69. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
  70. package/dist/convex-hull/ConvexHullStats.svelte +36 -175
  71. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
  72. package/dist/convex-hull/ConvexHullTooltip.svelte +11 -2
  73. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
  74. package/dist/convex-hull/GasPressureControls.svelte +4 -4
  75. package/dist/convex-hull/TemperatureSlider.svelte +2 -2
  76. package/dist/convex-hull/barycentric-coords.d.ts +2 -4
  77. package/dist/convex-hull/barycentric-coords.js +6 -33
  78. package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
  79. package/dist/convex-hull/canvas-interactions.svelte.js +278 -0
  80. package/dist/convex-hull/demo-temperature.d.ts +1 -1
  81. package/dist/convex-hull/demo-temperature.js +20 -22
  82. package/dist/convex-hull/gas-thermodynamics.d.ts +2 -2
  83. package/dist/convex-hull/gas-thermodynamics.js +22 -30
  84. package/dist/convex-hull/helpers.d.ts +42 -7
  85. package/dist/convex-hull/helpers.js +171 -78
  86. package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
  87. package/dist/convex-hull/hull-state.svelte.js +124 -0
  88. package/dist/convex-hull/index.d.ts +10 -8
  89. package/dist/convex-hull/index.js +7 -2
  90. package/dist/convex-hull/thermodynamics.js +136 -960
  91. package/dist/convex-hull/types.d.ts +13 -5
  92. package/dist/convex-hull/types.js +12 -0
  93. package/dist/coordination/CoordinationBarPlot.svelte +27 -34
  94. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  95. package/dist/element/BohrAtom.svelte +2 -1
  96. package/dist/element/index.d.ts +4 -0
  97. package/dist/element/index.js +18 -0
  98. package/dist/feedback/DragOverlay.svelte +3 -1
  99. package/dist/feedback/DragOverlay.svelte.d.ts +1 -0
  100. package/dist/feedback/StatusMessage.svelte +13 -3
  101. package/dist/fermi-surface/FermiSlice.svelte +13 -5
  102. package/dist/fermi-surface/FermiSurface.svelte +78 -151
  103. package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -14
  104. package/dist/fermi-surface/FermiSurfaceControls.svelte +1 -1
  105. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  106. package/dist/fermi-surface/FermiSurfaceScene.svelte +72 -221
  107. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
  108. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +8 -34
  109. package/dist/fermi-surface/compute.js +67 -66
  110. package/dist/fermi-surface/export.js +6 -16
  111. package/dist/fermi-surface/index.d.ts +0 -1
  112. package/dist/fermi-surface/index.js +0 -1
  113. package/dist/fermi-surface/parse.d.ts +1 -1
  114. package/dist/fermi-surface/parse.js +71 -79
  115. package/dist/fermi-surface/types.d.ts +3 -2
  116. package/dist/heatmap-matrix/HeatmapMatrix.svelte +69 -52
  117. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +4 -3
  118. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +3 -2
  119. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
  120. package/dist/heatmap-matrix/index.d.ts +3 -2
  121. package/dist/heatmap-matrix/index.js +1 -1
  122. package/dist/index.d.ts +1 -0
  123. package/dist/index.js +1 -0
  124. package/dist/io/ExportPane.svelte +166 -0
  125. package/dist/io/ExportPane.svelte.d.ts +17 -0
  126. package/dist/io/decompress.js +5 -4
  127. package/dist/io/export.d.ts +9 -5
  128. package/dist/io/export.js +77 -51
  129. package/dist/io/fetch.d.ts +2 -1
  130. package/dist/io/fetch.js +5 -1
  131. package/dist/io/file-drop.d.ts +8 -1
  132. package/dist/io/file-drop.js +48 -36
  133. package/dist/io/index.d.ts +2 -0
  134. package/dist/io/index.js +10 -0
  135. package/dist/io/types.d.ts +13 -0
  136. package/dist/io/url-drop.js +64 -33
  137. package/dist/isosurface/parse.js +52 -51
  138. package/dist/isosurface/slice.js +5 -4
  139. package/dist/isosurface/types.js +1 -1
  140. package/dist/keyboard.d.ts +3 -0
  141. package/dist/keyboard.js +23 -0
  142. package/dist/labels.d.ts +1 -1
  143. package/dist/labels.js +9 -8
  144. package/dist/layout/FullscreenButton.svelte +33 -0
  145. package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
  146. package/dist/layout/FullscreenToggle.svelte +8 -14
  147. package/dist/layout/PropertyFilter.svelte +3 -2
  148. package/dist/layout/SettingsSection.svelte +1 -1
  149. package/dist/layout/ViewerChrome.svelte +116 -0
  150. package/dist/layout/ViewerChrome.svelte.d.ts +17 -0
  151. package/dist/layout/fullscreen.d.ts +4 -0
  152. package/dist/layout/fullscreen.svelte.d.ts +8 -0
  153. package/dist/layout/fullscreen.svelte.js +37 -0
  154. package/dist/layout/index.d.ts +3 -0
  155. package/dist/layout/index.js +3 -0
  156. package/dist/layout/json-tree/JsonNode.svelte +1 -1
  157. package/dist/layout/json-tree/JsonTree.svelte +2 -2
  158. package/dist/layout/json-tree/utils.js +5 -4
  159. package/dist/marching-cubes.js +8 -13
  160. package/dist/math.d.ts +12 -4
  161. package/dist/math.js +42 -30
  162. package/dist/overlays/DraggablePane.svelte +4 -4
  163. package/dist/overlays/index.d.ts +4 -0
  164. package/dist/periodic-table/PeriodicTable.svelte +27 -15
  165. package/dist/periodic-table/PropertySelect.svelte +1 -0
  166. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +9 -3
  167. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  168. package/dist/phase-diagram/PhaseDiagramControls.svelte +3 -2
  169. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
  170. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +4 -2
  171. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
  172. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +47 -132
  173. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
  174. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +1 -1
  175. package/dist/phase-diagram/build-diagram.js +2 -2
  176. package/dist/phase-diagram/colors.js +1 -1
  177. package/dist/phase-diagram/parse.d.ts +2 -1
  178. package/dist/phase-diagram/parse.js +6 -5
  179. package/dist/phase-diagram/types.d.ts +1 -1
  180. package/dist/phase-diagram/utils.d.ts +3 -3
  181. package/dist/phase-diagram/utils.js +8 -12
  182. package/dist/plot/{BarPlot.svelte → bar/BarPlot.svelte} +246 -841
  183. package/dist/plot/{BarPlot.svelte.d.ts → bar/BarPlot.svelte.d.ts} +8 -16
  184. package/dist/plot/{BarPlotControls.svelte → bar/BarPlotControls.svelte} +6 -5
  185. package/dist/plot/{BarPlotControls.svelte.d.ts → bar/BarPlotControls.svelte.d.ts} +3 -3
  186. package/dist/plot/{SpacegroupBarPlot.svelte → bar/SpacegroupBarPlot.svelte} +8 -7
  187. package/dist/plot/{SpacegroupBarPlot.svelte.d.ts → bar/SpacegroupBarPlot.svelte.d.ts} +1 -1
  188. package/dist/plot/bar/data.d.ts +40 -0
  189. package/dist/plot/bar/data.js +154 -0
  190. package/dist/plot/bar/geometry.d.ts +39 -0
  191. package/dist/plot/bar/geometry.js +60 -0
  192. package/dist/plot/bar/index.d.ts +3 -0
  193. package/dist/plot/bar/index.js +3 -0
  194. package/dist/plot/box/BoxPlot.svelte +1292 -0
  195. package/dist/plot/box/BoxPlot.svelte.d.ts +95 -0
  196. package/dist/plot/box/BoxPlotControls.svelte +109 -0
  197. package/dist/plot/box/BoxPlotControls.svelte.d.ts +19 -0
  198. package/dist/plot/box/Violin.svelte +14 -0
  199. package/dist/plot/box/Violin.svelte.d.ts +70 -0
  200. package/dist/plot/box/box-plot.d.ts +56 -0
  201. package/dist/plot/box/box-plot.js +129 -0
  202. package/dist/plot/box/index.d.ts +5 -0
  203. package/dist/plot/box/index.js +5 -0
  204. package/dist/plot/box/kde.d.ts +17 -0
  205. package/dist/plot/box/kde.js +160 -0
  206. package/dist/plot/box/quantile.d.ts +3 -0
  207. package/dist/plot/box/quantile.js +53 -0
  208. package/dist/plot/{auto-place.d.ts → core/auto-place.d.ts} +1 -1
  209. package/dist/plot/{auto-place.js → core/auto-place.js} +6 -3
  210. package/dist/plot/core/axis-utils.d.ts +46 -0
  211. package/dist/plot/core/axis-utils.js +110 -0
  212. package/dist/plot/{AxisLabel.svelte → core/components/AxisLabel.svelte} +2 -2
  213. package/dist/plot/{AxisLabel.svelte.d.ts → core/components/AxisLabel.svelte.d.ts} +1 -1
  214. package/dist/plot/{ColorBar.svelte → core/components/ColorBar.svelte} +41 -38
  215. package/dist/plot/{ColorBar.svelte.d.ts → core/components/ColorBar.svelte.d.ts} +7 -6
  216. package/dist/plot/{ColorScaleSelect.svelte → core/components/ColorScaleSelect.svelte} +4 -3
  217. package/dist/plot/{ColorScaleSelect.svelte.d.ts → core/components/ColorScaleSelect.svelte.d.ts} +2 -2
  218. package/dist/plot/core/components/ControlPane.svelte +46 -0
  219. package/dist/plot/core/components/ControlPane.svelte.d.ts +13 -0
  220. package/dist/plot/{FillArea.svelte → core/components/FillArea.svelte} +17 -6
  221. package/dist/plot/{FillArea.svelte.d.ts → core/components/FillArea.svelte.d.ts} +1 -1
  222. package/dist/plot/{InteractiveAxisLabel.svelte → core/components/InteractiveAxisLabel.svelte} +3 -3
  223. package/dist/plot/{InteractiveAxisLabel.svelte.d.ts → core/components/InteractiveAxisLabel.svelte.d.ts} +2 -2
  224. package/dist/plot/{Line.svelte → core/components/Line.svelte} +33 -15
  225. package/dist/plot/{Line.svelte.d.ts → core/components/Line.svelte.d.ts} +3 -2
  226. package/dist/plot/{PlotAxis.svelte → core/components/PlotAxis.svelte} +9 -6
  227. package/dist/plot/{PlotAxis.svelte.d.ts → core/components/PlotAxis.svelte.d.ts} +5 -3
  228. package/dist/plot/{PlotControls.svelte → core/components/PlotControls.svelte} +17 -29
  229. package/dist/plot/core/components/PlotControls.svelte.d.ts +4 -0
  230. package/dist/plot/{PlotLegend.svelte → core/components/PlotLegend.svelte} +21 -10
  231. package/dist/plot/{PlotLegend.svelte.d.ts → core/components/PlotLegend.svelte.d.ts} +3 -2
  232. package/dist/plot/{PlotTooltip.svelte → core/components/PlotTooltip.svelte} +17 -1
  233. package/dist/plot/{PlotTooltip.svelte.d.ts → core/components/PlotTooltip.svelte.d.ts} +8 -0
  234. package/dist/plot/{PortalSelect.svelte → core/components/PortalSelect.svelte} +11 -7
  235. package/dist/plot/{ReferenceLine.svelte → core/components/ReferenceLine.svelte} +3 -3
  236. package/dist/plot/{ReferenceLine.svelte.d.ts → core/components/ReferenceLine.svelte.d.ts} +1 -1
  237. package/dist/plot/{ReferenceLine3D.svelte → core/components/ReferenceLine3D.svelte} +5 -5
  238. package/dist/plot/{ReferenceLine3D.svelte.d.ts → core/components/ReferenceLine3D.svelte.d.ts} +5 -5
  239. package/dist/plot/{ReferencePlane.svelte → core/components/ReferencePlane.svelte} +8 -8
  240. package/dist/plot/{ReferencePlane.svelte.d.ts → core/components/ReferencePlane.svelte.d.ts} +5 -5
  241. package/dist/plot/{ZeroLines.svelte → core/components/ZeroLines.svelte} +3 -3
  242. package/dist/plot/{ZeroLines.svelte.d.ts → core/components/ZeroLines.svelte.d.ts} +3 -3
  243. package/dist/plot/{ZoomRect.svelte → core/components/ZoomRect.svelte} +1 -1
  244. package/dist/plot/{ZoomRect.svelte.d.ts → core/components/ZoomRect.svelte.d.ts} +1 -1
  245. package/dist/plot/core/components/index.d.ts +17 -0
  246. package/dist/plot/core/components/index.js +17 -0
  247. package/dist/plot/{data-cleaning.d.ts → core/data-cleaning.d.ts} +71 -1
  248. package/dist/plot/{data-cleaning.js → core/data-cleaning.js} +21 -23
  249. package/dist/plot/{data-transform.d.ts → core/data-transform.d.ts} +2 -2
  250. package/dist/plot/{data-transform.js → core/data-transform.js} +3 -3
  251. package/dist/plot/core/fill-utils.d.ts +34 -0
  252. package/dist/plot/core/fill-utils.js +391 -0
  253. package/dist/plot/core/index.d.ts +10 -0
  254. package/dist/plot/core/index.js +11 -0
  255. package/dist/plot/core/interactions.d.ts +39 -0
  256. package/dist/plot/core/interactions.js +209 -0
  257. package/dist/plot/{layout.d.ts → core/layout.d.ts} +1 -0
  258. package/dist/plot/{layout.js → core/layout.js} +16 -8
  259. package/dist/plot/core/pan-zoom.svelte.d.ts +35 -0
  260. package/dist/plot/core/pan-zoom.svelte.js +221 -0
  261. package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
  262. package/dist/plot/core/placed-tween.svelte.js +68 -0
  263. package/dist/plot/{reference-line.d.ts → core/reference-line.d.ts} +11 -11
  264. package/dist/plot/{reference-line.js → core/reference-line.js} +29 -42
  265. package/dist/plot/core/scales.d.ts +40 -0
  266. package/dist/plot/{scales.js → core/scales.js} +94 -93
  267. package/dist/plot/core/svg.d.ts +3 -0
  268. package/dist/plot/core/svg.js +41 -0
  269. package/dist/plot/{types.d.ts → core/types.d.ts} +36 -85
  270. package/dist/plot/{types.js → core/types.js} +1 -1
  271. package/dist/plot/{utils → core/utils}/label-placement.d.ts +3 -3
  272. package/dist/plot/{utils → core/utils}/label-placement.js +3 -3
  273. package/dist/plot/core/utils/series-visibility.d.ts +26 -0
  274. package/dist/plot/{utils → core/utils}/series-visibility.js +29 -2
  275. package/dist/plot/core/utils.d.ts +12 -0
  276. package/dist/plot/core/utils.js +27 -0
  277. package/dist/plot/{Histogram.svelte → histogram/Histogram.svelte} +174 -551
  278. package/dist/plot/{Histogram.svelte.d.ts → histogram/Histogram.svelte.d.ts} +2 -2
  279. package/dist/plot/{HistogramControls.svelte → histogram/HistogramControls.svelte} +6 -6
  280. package/dist/plot/{HistogramControls.svelte.d.ts → histogram/HistogramControls.svelte.d.ts} +4 -4
  281. package/dist/plot/histogram/index.d.ts +2 -0
  282. package/dist/plot/histogram/index.js +2 -0
  283. package/dist/plot/index.d.ts +8 -41
  284. package/dist/plot/index.js +10 -39
  285. package/dist/plot/sankey/Sankey.svelte +697 -0
  286. package/dist/plot/sankey/Sankey.svelte.d.ts +74 -0
  287. package/dist/plot/sankey/SankeyControls.svelte +98 -0
  288. package/dist/plot/sankey/SankeyControls.svelte.d.ts +19 -0
  289. package/dist/plot/sankey/index.d.ts +4 -0
  290. package/dist/plot/sankey/index.js +3 -0
  291. package/dist/plot/sankey/sankey-types.d.ts +42 -0
  292. package/dist/plot/sankey/sankey-types.js +4 -0
  293. package/dist/plot/sankey/sankey.d.ts +52 -0
  294. package/dist/plot/sankey/sankey.js +189 -0
  295. package/dist/plot/{BinnedScatterPlot.svelte → scatter/BinnedScatterPlot.svelte} +64 -64
  296. package/dist/plot/{BinnedScatterPlot.svelte.d.ts → scatter/BinnedScatterPlot.svelte.d.ts} +6 -6
  297. package/dist/plot/{ElementScatter.svelte → scatter/ElementScatter.svelte} +6 -6
  298. package/dist/plot/{ElementScatter.svelte.d.ts → scatter/ElementScatter.svelte.d.ts} +2 -2
  299. package/dist/plot/{ScatterPlot.svelte → scatter/ScatterPlot.svelte} +297 -1008
  300. package/dist/plot/{ScatterPlot.svelte.d.ts → scatter/ScatterPlot.svelte.d.ts} +10 -18
  301. package/dist/plot/{ScatterPlotControls.svelte → scatter/ScatterPlotControls.svelte} +6 -5
  302. package/dist/plot/{ScatterPlotControls.svelte.d.ts → scatter/ScatterPlotControls.svelte.d.ts} +2 -2
  303. package/dist/plot/{ScatterPoint.svelte → scatter/ScatterPoint.svelte} +7 -7
  304. package/dist/plot/{ScatterPoint.svelte.d.ts → scatter/ScatterPoint.svelte.d.ts} +3 -3
  305. package/dist/plot/{adaptive-density.d.ts → scatter/adaptive-density.d.ts} +14 -4
  306. package/dist/plot/{adaptive-density.js → scatter/adaptive-density.js} +46 -20
  307. package/dist/plot/{binned-scatter-types.d.ts → scatter/binned-scatter-types.d.ts} +5 -12
  308. package/dist/plot/scatter/index.d.ts +7 -0
  309. package/dist/plot/scatter/index.js +5 -0
  310. package/dist/plot/scatter/scatter-data.d.ts +19 -0
  311. package/dist/plot/scatter/scatter-data.js +212 -0
  312. package/dist/plot/{ScatterPlot3D.svelte → scatter-3d/ScatterPlot3D.svelte} +25 -34
  313. package/dist/plot/{ScatterPlot3D.svelte.d.ts → scatter-3d/ScatterPlot3D.svelte.d.ts} +9 -17
  314. package/dist/plot/{ScatterPlot3DControls.svelte → scatter-3d/ScatterPlot3DControls.svelte} +14 -14
  315. package/dist/plot/{ScatterPlot3DControls.svelte.d.ts → scatter-3d/ScatterPlot3DControls.svelte.d.ts} +6 -6
  316. package/dist/plot/{ScatterPlot3DScene.svelte → scatter-3d/ScatterPlot3DScene.svelte} +129 -128
  317. package/dist/plot/{ScatterPlot3DScene.svelte.d.ts → scatter-3d/ScatterPlot3DScene.svelte.d.ts} +6 -15
  318. package/dist/plot/{Surface3D.svelte → scatter-3d/Surface3D.svelte} +7 -6
  319. package/dist/plot/{Surface3D.svelte.d.ts → scatter-3d/Surface3D.svelte.d.ts} +5 -4
  320. package/dist/plot/scatter-3d/index.d.ts +4 -0
  321. package/dist/plot/scatter-3d/index.js +4 -0
  322. package/dist/plot/sunburst/Sunburst.svelte +1041 -0
  323. package/dist/plot/sunburst/Sunburst.svelte.d.ts +97 -0
  324. package/dist/plot/sunburst/SunburstControls.svelte +200 -0
  325. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +26 -0
  326. package/dist/plot/sunburst/index.d.ts +4 -0
  327. package/dist/plot/sunburst/index.js +4 -0
  328. package/dist/plot/sunburst/render.d.ts +34 -0
  329. package/dist/plot/sunburst/render.js +122 -0
  330. package/dist/plot/sunburst/sunburst.d.ts +62 -0
  331. package/dist/plot/sunburst/sunburst.js +269 -0
  332. package/dist/rdf/RdfPlot.svelte +2 -1
  333. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  334. package/dist/rdf/calc-rdf.js +11 -24
  335. package/dist/sanitize.js +14 -3
  336. package/dist/scene/SceneCamera.svelte +62 -0
  337. package/dist/scene/SceneCamera.svelte.d.ts +19 -0
  338. package/dist/scene/bind-renderer.svelte.d.ts +2 -0
  339. package/dist/scene/bind-renderer.svelte.js +14 -0
  340. package/dist/scene/index.d.ts +4 -0
  341. package/dist/scene/index.js +5 -0
  342. package/dist/scene/props.js +52 -0
  343. package/dist/scene/types.d.ts +26 -0
  344. package/dist/scene/types.js +1 -0
  345. package/dist/settings.d.ts +79 -3
  346. package/dist/settings.js +321 -1
  347. package/dist/spectral/Bands.svelte +47 -36
  348. package/dist/spectral/Bands.svelte.d.ts +6 -6
  349. package/dist/spectral/BandsAndDos.svelte +23 -25
  350. package/dist/spectral/BrillouinBandsDos.svelte +42 -30
  351. package/dist/spectral/Dos.svelte +15 -23
  352. package/dist/spectral/Dos.svelte.d.ts +4 -3
  353. package/dist/spectral/helpers.d.ts +8 -6
  354. package/dist/spectral/helpers.js +137 -65
  355. package/dist/state.svelte.d.ts +0 -7
  356. package/dist/state.svelte.js +0 -6
  357. package/dist/structure/Arrow.svelte +2 -4
  358. package/dist/structure/AtomLegend.svelte +8 -9
  359. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  360. package/dist/structure/CanvasTooltip.svelte +1 -0
  361. package/dist/structure/CellSelect.svelte +12 -5
  362. package/dist/structure/CellSelect.svelte.d.ts +2 -1
  363. package/dist/structure/Cylinder.svelte +12 -8
  364. package/dist/structure/Cylinder.svelte.d.ts +4 -1
  365. package/dist/structure/Lattice.svelte +2 -2
  366. package/dist/structure/Structure.svelte +365 -423
  367. package/dist/structure/Structure.svelte.d.ts +5 -15
  368. package/dist/structure/StructureControls.svelte +217 -2
  369. package/dist/structure/StructureControls.svelte.d.ts +5 -3
  370. package/dist/structure/StructureExportPane.svelte +54 -156
  371. package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
  372. package/dist/structure/StructureInfoPane.svelte +10 -9
  373. package/dist/structure/StructureInfoPane.svelte.d.ts +5 -5
  374. package/dist/structure/StructureScene.svelte +376 -208
  375. package/dist/structure/StructureScene.svelte.d.ts +22 -20
  376. package/dist/structure/{label-placement.d.ts → atom-label-placement.d.ts} +3 -3
  377. package/dist/structure/{label-placement.js → atom-label-placement.js} +15 -5
  378. package/dist/structure/atom-properties.d.ts +1 -1
  379. package/dist/structure/atom-properties.js +17 -22
  380. package/dist/structure/bond-order-perception.js +3 -5
  381. package/dist/structure/bonding.d.ts +4 -0
  382. package/dist/structure/bonding.js +134 -63
  383. package/dist/structure/export.d.ts +24 -4
  384. package/dist/structure/export.js +89 -143
  385. package/dist/structure/index.d.ts +4 -4
  386. package/dist/structure/index.js +3 -3
  387. package/dist/structure/measure.d.ts +3 -2
  388. package/dist/structure/measure.js +6 -5
  389. package/dist/structure/parse.d.ts +3 -2
  390. package/dist/structure/parse.js +419 -438
  391. package/dist/structure/partial-occupancy.d.ts +0 -1
  392. package/dist/structure/partial-occupancy.js +1 -1
  393. package/dist/structure/pbc.d.ts +1 -1
  394. package/dist/structure/pbc.js +190 -13
  395. package/dist/structure/polyhedra.d.ts +41 -0
  396. package/dist/structure/polyhedra.js +602 -0
  397. package/dist/structure/site.d.ts +4 -0
  398. package/dist/structure/site.js +1 -0
  399. package/dist/structure/supercell.js +3 -2
  400. package/dist/structure/validation.js +5 -6
  401. package/dist/symmetry/SymmetryElementControls.svelte +69 -0
  402. package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
  403. package/dist/symmetry/SymmetryElements.svelte +354 -0
  404. package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
  405. package/dist/symmetry/SymmetryStats.svelte +113 -8
  406. package/dist/symmetry/WyckoffTable.svelte +68 -7
  407. package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
  408. package/dist/symmetry/cell-transform.js +7 -14
  409. package/dist/symmetry/index.d.ts +14 -4
  410. package/dist/symmetry/index.js +291 -72
  411. package/dist/symmetry/spacegroups.d.ts +12 -1
  412. package/dist/symmetry/spacegroups.js +63 -14
  413. package/dist/symmetry/symmetry-elements.d.ts +33 -0
  414. package/dist/symmetry/symmetry-elements.js +521 -0
  415. package/dist/symmetry/wyckoff-db.d.ts +9 -0
  416. package/dist/symmetry/wyckoff-db.js +87 -0
  417. package/dist/table/HeatmapTable.svelte +66 -25
  418. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  419. package/dist/table/index.d.ts +1 -3
  420. package/dist/table/index.js +1 -1
  421. package/dist/theme/index.js +8 -8
  422. package/dist/tooltip/KCoords.svelte +45 -0
  423. package/dist/tooltip/KCoords.svelte.d.ts +8 -0
  424. package/dist/tooltip/index.d.ts +1 -0
  425. package/dist/tooltip/index.js +1 -0
  426. package/dist/trajectory/Trajectory.svelte +123 -100
  427. package/dist/trajectory/Trajectory.svelte.d.ts +11 -22
  428. package/dist/trajectory/TrajectoryExportPane.svelte +17 -25
  429. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
  430. package/dist/trajectory/TrajectoryInfoPane.svelte +5 -3
  431. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
  432. package/dist/trajectory/constants.js +6 -2
  433. package/dist/trajectory/extract.js +17 -37
  434. package/dist/trajectory/format-detect.d.ts +1 -1
  435. package/dist/trajectory/format-detect.js +27 -19
  436. package/dist/trajectory/frame-reader.d.ts +0 -1
  437. package/dist/trajectory/frame-reader.js +63 -162
  438. package/dist/trajectory/helpers.d.ts +10 -2
  439. package/dist/trajectory/helpers.js +56 -36
  440. package/dist/trajectory/index.js +1 -1
  441. package/dist/trajectory/parse/ase.d.ts +9 -1
  442. package/dist/trajectory/parse/ase.js +47 -32
  443. package/dist/trajectory/parse/diagnostics.d.ts +3 -0
  444. package/dist/trajectory/parse/diagnostics.js +14 -0
  445. package/dist/trajectory/parse/hdf5.js +1 -1
  446. package/dist/trajectory/parse/index.d.ts +1 -1
  447. package/dist/trajectory/parse/index.js +65 -105
  448. package/dist/trajectory/parse/lammps.d.ts +0 -2
  449. package/dist/trajectory/parse/lammps.js +8 -6
  450. package/dist/trajectory/parse/pymatgen.d.ts +2 -0
  451. package/dist/trajectory/parse/pymatgen.js +74 -0
  452. package/dist/trajectory/parse/vasp.js +38 -18
  453. package/dist/trajectory/parse/xyz.d.ts +13 -1
  454. package/dist/trajectory/parse/xyz.js +102 -94
  455. package/dist/trajectory/plotting.d.ts +1 -2
  456. package/dist/trajectory/plotting.js +16 -113
  457. package/dist/utils.d.ts +2 -0
  458. package/dist/utils.js +7 -5
  459. package/dist/xrd/XrdPlot.svelte +16 -30
  460. package/dist/xrd/broadening.d.ts +2 -1
  461. package/dist/xrd/calc-xrd.js +18 -20
  462. package/dist/xrd/index.d.ts +2 -2
  463. package/dist/xrd/parse.js +2 -2
  464. package/package.json +43 -26
  465. package/dist/element/data.json +0 -11864
  466. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  467. package/dist/fermi-surface/marching-cubes.js +0 -2
  468. package/dist/plot/PlotControls.svelte.d.ts +0 -4
  469. package/dist/plot/axis-utils.d.ts +0 -19
  470. package/dist/plot/axis-utils.js +0 -78
  471. package/dist/plot/defaults.d.ts +0 -19
  472. package/dist/plot/defaults.js +0 -9
  473. package/dist/plot/fill-utils.d.ts +0 -46
  474. package/dist/plot/fill-utils.js +0 -322
  475. package/dist/plot/hover-lock.svelte.d.ts +0 -14
  476. package/dist/plot/hover-lock.svelte.js +0 -46
  477. package/dist/plot/interactions.d.ts +0 -12
  478. package/dist/plot/interactions.js +0 -101
  479. package/dist/plot/scales.d.ts +0 -48
  480. package/dist/plot/svg.d.ts +0 -1
  481. package/dist/plot/svg.js +0 -11
  482. package/dist/plot/utils/series-visibility.d.ts +0 -15
  483. package/dist/plot/utils.d.ts +0 -1
  484. package/dist/plot/utils.js +0 -14
  485. /package/dist/plot/{PortalSelect.svelte.d.ts → core/components/PortalSelect.svelte.d.ts} +0 -0
  486. /package/dist/plot/{binned-scatter-types.js → scatter/binned-scatter-types.js} +0 -0
@@ -0,0 +1,1041 @@
1
+ <script
2
+ lang="ts"
3
+ generics="Metadata extends Record<string, unknown> = Record<string, unknown>"
4
+ >
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
+ import { format_value } from '../../labels'
9
+ import { FullscreenToggle, set_fullscreen_bg } from '../../layout'
10
+ import { DEG_TO_RAD, type Vec2 } from '../../math'
11
+ import type {
12
+ BasePlotProps,
13
+ LegendConfig,
14
+ LegendItem,
15
+ SunburstLabelRotation,
16
+ SunburstLabelText,
17
+ SunburstNode,
18
+ SunburstNodeHandlerProps,
19
+ SunburstShape,
20
+ SunburstSort,
21
+ SunburstValueMode,
22
+ } from '..'
23
+ 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'
30
+ 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'
33
+ import type { ScreenArc as ScreenArcOf } from './render'
34
+ import { compute_sunburst_layout, type PositionedArc } from './sunburst'
35
+ import { DEFAULTS } from '../../settings'
36
+ import { arc as d3_arc } from 'd3-shape'
37
+ import { type ComponentProps, type Snippet, tick, untrack } from 'svelte'
38
+ import { cubicInOut } from 'svelte/easing'
39
+ import type { HTMLAttributes } from 'svelte/elements'
40
+ import { Tween, type TweenOptions } from 'svelte/motion'
41
+ import { SvelteSet } from 'svelte/reactivity'
42
+
43
+ const DEFAULT_PADDING: Required<Sides> = { t: 10, b: 10, l: 10, r: 10 }
44
+
45
+ // An arc with its current screen-space geometry (angles in radians, radii in px)
46
+ type ScreenArc = ScreenArcOf<Metadata>
47
+
48
+ let {
49
+ data = $bindable([]),
50
+ shape = $bindable(DEFAULTS.sunburst.shape),
51
+ value_mode = $bindable(DEFAULTS.sunburst.value_mode),
52
+ sort = `none`,
53
+ level_lighten = 0,
54
+ min_fraction = $bindable(DEFAULTS.sunburst.min_fraction),
55
+ other_label = `Other`,
56
+ max_depth = $bindable(DEFAULTS.sunburst.max_depth),
57
+ inner_radius = $bindable(DEFAULTS.sunburst.inner_radius),
58
+ pad_angle = $bindable(DEFAULTS.sunburst.pad_angle),
59
+ show_labels = $bindable(DEFAULTS.sunburst.show_labels),
60
+ label_rotation = $bindable(DEFAULTS.sunburst.label_rotation),
61
+ label_text = $bindable(DEFAULTS.sunburst.label_text),
62
+ zoom_on_click = $bindable(DEFAULTS.sunburst.zoom_on_click),
63
+ zoom_root_id = $bindable(null),
64
+ show_breadcrumbs = $bindable(DEFAULTS.sunburst.show_breadcrumbs),
65
+ color_values,
66
+ color_scale = `interpolateViridis`,
67
+ color_range,
68
+ colorbar = {},
69
+ export_buttons = true,
70
+ export_filename = `sunburst`,
71
+ tween,
72
+ value_format = `,`,
73
+ padding = DEFAULT_PADDING,
74
+ legend = {},
75
+ show_legend = false,
76
+ tooltip,
77
+ arc_content,
78
+ center_content,
79
+ hovered = $bindable(false),
80
+ change = () => {},
81
+ on_node_click,
82
+ on_node_hover,
83
+ on_zoom,
84
+ show_controls = $bindable(true),
85
+ controls_open = $bindable(false),
86
+ controls_toggle_props,
87
+ controls_pane_props,
88
+ fullscreen = $bindable(false),
89
+ fullscreen_toggle = true,
90
+ children,
91
+ header_controls,
92
+ controls_extra,
93
+ ...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()
149
+
150
+ let [width, height] = $state([0, 0])
151
+ let wrapper: HTMLDivElement | undefined = $state()
152
+ let svg_element: SVGSVGElement | null = $state(null)
153
+ let center_el: SVGCircleElement | null = $state(null)
154
+
155
+ let hovered_idx = $state<number | null>(null)
156
+ let hover_info = $state<SunburstNodeHandlerProps<Metadata> | null>(null)
157
+ let hover_pos = $state<{ x: number; y: number }>({ x: 0, y: 0 })
158
+ // Depth-1 category ids muted via legend toggle (dimmed, not removed - keeps layout stable)
159
+ let muted_ids = new SvelteSet<string | number>()
160
+
161
+ let pad = $derived(filter_padding(padding, DEFAULT_PADDING))
162
+ let inner_width = $derived(Math.max(0, width - pad.l - pad.r))
163
+ 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
168
+ let colorbar_height = $state(0)
169
+ let colorbar_reserve = $derived(
170
+ colorbar_height > 0 ? Math.min(colorbar_height + 16, avail_height / 2) : 0,
171
+ )
172
+ let inner_height = $derived(avail_height - colorbar_reserve)
173
+
174
+ // Degrade to an empty layout (instead of crashing the host page) on invalid data.
175
+ // 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,
195
+ )
196
+ let zoomed = $derived((zoom_root?.depth ?? 0) > 0)
197
+
198
+ // Drop muted ids that no longer exist when data changes (untrack avoids a
199
+ // self-trigger loop from reading/writing muted_ids in the same effect).
200
+ // Hover/focus state is index-based, so a layout swap would otherwise leave a stale
201
+ // tooltip and highlight whatever unrelated node now occupies the old index.
202
+ $effect(() => {
203
+ const valid = new Set(
204
+ layout.arcs.filter((arc) => arc.depth === 1).map((arc) => arc.id),
205
+ )
206
+ untrack(() => {
207
+ for (const id of muted_ids) if (!valid.has(id)) muted_ids.delete(id)
208
+ set_arc_hover(null)
209
+ focused_idx = null
210
+ })
211
+ })
212
+
213
+ // The view window in normalized partition coordinates: the zoom root's angular
214
+ // span + how many rings to show below it
215
+ let view_target = $derived.by(() => {
216
+ const below = layout.root ? layout.max_depth - (zoom_root?.depth ?? 0) : 1
217
+ return {
218
+ x0: zoom_root?.x0 ?? 0,
219
+ x1: zoom_root?.x1 ?? 1,
220
+ y0: zoom_root?.y0 ?? 0,
221
+ n_rings: Math.max(1, max_depth > 0 ? Math.min(max_depth, below) : below),
222
+ }
223
+ })
224
+
225
+ // Zooming tweens this single object; all arc geometry re-derives from view.current
226
+ // each frame via clamping scales (the classic zoomable-sunburst trick - no per-arc
227
+ // tweens, no re-layout). Tween.of seeds it at view_target (charts load fully drawn)
228
+ // then re-targets on change via a render-effect that reads only view_target, never
229
+ // view.current - so the tween can't feed back into its own target. untrack reads the
230
+ // tween options once at init (they're not meant to update reactively).
231
+ const view = Tween.of(
232
+ () => view_target,
233
+ untrack(() => ({ duration: 400, easing: cubicInOut, ...tween })),
234
+ )
235
+
236
+ // Pixel geometry
237
+ let radius = $derived(Math.max(0, Math.min(inner_width, inner_height) / 2))
238
+ let cx = $derived(pad.l + inner_width / 2)
239
+ let cy = $derived(pad.t + inner_height / 2)
240
+ // Min 14px center hole when zoomed so there's always a zoom-out click target
241
+ let hole_r = $derived(Math.max(inner_radius * radius, zoomed ? 14 : 0))
242
+
243
+ let screen_geom = $derived({ shape, inner_width, inner_height, radius, hole_r })
244
+
245
+ // Projected with view.current once per animation frame; project_arcs is also called
246
+ // with view.target where settled geometry suffices (e.g. legend placement, which
247
+ // shouldn't rerun per frame)
248
+ let projection = $derived(project_arcs(layout.arcs, view.current, screen_geom))
249
+ let screen_arcs = $derived(projection.all)
250
+ // Rendering iterates only non-collapsed arcs - when zoomed into a small subtree of
251
+ // a large hierarchy this keeps per-frame template work proportional to what's on screen
252
+ let visible_arcs = $derived(projection.visible)
253
+
254
+ // Roving tabindex: exactly one arc is in the tab order (the last-focused one, else
255
+ // the first visible clickable arc); arrow keys move focus between arcs. Without
256
+ // this, tabbing through a large chart would visit every single arc.
257
+ let focused_idx = $state<number | null>(null)
258
+ let roving_idx = $derived.by(() => {
259
+ if (focused_idx != null && screen_arcs[focused_idx]?.visible) return focused_idx
260
+ return visible_arcs.find((screen) => arc_clickable(screen.arc))?.arc.node_idx ?? null
261
+ })
262
+
263
+ let arc_gen = $derived(
264
+ d3_arc<ScreenArc>()
265
+ .startAngle((screen) => screen.a0)
266
+ .endAngle((screen) => screen.a1)
267
+ .innerRadius((screen) => screen.r0)
268
+ .outerRadius((screen) => screen.r1)
269
+ .padAngle(pad_angle * DEG_TO_RAD)
270
+ .padRadius(radius || 1),
271
+ )
272
+
273
+ // Path data for one arc/rect in the current shape
274
+ const screen_path = (screen: ScreenArc): string =>
275
+ shape === `icicle`
276
+ ? `M${screen.a0},${screen.r0}H${screen.a1}V${screen.r1}H${screen.a0}Z`
277
+ : arc_gen(screen) ?? ``
278
+
279
+ // The chart group's transform: sunburst draws around the center, icicle from the
280
+ // top-left of the padded plot area
281
+ let chart_transform = $derived(
282
+ shape === `icicle` ? `translate(${pad.l}, ${pad.t})` : `translate(${cx}, ${cy})`,
283
+ )
284
+
285
+ // Arc centroid in container (pad-offset) pixel space, for tooltip + legend placement
286
+ const arc_center = (screen: ScreenArc): { x: number; y: number } => {
287
+ if (shape === `icicle`) {
288
+ return { x: pad.l + (screen.a0 + screen.a1) / 2, y: pad.t + (screen.r0 + screen.r1) / 2 }
289
+ }
290
+ const mid_a = (screen.a0 + screen.a1) / 2
291
+ const mid_r = (screen.r0 + screen.r1) / 2
292
+ return { x: cx + Math.sin(mid_a) * mid_r, y: cy - Math.cos(mid_a) * mid_r }
293
+ }
294
+
295
+ // 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
+ })
315
+ const arc_color = (arc: PositionedArc<Metadata>): string =>
316
+ 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
+ }
343
+
344
+ // 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
+ }
358
+
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}`
363
+
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
+ ) {
386
+ // Same arc as before: only the cursor anchor moves - skip rebuilding the handler
387
+ // payload and re-firing change/on_node_hover on every mousemove within an arc.
388
+ // Requires hover_info: legend item hover sets hovered_idx alone (for dimming), and
389
+ // skipping then would leave the arc's own tooltip permanently suppressed.
390
+ if (screen && screen.arc.node_idx === hovered_idx && hover_info) {
391
+ hover_pos = event_pos(event) ?? hover_pos
392
+ return
393
+ }
394
+ if (screen) {
395
+ hovered = true
396
+ hovered_idx = screen.arc.node_idx
397
+ hover_info = make_node_props(screen.arc)
398
+ hover_pos = event_pos(event) ?? arc_center(screen)
399
+ change(hover_info)
400
+ if (event) on_node_hover?.({ ...hover_info, event })
401
+ } else {
402
+ // Already clear: don't re-fire change(null)/on_node_hover(null) - both the svg
403
+ // and chart group have mouseleave handlers, and zoom_to clears unconditionally
404
+ if (hovered_idx == null && hover_info == null) return
405
+ hovered = false
406
+ hovered_idx = null
407
+ hover_info = null
408
+ change(null)
409
+ on_node_hover?.(null)
410
+ }
411
+ }
412
+
413
+ const screen_arc_from_event = (event: Event): ScreenArc | null => {
414
+ const idx = closest_data_idx(event, `data-sunburst-node-idx`, svg_element)
415
+ return idx == null ? null : screen_arcs[idx] ?? null
416
+ }
417
+
418
+ function handle_arc_hover_event(event: MouseEvent | FocusEvent) {
419
+ const screen = screen_arc_from_event(event)
420
+ // roving tabindex follows keyboard focus
421
+ if (event.type === `focusin` && screen) focused_idx = screen.arc.node_idx
422
+ set_arc_hover(screen, event)
423
+ }
424
+
425
+ // Re-root the view on the given arc (or the data root when null) and notify
426
+ function zoom_to(arc: PositionedArc<Metadata> | null) {
427
+ zoom_root_id = arc && arc.depth > 0 ? arc.id : null
428
+ // The clicked arc collapses into the hole - drop the now-stale hover/tooltip
429
+ 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
+ )
442
+ }
443
+
444
+ function handle_arc_click(event: MouseEvent | KeyboardEvent) {
445
+ if (event instanceof MouseEvent && selection_in_chart()) return
446
+ const screen = screen_arc_from_event(event)
447
+ if (!screen) return
448
+ const { arc } = screen
449
+ on_node_click?.({ ...make_node_props(arc), event })
450
+ if (zoom_on_click && !arc.is_leaf && arc.id !== zoom_root?.id) zoom_to(arc)
451
+ }
452
+
453
+ function zoom_out(event?: Event) {
454
+ if (event instanceof MouseEvent && selection_in_chart()) return
455
+ if (!zoomed) return
456
+ zoom_to(breadcrumb_arcs.at(-2) ?? null)
457
+ }
458
+
459
+ // Double-clicking empty chart background resets the zoom to the root (double-
460
+ // clicking an arc or label is click-to-zoom/text-selection territory, not a reset;
461
+ // the center zoom-out button already fired its own click action twice - compounding
462
+ // a third full reset would teleport step-by-step zoom-outs straight to the root)
463
+ function handle_dblclick(event: MouseEvent) {
464
+ if (screen_arc_from_event(event) || selection_in_chart()) return
465
+ const target = event.target as Element | null
466
+ if (target?.closest?.(`.center-circle, .center-label`)) return
467
+ if (zoomed) zoom_to(null)
468
+ }
469
+
470
+ const focus_arc = (idx: number | null) => {
471
+ if (idx == null) return
472
+ svg_element
473
+ ?.querySelector<SVGPathElement>(`.arcs [data-sunburst-node-idx="${idx}"]`)
474
+ ?.focus()
475
+ }
476
+
477
+ // Arrow-key navigation: left/right cycle through visible siblings (wrapping),
478
+ // down enters the first child, up returns to the parent. The pre-order walk
479
+ // lives in render.ts (arrow_nav_target); this wrapper supplies the event's arc
480
+ // and the current screen-space visibility.
481
+ const nav_target_from_event = (event: KeyboardEvent): number | null => {
482
+ const cur = screen_arc_from_event(event)?.arc
483
+ if (!cur) return null
484
+ return arrow_nav_target(
485
+ layout.arcs,
486
+ (idx) => screen_arcs[idx]?.visible ?? false,
487
+ cur.node_idx,
488
+ event.key,
489
+ )
490
+ }
491
+
492
+ const is_activation_key = (evt: KeyboardEvent) => [`Enter`, ` `].includes(evt.key)
493
+
494
+ function handle_arc_keydown(event: KeyboardEvent) {
495
+ const nav_target = nav_target_from_event(event)
496
+ if (nav_target != null) {
497
+ event.preventDefault()
498
+ focus_arc(nav_target)
499
+ return
500
+ }
501
+ if (!is_activation_key(event)) return
502
+ event.preventDefault()
503
+ const prev_root = zoom_root_id
504
+ handle_arc_click(event)
505
+ // Zooming via keyboard unmounts the focused arc - move focus to the center circle
506
+ // (the zoom-out button) so keyboard users stay inside the chart. In icicle mode
507
+ // focus the new root's first child (pre-order: node_idx + 1): the clicked arc
508
+ // itself collapses to zero height once the zoom tween settles, so focusing it
509
+ // (the roving index) would drop focus to <body> mid-animation.
510
+ if (zoom_root_id !== prev_root) {
511
+ tick().then(() => {
512
+ if (shape === `sunburst`) center_el?.focus()
513
+ else focus_arc(zoom_root ? zoom_root.node_idx + 1 : roving_idx)
514
+ })
515
+ }
516
+ }
517
+
518
+ function handle_center_keydown(event: KeyboardEvent) {
519
+ if (!is_activation_key(event)) return
520
+ event.preventDefault()
521
+ zoom_out()
522
+ }
523
+
524
+ const arc_clickable = (arc: PositionedArc<Metadata>): boolean =>
525
+ Boolean(on_node_click) || (zoom_on_click && !arc.is_leaf)
526
+
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
+ }
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)
561
+ 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
+ }
569
+ }),
570
+ )
571
+
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
578
+ }
579
+
580
+ // Legend: one item per depth-1 category, toggling mutes (dims) rather than removes.
581
+ let depth1_arcs = $derived(layout.arcs.filter((arc) => arc.depth === 1))
582
+ let legend_visible = $derived(show_legend && legend != null && depth1_arcs.length > 1)
583
+ let legend_element = $state<HTMLDivElement | undefined>()
584
+ let legend_placement = $derived.by(() => {
585
+ if (!legend_visible || !width || !height) return null
586
+ // Place against the settled (target) geometry, not the animated view - placement
587
+ // is stable during zoom tweens and compute_element_placement runs once per zoom
588
+ // instead of once per frame
589
+ const settled = project_arcs(layout.arcs, view.target, screen_geom).visible
590
+ return compute_element_placement({
591
+ plot_bounds: { x: pad.l, y: pad.t, width: inner_width, height: inner_height },
592
+ element: legend_element,
593
+ element_size: { width: 120, height: 60 },
594
+ axis_clearance: legend?.axis_clearance,
595
+ exclude_rects: [],
596
+ points: settled.map(arc_center),
597
+ })
598
+ })
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
+ }))
606
+ )
607
+
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
+ }
614
+
615
+ $effect(() => set_fullscreen_bg(wrapper, fullscreen, `--sunburst-fullscreen-bg`))
616
+
617
+ let center_label = $derived(zoom_root?.label ?? (zoomed ? `${zoom_root?.id}` : ``))
618
+ // Where the center circle takes you on click (parent of the current zoom root)
619
+ let zoom_out_label = $derived.by(() => {
620
+ const parent = breadcrumb_arcs.at(-2)
621
+ 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
635
+ })
636
+
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
+ }
659
+ </script>
660
+
661
+ <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
+ }}
678
+ />
679
+
680
+ <div
681
+ bind:this={wrapper}
682
+ bind:clientWidth={width}
683
+ bind:clientHeight={height}
684
+ {...rest}
685
+ class="sunburst {rest.class ?? ``}"
686
+ class:fullscreen
687
+ class:icicle={shape === `icicle`}
688
+ >
689
+ {#if width && height}
690
+ <div class="header-controls">
691
+ {@render header_controls?.({ height, width, fullscreen })}
692
+ {#if show_controls}
693
+ <SunburstControls
694
+ toggle_props={{
695
+ ...controls_toggle_props,
696
+ // join the header flex row instead of absolute positioning (overrides
697
+ // ControlPane's default; flex layout can't overlap with the other buttons)
698
+ style: `position: static; ${controls_toggle_props?.style ?? ``}`,
699
+ }}
700
+ pane_props={controls_pane_props}
701
+ bind:show_controls
702
+ bind:controls_open
703
+ bind:shape
704
+ bind:value_mode
705
+ bind:max_depth
706
+ bind:inner_radius
707
+ bind:pad_angle
708
+ bind:min_fraction
709
+ bind:show_labels
710
+ bind:label_rotation
711
+ bind:label_text
712
+ bind:zoom_on_click
713
+ bind:show_breadcrumbs
714
+ {export_buttons}
715
+ on_export={export_chart}
716
+ >
717
+ {@render controls_extra?.({ zoom_root_id })}
718
+ </SunburstControls>
719
+ {/if}
720
+ {#if fullscreen_toggle}
721
+ <FullscreenToggle bind:fullscreen />
722
+ {/if}
723
+ </div>
724
+ {#if show_breadcrumbs && breadcrumb_arcs.length > 0}
725
+ <nav class="breadcrumbs" aria-label="zoom path">
726
+ {#each breadcrumb_arcs as crumb, crumb_idx (crumb.node_idx)}
727
+ {#if crumb_idx > 0}<span class="breadcrumb-sep" aria-hidden="true">›</span>{/if}
728
+ <button
729
+ type="button"
730
+ class="breadcrumb"
731
+ disabled={crumb_idx === breadcrumb_arcs.length - 1}
732
+ onclick={() => zoom_to(crumb)}
733
+ >
734
+ {crumb.depth === 0 ? `all` : crumb.label ?? crumb.id}
735
+ </button>
736
+ {/each}
737
+ </nav>
738
+ {/if}
739
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
740
+ <svg
741
+ bind:this={svg_element}
742
+ viewBox="0 0 {width} {height}"
743
+ role="application"
744
+ aria-label={rest[`aria-label`] ?? `${shape === `icicle` ? `Icicle` : `Sunburst`} chart`}
745
+ onmouseleave={() => set_arc_hover(null)}
746
+ >
747
+ <!-- Hover/click delegation sits on the chart group (not the arcs group) so
748
+ labels - which carry the same data-sunburst-node-idx and are selectable text -
749
+ forward interactions to their arc instead of swallowing them -->
750
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
751
+ <g
752
+ transform={chart_transform}
753
+ onmousemove={handle_arc_hover_event}
754
+ onmouseleave={() => set_arc_hover(null)}
755
+ onfocusin={handle_arc_hover_event}
756
+ onfocusout={() => set_arc_hover(null)}
757
+ onclick={handle_arc_click}
758
+ ondblclick={handle_dblclick}
759
+ onkeydown={handle_arc_keydown}
760
+ >
761
+ <!-- Arcs -->
762
+ <g class="arcs">
763
+ {#each visible_arcs as screen (screen.arc.node_idx)}
764
+ {#if arc_content}
765
+ {@render arc_content(screen)}
766
+ {:else}
767
+ {@const clickable = arc_clickable(screen.arc)}
768
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
769
+ <path
770
+ d={screen_path(screen)}
771
+ 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)
777
+ : undefined}
778
+ aria-label={clickable ? arc_info[screen.arc.node_idx].aria : undefined}
779
+ style:cursor={clickable ? `pointer` : `default`}
780
+ />
781
+ {/if}
782
+ {/each}
783
+ </g>
784
+
785
+ <!-- Arc labels: selectable text; data-sunburst-node-idx forwards hover/click
786
+ to the underlying arc via the chart-group delegation above -->
787
+ {#if show_labels}
788
+ <g class="arc-labels">
789
+ {#each visible_arcs as screen (screen.arc.node_idx)}
790
+ {@const lbl = label_attrs(screen)}
791
+ {#if lbl}
792
+ <text
793
+ class="arc-label"
794
+ data-sunburst-node-idx={screen.arc.node_idx}
795
+ 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`}
799
+ >
800
+ {lbl.text}
801
+ </text>
802
+ {/if}
803
+ {/each}
804
+ </g>
805
+ {/if}
806
+
807
+ {#if shape === `sunburst`}
808
+ <!-- Center: zoom-out button + current-root summary -->
809
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
810
+ <circle
811
+ bind:this={center_el}
812
+ class="center-circle"
813
+ r={hole_r}
814
+ role={zoomed ? `button` : undefined}
815
+ tabindex={zoomed ? 0 : undefined}
816
+ aria-label={zoomed ? `zoom out to ${zoom_out_label}` : undefined}
817
+ style:cursor={zoomed ? `pointer` : `default`}
818
+ style:pointer-events={zoomed ? `auto` : `none`}
819
+ onclick={zoom_out}
820
+ onkeydown={handle_center_keydown}
821
+ />
822
+ {#if center_content}
823
+ {@render center_content({ root: zoom_root, radius: hole_r, zoomed })}
824
+ {:else if hole_r >= 18 && zoom_root}
825
+ <!-- Selectable text overlaying the center circle; clicks forward to the
826
+ same zoom-out action as the circle (which also handles keyboard) -->
827
+ <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
828
+ <text
829
+ class="center-label"
830
+ style:cursor={zoomed ? `pointer` : `text`}
831
+ onclick={zoom_out}
832
+ >
833
+ {#if center_label}
834
+ <tspan x="0" dy={zoom_root.value ? `-0.3em` : `0.35em`}>
835
+ {center_label}
836
+ </tspan>
837
+ {/if}
838
+ <tspan x="0" dy={center_label ? `1.2em` : `0.35em`} class="center-value">
839
+ {format_value(zoom_root.value, value_format)}
840
+ </tspan>
841
+ </text>
842
+ {/if}
843
+ {/if}
844
+ </g>
845
+ </svg>
846
+ {/if}
847
+
848
+ {#if hover_info}
849
+ <PlotTooltip
850
+ x={hover_pos.x}
851
+ y={hover_pos.y}
852
+ offset={{ x: 10, y: 5 }}
853
+ constrain_to={{ width, height }}
854
+ fallback_size={{ width: 140, height: 44 }}
855
+ bg_color={hover_info.color}
856
+ >
857
+ {#if tooltip}
858
+ {@render tooltip(hover_info)}
859
+ {: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
865
+ ? `, ${format_value(hover_info.parent_fraction, `.1%`)} of parent`
866
+ : ``
867
+ })
868
+ {/if}
869
+ </PlotTooltip>
870
+ {/if}
871
+
872
+ {#if legend_visible}
873
+ {@const legend_left = legend_placement?.x ?? pad.l + 10}
874
+ {@const legend_top = legend_placement?.y ?? pad.t + 10}
875
+ <PlotLegend
876
+ bind:root_element={legend_element}
877
+ {...legend}
878
+ series_data={legend_data}
879
+ on_toggle={legend?.on_toggle ?? toggle_category}
880
+ on_item_hover={(item) =>
881
+ (hovered_idx = item != null && item.series_idx >= 0
882
+ ? depth1_arcs[item.series_idx]?.node_idx ?? null
883
+ : null)}
884
+ style={`position: absolute; left: ${legend_left}px; top: ${legend_top}px; pointer-events: auto; ${
885
+ legend?.style ?? ``
886
+ }`}
887
+ />
888
+ {/if}
889
+
890
+ {#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>
902
+ {/if}
903
+
904
+ {@render children?.({ height, width, fullscreen })}
905
+ </div>
906
+
907
+ <style>
908
+ .sunburst {
909
+ position: relative;
910
+ width: var(--sunburst-width, 100%);
911
+ height: var(--sunburst-height, auto);
912
+ min-height: var(--sunburst-min-height, 300px);
913
+ container-type: size;
914
+ z-index: var(--sunburst-z-index, auto);
915
+ /* flex-basis auto (not 1 = 0%) so an authored height wins over flex sizing in
916
+ column-flex parents while the chart still grows/shrinks to fill fixed layouts */
917
+ flex: var(--sunburst-flex, 1 1 auto);
918
+ display: var(--sunburst-display, flex);
919
+ flex-direction: column;
920
+ background: var(--sunburst-bg, var(--plot-bg));
921
+ border-radius: var(--sunburst-border-radius, var(--border-radius, 3pt));
922
+ }
923
+ .sunburst.fullscreen {
924
+ position: fixed;
925
+ top: 0;
926
+ left: 0;
927
+ width: 100vw !important;
928
+ height: 100vh !important;
929
+ z-index: var(--sunburst-fullscreen-z-index, var(--z-index-overlay-nav, 100000001));
930
+ margin: 0;
931
+ border-radius: 0;
932
+ background: var(--sunburst-fullscreen-bg, var(--sunburst-bg, var(--plot-bg)));
933
+ max-height: none !important;
934
+ overflow: hidden;
935
+ /* border-top (not padding-top): bind:clientHeight includes padding but excludes
936
+ borders - padding made the chart overflow + clip its bottom 2em (x-axis title) */
937
+ border-top: var(--plot-fullscreen-padding-top, 2em) solid
938
+ var(--sunburst-fullscreen-bg, var(--sunburst-bg, var(--plot-bg, transparent)));
939
+ box-sizing: border-box;
940
+ }
941
+ .header-controls {
942
+ position: absolute;
943
+ top: var(--ctrl-btn-top, 5pt);
944
+ right: var(--fullscreen-btn-right, 4px);
945
+ z-index: var(--fullscreen-btn-z-index, 10);
946
+ display: flex;
947
+ align-items: center;
948
+ gap: 8px;
949
+ }
950
+ .header-controls :global(.fullscreen-toggle) {
951
+ position: static;
952
+ opacity: 1;
953
+ }
954
+ .breadcrumb {
955
+ background: var(--sunburst-btn-bg, rgba(128, 128, 128, 0.15));
956
+ color: inherit;
957
+ border: none;
958
+ border-radius: 3pt;
959
+ padding: 1px 6px;
960
+ cursor: pointer;
961
+ font: inherit;
962
+ }
963
+ .breadcrumb:hover:not(:disabled) {
964
+ background: var(--sunburst-btn-hover-bg, rgba(128, 128, 128, 0.35));
965
+ }
966
+ .breadcrumbs {
967
+ position: absolute;
968
+ top: var(--sunburst-breadcrumbs-top, 5pt);
969
+ left: var(--sunburst-breadcrumbs-left, 8px);
970
+ z-index: 9;
971
+ display: flex;
972
+ align-items: center;
973
+ gap: 2px;
974
+ flex-wrap: wrap;
975
+ max-width: 75%;
976
+ font-size: var(--sunburst-breadcrumbs-font-size, 0.85em);
977
+ }
978
+ .breadcrumb:disabled {
979
+ cursor: default;
980
+ font-weight: bold;
981
+ background: transparent;
982
+ }
983
+ .breadcrumb-sep {
984
+ opacity: 0.6;
985
+ }
986
+ .sunburst :global(.pane-toggle),
987
+ .sunburst .header-controls {
988
+ opacity: 0;
989
+ transition: opacity 0.2s, background-color 0.2s;
990
+ }
991
+ .sunburst:hover :global(.pane-toggle),
992
+ .sunburst:hover .header-controls,
993
+ .sunburst :global(.pane-toggle:focus-visible),
994
+ .sunburst :global(.pane-toggle[aria-expanded='true']),
995
+ .sunburst .header-controls:has(:global([aria-expanded='true'])),
996
+ .sunburst .header-controls:focus-within {
997
+ opacity: 1;
998
+ }
999
+ svg {
1000
+ width: var(--sunburst-svg-width, 100%);
1001
+ height: var(--sunburst-svg-height, 100%);
1002
+ flex: var(--sunburst-svg-flex, 1);
1003
+ overflow: var(--sunburst-svg-overflow, visible);
1004
+ fill: var(--text-color);
1005
+ font-size: var(--sunburst-font-size, 11px);
1006
+ }
1007
+ .arcs path {
1008
+ /* stroke via CSS (not presentation attributes): var() substitution in SVG
1009
+ presentation attributes is not reliably supported across browsers */
1010
+ stroke: var(--sunburst-arc-stroke, var(--plot-bg, white));
1011
+ stroke-width: var(--sunburst-arc-stroke-width, 0.25);
1012
+ transition: fill-opacity 0.15s ease, transform 0.15s ease;
1013
+ /* hover 'pull': scaling about the chart center offsets the arc radially */
1014
+ transform-origin: 0 0;
1015
+ }
1016
+ .sunburst:not(.icicle) .arcs path:hover {
1017
+ transform: scale(var(--sunburst-hover-scale, 1.02));
1018
+ }
1019
+ .center-circle {
1020
+ fill: var(--sunburst-center-bg, transparent);
1021
+ }
1022
+ .arc-label {
1023
+ text-anchor: middle;
1024
+ dominant-baseline: central;
1025
+ /* selectable so labels can be copied; clicks/hover still reach the underlying
1026
+ arc via data-sunburst-node-idx + delegation on the chart group */
1027
+ -webkit-user-select: text;
1028
+ user-select: text;
1029
+ }
1030
+ .center-label {
1031
+ fill: var(--text-color);
1032
+ text-anchor: middle;
1033
+ font-weight: bold;
1034
+ -webkit-user-select: text;
1035
+ user-select: text;
1036
+ }
1037
+ .center-label .center-value {
1038
+ font-weight: normal;
1039
+ opacity: 0.8;
1040
+ }
1041
+ </style>