matterviz 0.3.7 → 0.4.0

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