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,1292 @@
1
+ <script
2
+ lang="ts"
3
+ generics="Metadata extends Record<string, unknown> = Record<string, unknown>"
4
+ >
5
+ import { format_value } from '../../labels'
6
+ import { FullscreenToggle, set_fullscreen_bg } from '../../layout'
7
+ import type { Vec2 } from '../../math'
8
+ import type {
9
+ BandwidthOption,
10
+ BasePlotProps,
11
+ BoxHandlerProps,
12
+ BoxPlotSeries,
13
+ LegendConfig,
14
+ LegendItem,
15
+ Orientation,
16
+ PanConfig,
17
+ PlotConfig,
18
+ RefLine,
19
+ RefLineEvent,
20
+ ScaleType,
21
+ UserContentProps,
22
+ ViolinKind,
23
+ ViolinSide,
24
+ WhiskerMode,
25
+ } from '..'
26
+ import {
27
+ BoxPlotControls,
28
+ compute_element_placement,
29
+ PlotAxis,
30
+ PlotLegend,
31
+ ReferenceLine,
32
+ } from '..'
33
+ import {
34
+ build_obstacles_norm,
35
+ clip_bar,
36
+ has_explicit_position,
37
+ measured_footprint,
38
+ place_decorations,
39
+ placed_coords,
40
+ } from '../core/auto-place'
41
+ import { compute_box_stats } from './box-plot'
42
+ import { gaussian_kde, type KdeResult } from './kde'
43
+ import { create_placed_tween } from '../core/placed-tween.svelte'
44
+ import { create_pan_zoom } from '../core/pan-zoom.svelte'
45
+ import { create_legend_visibility } from '../core/utils/series-visibility'
46
+ import {
47
+ axis_ranges_equal,
48
+ invert_rect_range,
49
+ resolve_axis_ranges,
50
+ } from '../core/interactions'
51
+ import {
52
+ calc_auto_padding,
53
+ filter_padding,
54
+ LABEL_GAP_DEFAULT,
55
+ y2_axis_label_x,
56
+ measure_max_tick_width,
57
+ } from '../core/layout'
58
+ import { LOG_EPS } from '../../math'
59
+ import type { IndexedRefLine } from '../core/reference-line'
60
+ import { group_ref_lines_by_z, index_ref_lines } from '../core/reference-line'
61
+ import {
62
+ create_scale,
63
+ generate_ticks,
64
+ get_nice_data_range,
65
+ get_tick_label,
66
+ } from '../core/scales'
67
+ import { DEFAULT_SERIES_COLORS } from '../core/types'
68
+ import { unique_id } from '../core/utils'
69
+ import { DEFAULTS } from '../../settings'
70
+ import type { Snippet } from 'svelte'
71
+ import { onDestroy, untrack } from 'svelte'
72
+ import type { HTMLAttributes } from 'svelte/elements'
73
+ import { SvelteMap } from 'svelte/reactivity'
74
+ import PlotTooltip from '../core/components/PlotTooltip.svelte'
75
+ import { violin_path } from '../core/svg'
76
+ import ZeroLines from '../core/components/ZeroLines.svelte'
77
+ import ZoomRect from '../core/components/ZoomRect.svelte'
78
+
79
+ // Box style props
80
+ interface BoxStyle {
81
+ color?: string
82
+ opacity?: number
83
+ stroke_width?: number
84
+ stroke_color?: string
85
+ border_radius?: number
86
+ }
87
+ interface WhiskerStyle {
88
+ width?: number
89
+ color?: string
90
+ cap_fraction?: number
91
+ }
92
+ interface BoxLineStyle {
93
+ width?: number
94
+ color?: string
95
+ }
96
+ interface OutlierStyle {
97
+ radius?: number
98
+ opacity?: number
99
+ stroke_width?: number
100
+ }
101
+ interface ViolinStyle {
102
+ opacity?: number
103
+ stroke_width?: number
104
+ }
105
+
106
+ // Hover state carries the box payload plus the pixel anchor for the tooltip
107
+ type BoxHover = BoxHandlerProps<Metadata> & { cx: number; cy: number }
108
+
109
+ let {
110
+ series = $bindable([]),
111
+ orientation = $bindable(`vertical`),
112
+ x_axis = $bindable({}),
113
+ x2_axis: x2_axis_prop = $bindable({}),
114
+ y_axis = $bindable({}),
115
+ y2_axis: y2_axis_prop = $bindable({}),
116
+ display = $bindable(DEFAULTS.box.display),
117
+ range_padding = 0.05,
118
+ padding = { t: 20, b: 60, l: 60, r: 20 },
119
+ legend = {},
120
+ show_legend,
121
+ box = {},
122
+ whisker = {},
123
+ median_style = {},
124
+ outlier_style = {},
125
+ whisker_mode = $bindable(DEFAULTS.box.whisker_mode),
126
+ whisker_range = 1.5,
127
+ whisker_percentiles = [5, 95],
128
+ show_outliers = $bindable(DEFAULTS.box.show_outliers),
129
+ show_mean = $bindable(DEFAULTS.box.show_mean),
130
+ show_value_labels = false,
131
+ value_label_stat = `median`,
132
+ value_label_format = `.3~s`,
133
+ kind = $bindable(DEFAULTS.box.kind),
134
+ side = $bindable(DEFAULTS.box.side),
135
+ bandwidth = DEFAULTS.box.bandwidth,
136
+ violin_width = DEFAULTS.box.violin_width,
137
+ violin_style = {},
138
+ kde_points = 100,
139
+ kde_cut = 2,
140
+ kde_max_samples = 5000,
141
+ kde_clip = undefined,
142
+ tooltip,
143
+ user_content,
144
+ hovered = $bindable(false),
145
+ change = () => {},
146
+ on_box_click,
147
+ on_box_hover,
148
+ ref_lines = $bindable([]),
149
+ on_ref_line_click,
150
+ on_ref_line_hover,
151
+ show_controls = $bindable(true),
152
+ controls_open = $bindable(false),
153
+ controls_toggle_props,
154
+ controls_pane_props,
155
+ fullscreen = $bindable(false),
156
+ fullscreen_toggle = true,
157
+ children,
158
+ header_controls,
159
+ controls_extra,
160
+ pan = {},
161
+ ...rest
162
+ }: HTMLAttributes<HTMLDivElement> & BasePlotProps & PlotConfig & {
163
+ series?: BoxPlotSeries<Metadata>[]
164
+ orientation?: Orientation
165
+ legend?: LegendConfig | null
166
+ show_legend?: boolean
167
+ box?: BoxStyle
168
+ whisker?: WhiskerStyle
169
+ median_style?: BoxLineStyle
170
+ outlier_style?: OutlierStyle
171
+ whisker_mode?: WhiskerMode
172
+ whisker_range?: number
173
+ whisker_percentiles?: Vec2
174
+ show_outliers?: boolean
175
+ show_mean?: boolean
176
+ show_value_labels?: boolean
177
+ value_label_stat?: `median` | `mean`
178
+ value_label_format?: string
179
+ kind?: ViolinKind
180
+ side?: ViolinSide
181
+ bandwidth?: BandwidthOption
182
+ violin_width?: number
183
+ violin_style?: ViolinStyle
184
+ kde_points?: number
185
+ kde_cut?: number
186
+ kde_max_samples?: number
187
+ kde_clip?: [number | null, number | null]
188
+ tooltip?: Snippet<[BoxHandlerProps<Metadata>]>
189
+ user_content?: Snippet<[UserContentProps]>
190
+ header_controls?: Snippet<[{ height: number; width: number; fullscreen: boolean }]>
191
+ controls_extra?: Snippet<[{ orientation: Orientation } & Required<PlotConfig>]>
192
+ change?: (data: BoxHandlerProps<Metadata> | null) => void
193
+ on_box_click?: (
194
+ data: BoxHandlerProps<Metadata> & { event: MouseEvent | KeyboardEvent },
195
+ ) => void
196
+ on_box_hover?: (
197
+ data:
198
+ | (BoxHandlerProps<Metadata> & { event: MouseEvent | FocusEvent | KeyboardEvent })
199
+ | null,
200
+ ) => void
201
+ ref_lines?: RefLine[]
202
+ on_ref_line_click?: (event: RefLineEvent) => void
203
+ on_ref_line_hover?: (event: RefLineEvent | null) => void
204
+ pan?: PanConfig
205
+ } = $props()
206
+
207
+ let box_state = $derived({ ...DEFAULTS.box.box, ...box })
208
+ let whisker_state = $derived({ ...DEFAULTS.box.whisker, ...whisker })
209
+ let median_state = $derived({ ...DEFAULTS.box.median, ...median_style })
210
+ let outlier_state = $derived({ ...DEFAULTS.box.outlier, ...outlier_style })
211
+ let violin_state = $derived({ ...DEFAULTS.box.violin, ...violin_style })
212
+
213
+ // Merge secondary-axis defaults as deriveds instead of assigning back into the
214
+ // $bindable props (which would push library defaults into the parent's bound state)
215
+ let y2_axis = $derived(
216
+ {
217
+ format: ``,
218
+ scale_type: `linear`,
219
+ ticks: 5,
220
+ range: [null, null],
221
+ ...y2_axis_prop,
222
+ } as typeof y2_axis_prop,
223
+ )
224
+ let x2_axis = $derived(
225
+ {
226
+ format: ``,
227
+ scale_type: `linear`,
228
+ ticks: 5,
229
+ range: [null, null],
230
+ ...x2_axis_prop,
231
+ } as typeof x2_axis_prop,
232
+ )
233
+
234
+ let [width, height] = $state([0, 0])
235
+ let wrapper: HTMLDivElement | undefined = $state()
236
+ let svg_element: SVGElement | null = $state(null)
237
+ const clip_path_id = unique_id(`box-clip`) // stable, collision-resistant (see unique_id)
238
+
239
+ let hovered_ref_line_idx = $state<number | null>(null)
240
+
241
+ let ref_lines_by_z = $derived(group_ref_lines_by_z(index_ref_lines(ref_lines)))
242
+
243
+ // === Box stats + slot model ===
244
+ const box_color = (idx: number): string =>
245
+ series[idx]?.color ?? DEFAULT_SERIES_COLORS[idx % DEFAULT_SERIES_COLORS.length]
246
+
247
+ // Which glyph(s) a series draws (per-series kind overrides the component default)
248
+ const effective_kind = (srs: BoxPlotSeries<Metadata>): ViolinKind => srs.kind ?? kind
249
+ const draws_violin = (srs: BoxPlotSeries<Metadata>): boolean => effective_kind(srs) !== `box`
250
+ const draws_box = (srs: BoxPlotSeries<Metadata>): boolean => effective_kind(srs) !== `violin`
251
+
252
+ let box_stats = $derived(
253
+ series.map((srs) =>
254
+ compute_box_stats(srs.y ?? [], {
255
+ whisker_mode: srs.whisker_mode ?? whisker_mode,
256
+ whisker_range: srs.whisker_range ?? whisker_range,
257
+ whisker_percentiles: srs.whisker_percentiles ?? whisker_percentiles,
258
+ collect_outliers: show_outliers && draws_box(srs) && (srs.visible ?? true),
259
+ })
260
+ ),
261
+ )
262
+
263
+ // Slots position boxes/violins along the category axis. Series sharing a `category` occupy
264
+ // one slot (split/grouped violins). Without `category`, each series gets its own slot —
265
+ // byte-identical to the original one-box-per-series behavior. Override tick labels via
266
+ // x_axis.ticks (a Record).
267
+ let use_categories = $derived(series.some((srs) => srs.category != null))
268
+ const slot_key = (srs: BoxPlotSeries<Metadata>, idx: number): string =>
269
+ srs.category ?? `${idx}`
270
+ let slot_list = $derived(
271
+ use_categories
272
+ ? [...new Set(series.map(slot_key))]
273
+ : series.map((srs, idx) => srs.label ?? `${idx}`),
274
+ )
275
+ let slot_lookup = $derived(new Map(slot_list.map((slot, idx) => [slot, idx])))
276
+ const slot_of = (idx: number): number =>
277
+ use_categories ? (slot_lookup.get(slot_key(series[idx], idx)) ?? idx) : idx
278
+ let slot_indices = $derived(slot_list.map((_, idx) => idx))
279
+ // A slot's tick label is colored only when a single series occupies it. Precompute
280
+ // slot -> color in one pass so the PlotAxis tick_color callback stays O(1) per tick.
281
+ let slot_colors = $derived.by(() => {
282
+ const by_slot = new SvelteMap<number, number[]>()
283
+ series.forEach((_srs, idx) => {
284
+ const slot = slot_of(idx)
285
+ const idxs = by_slot.get(slot)
286
+ if (idxs) idxs.push(idx)
287
+ else by_slot.set(slot, [idx])
288
+ })
289
+ const colors = new SvelteMap<number, string | undefined>()
290
+ for (const [slot, idxs] of by_slot) {
291
+ colors.set(slot, idxs.length === 1 ? box_color(idxs[0]) : undefined)
292
+ }
293
+ return colors
294
+ })
295
+ let cat_axis = $derived(orientation === `horizontal` ? `y` : `x`)
296
+
297
+ type Box = {
298
+ series: BoxPlotSeries<Metadata>
299
+ idx: number
300
+ slot: number
301
+ stats: (typeof box_stats)[number]
302
+ }
303
+ let visible_boxes = $derived<Box[]>(
304
+ series
305
+ .map((srs, idx) => ({ series: srs, idx, slot: slot_of(idx), stats: box_stats[idx] }))
306
+ .filter((box_item) => box_item.series.visible ?? true),
307
+ )
308
+
309
+ // KDE per visible violin series, keyed by series index (bandwidth from the full sample)
310
+ let violin_kdes = $derived.by(() => {
311
+ const map = new SvelteMap<number, KdeResult>()
312
+ const [val_axis, val_axis2] = orientation === `vertical`
313
+ ? [y_axis, y2_axis]
314
+ : [x_axis, x2_axis]
315
+ for (const box_item of visible_boxes) {
316
+ if (!draws_violin(box_item.series)) continue
317
+ const samples = box_item.series.y ?? []
318
+ let clip = box_item.series.clip ?? kde_clip
319
+ // On a log value axis the KDE grid tail (data_min - cut*bandwidth) is usually <= 0 →
320
+ // NaN pixels + LOG_EPS range pollution. Clamp the grid to the smallest positive sample.
321
+ if ((is_secondary(box_item.series) ? val_axis2 : val_axis).scale_type === `log`) {
322
+ const min_pos = samples.reduce((min, val) => (val > 0 && val < min ? val : min), Infinity)
323
+ // Guard: no positive samples → min_pos is Infinity; leave clip unchanged so the KDE
324
+ // never receives a non-finite lower bound
325
+ if (Number.isFinite(min_pos)) {
326
+ clip = [Math.max(clip?.[0] ?? -Infinity, min_pos), clip?.[1] ?? null]
327
+ }
328
+ }
329
+ map.set(
330
+ box_item.idx,
331
+ gaussian_kde(samples, {
332
+ bandwidth: box_item.series.bandwidth ?? bandwidth,
333
+ n_points: kde_points,
334
+ cut: kde_cut,
335
+ clip,
336
+ max_samples: kde_max_samples,
337
+ }),
338
+ )
339
+ }
340
+ return map
341
+ })
342
+
343
+ // The horizontal category pixel axis is inverted, so flip the half-violin side to keep
344
+ // `positive` meaning "above the center line" (vertical/`both` pass through unchanged)
345
+ const to_screen_side = (eff_side: ViolinSide, vertical: boolean): ViolinSide =>
346
+ vertical || eff_side === `both` ? eff_side : eff_side === `positive` ? `negative` : `positive`
347
+
348
+ // Peak density per violin, computed once on data change (avoids spreading kde.density into
349
+ // Math.max — unsafe for large kde_points — and re-deriving it on every render/hover).
350
+ let violin_max_density = $derived.by(() => {
351
+ const map = new SvelteMap<number, number>()
352
+ for (const [idx, kde] of violin_kdes) {
353
+ let max = 0
354
+ for (const den of kde.density) if (den > max) max = den
355
+ map.set(idx, max)
356
+ }
357
+ return map
358
+ })
359
+
360
+ // Which boxes live on the secondary value axis (y2 for vertical, x2 for horizontal)
361
+ const is_secondary = (srs: BoxPlotSeries<Metadata>): boolean =>
362
+ orientation === `vertical` ? srs.y_axis === `y2` : srs.x_axis === `x2`
363
+ let secondary_boxes = $derived(visible_boxes.filter((box_item) => is_secondary(box_item.series)))
364
+ let has_secondary = $derived(secondary_boxes.length > 0)
365
+
366
+ // Collect value-axis points (whiskers, quartiles, outliers, KDE tails) for auto-range
367
+ const value_points = (boxes: Box[]): { x: number; y: number }[] =>
368
+ boxes.flatMap((box_item) => {
369
+ const { whisker_low, whisker_high, q1, q3, median, mean, outliers } = box_item.stats
370
+ const vals = [whisker_low, whisker_high, q1, q3, median]
371
+ // keep the drawn mean line in range even when hidden outliers drag it past the whiskers
372
+ if (show_mean) vals.push(mean)
373
+ // outliers are sorted ascending; auto-range only needs their extremes (avoids
374
+ // spreading a potentially huge array as call args)
375
+ if (show_outliers && outliers.length > 0) {
376
+ vals.push(outliers[0], outliers[outliers.length - 1])
377
+ }
378
+ const kde = violin_kdes.get(box_item.idx)
379
+ if (kde && kde.grid.length > 0) vals.push(kde.grid[0], kde.grid[kde.grid.length - 1])
380
+ return vals.filter(Number.isFinite).map((val) => ({ x: 0, y: val }))
381
+ })
382
+
383
+ let auto_ranges = $derived.by(() => {
384
+ const cat_count = slot_list.length
385
+ const cat_range: Vec2 = cat_count > 0 ? [-0.5, cat_count - 0.5] : [0, 1]
386
+
387
+ const primary_boxes = visible_boxes.filter((box_item) => !is_secondary(box_item.series))
388
+ const calc_value_range = (
389
+ boxes: Box[],
390
+ limit: [number | null, number | null],
391
+ scale_type: ScaleType,
392
+ ): Vec2 => {
393
+ const pts = value_points(boxes)
394
+ if (pts.length === 0) return [0, 1]
395
+ return get_nice_data_range(pts, (pt) => pt.y, limit, scale_type, range_padding, false)
396
+ }
397
+ const vertical = orientation === `vertical`
398
+ const value_primary = calc_value_range(
399
+ primary_boxes,
400
+ (vertical ? y_axis.range : x_axis.range) ?? [null, null],
401
+ (vertical ? y_axis.scale_type : x_axis.scale_type) ?? `linear`,
402
+ )
403
+ const value_secondary = calc_value_range(
404
+ secondary_boxes,
405
+ (vertical ? y2_axis.range : x2_axis.range) ?? [null, null],
406
+ (vertical ? y2_axis.scale_type : x2_axis.scale_type) ?? `linear`,
407
+ )
408
+
409
+ return vertical
410
+ ? ({ x: cat_range, x2: [0, 1] as Vec2, y: value_primary, y2: value_secondary })
411
+ : ({ x: value_primary, x2: value_secondary, y: cat_range, y2: [0, 1] as Vec2 })
412
+ })
413
+
414
+ let ranges = $state<{
415
+ initial: { x: Vec2; x2: Vec2; y: Vec2; y2: Vec2 }
416
+ current: { x: Vec2; x2: Vec2; y: Vec2; y2: Vec2 }
417
+ }>({
418
+ initial: { x: [0, 1], x2: [0, 1], y: [0, 1], y2: [0, 1] },
419
+ current: { x: [0, 1], x2: [0, 1], y: [0, 1], y2: [0, 1] },
420
+ })
421
+
422
+ $effect(() => { // sync ranges from axis.range overrides / auto ranges
423
+ // resolve_axis_ranges returns null for transient non-finite bounds (skip: writing
424
+ // NaN breaks scales and, since NaN !== NaN, loops the effect)
425
+ const next = resolve_axis_ranges({ x: x_axis, x2: x2_axis, y: y_axis, y2: y2_axis }, auto_ranges)
426
+ if (!next) return
427
+ // untrack the read of `ranges` so the assignment can't re-trigger this effect
428
+ // (reading + writing the same state otherwise causes effect_update_depth_exceeded).
429
+ const init = untrack(() => ranges.initial)
430
+ if (!axis_ranges_equal(init, next)) {
431
+ ranges = { initial: { ...next }, current: { ...next } }
432
+ }
433
+ })
434
+
435
+ const default_padding = { t: 20, b: 60, l: 60, r: 20 }
436
+ let base_pad = $derived(filter_padding(padding, default_padding))
437
+
438
+ $effect(() => { // dynamic padding from tick label widths
439
+ const new_pad = width && height && ticks.y.length > 0
440
+ ? calc_auto_padding({
441
+ padding,
442
+ default_padding,
443
+ x2_axis: { ...x2_axis, tick_values: ticks.x2 },
444
+ y_axis: { ...y_axis, tick_values: ticks.y },
445
+ y2_axis: { ...y2_axis, tick_values: ticks.y2 },
446
+ })
447
+ : filter_padding(padding, default_padding)
448
+ if (
449
+ width && height && orientation === `vertical` && has_secondary && ticks.y2.length > 0
450
+ ) {
451
+ const inside = y2_axis.tick?.label?.inside ?? false
452
+ const tick_shift = inside ? 0 : (y2_axis.tick?.label?.shift?.x ?? 0) + 8
453
+ const tick_width_contribution = inside ? 0 : tick_label_widths.y2_max
454
+ const label_space = y2_axis.label ? 20 : 0
455
+ new_pad.r = Math.max(new_pad.r, tick_shift + tick_width_contribution + 30 + label_space)
456
+ }
457
+ if (base_pad.t !== new_pad.t || base_pad.b !== new_pad.b ||
458
+ base_pad.l !== new_pad.l || base_pad.r !== new_pad.r) base_pad = new_pad
459
+ })
460
+
461
+ let legend_element = $state<HTMLDivElement | undefined>()
462
+ const legend_footprint = $derived(measured_footprint(legend_element, { width: 120, height: 60 }))
463
+ const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
464
+
465
+ // Obstacle field in normalized [0,1] coords: each box modeled as a whisker-spanning segment
466
+ const obstacles_norm = $derived.by(() => {
467
+ if (!width || !height || visible_boxes.length === 0) return []
468
+ const base_w = width - base_pad.l - base_pad.r
469
+ const base_h = height - base_pad.t - base_pad.b
470
+ if (base_w <= 0 || base_h <= 0) return []
471
+ const vertical = orientation === `vertical`
472
+ const segs: { points: { x: number; y: number }[]; draws_line: boolean }[] = []
473
+ for (const box_item of visible_boxes) {
474
+ const { whisker_low, whisker_high, median } = box_item.stats
475
+ if (!Number.isFinite(median)) continue
476
+ const secondary = is_secondary(box_item.series)
477
+ const cat_rng = vertical ? ranges.current.x : ranges.current.y
478
+ const val_rng = vertical
479
+ ? (secondary ? ranges.current.y2 : ranges.current.y)
480
+ : (secondary ? ranges.current.x2 : ranges.current.x)
481
+ const cat_span = cat_rng[1] - cat_rng[0]
482
+ const val_span = val_rng[1] - val_rng[0]
483
+ if (cat_span === 0 || val_span === 0) continue
484
+ const cross = (box_item.slot - cat_rng[0]) / cat_span
485
+ const lo = (whisker_low - val_rng[0]) / val_span
486
+ const hi = (whisker_high - val_rng[0]) / val_span
487
+ const seg = vertical
488
+ ? clip_bar(true, cross, 1 - hi, 1 - lo)
489
+ : clip_bar(false, 1 - cross, lo, hi)
490
+ if (seg) segs.push(seg)
491
+ }
492
+ return build_obstacles_norm(segs, base_w, base_h)
493
+ })
494
+
495
+ const should_show_legend = $derived(show_legend ?? false)
496
+ const decor = $derived.by(() =>
497
+ place_decorations({
498
+ base_pad,
499
+ width,
500
+ height,
501
+ obstacles_norm,
502
+ legend: legend != null && should_show_legend && legend_element != null &&
503
+ !legend_has_explicit_pos
504
+ ? { footprint: legend_footprint, clearance: legend?.axis_clearance }
505
+ : null,
506
+ })
507
+ )
508
+ const pad = $derived(decor.pad)
509
+ const legend_auto_outside = $derived(decor.legend_outside)
510
+ const legend_outside_x = $derived(decor.legend_pos.x)
511
+ const legend_outside_y = $derived(decor.legend_pos.y)
512
+ const chart_width = $derived(Math.max(1, width - pad.l - pad.r))
513
+ const chart_height = $derived(Math.max(1, height - pad.t - pad.b))
514
+
515
+ let scales = $derived({
516
+ x: create_scale(x_axis.scale_type ?? `linear`, ranges.current.x, [pad.l, width - pad.r]),
517
+ x2: create_scale(x2_axis.scale_type ?? `linear`, ranges.current.x2, [pad.l, width - pad.r]),
518
+ y: create_scale(y_axis.scale_type ?? `linear`, ranges.current.y, [height - pad.b, pad.t]),
519
+ y2: create_scale(y2_axis.scale_type ?? `linear`, ranges.current.y2, [height - pad.b, pad.t]),
520
+ })
521
+
522
+ // Value scale for a box (vertical -> y/y2, horizontal -> x/x2), made log-safe: on a
523
+ // log value axis, stats at values <= 0 (whisker_low is often exactly 0; negative
524
+ // outliers) have no finite pixel. Clamp to LOG_EPS so whiskers/boxes/labels draw
525
+ // toward the plot edge (the clip group crops the overshoot) instead of NaN coords.
526
+ const box_val_scale = (srs: BoxPlotSeries<Metadata>): (val: number) => number => {
527
+ const vertical = orientation === `vertical`
528
+ const secondary = is_secondary(srs)
529
+ const scale = vertical
530
+ ? (secondary ? scales.y2 : scales.y)
531
+ : (secondary ? scales.x2 : scales.x)
532
+ const axis = vertical ? (secondary ? y2_axis : y_axis) : (secondary ? x2_axis : x_axis)
533
+ return axis.scale_type === `log` ? (val) => scale(Math.max(val, LOG_EPS)) : scale
534
+ }
535
+
536
+ // Categorical tick labels (slot index -> category name) unless user provides a label mapping
537
+ let effective_cat_ticks = $derived.by(() => {
538
+ if (slot_list.length === 0) return undefined
539
+ const user_ticks = cat_axis === `x` ? x_axis.ticks : y_axis.ticks
540
+ if (user_ticks != null && typeof user_ticks === `object` && !Array.isArray(user_ticks)) {
541
+ return user_ticks
542
+ }
543
+ return Object.fromEntries(slot_list.map((cat, idx) => [idx, cat]))
544
+ })
545
+
546
+ let ticks = $derived({
547
+ x: width && height
548
+ ? (cat_axis === `x` ? slot_indices : generate_ticks(
549
+ ranges.current.x,
550
+ x_axis.scale_type ?? `linear`,
551
+ x_axis.ticks,
552
+ scales.x,
553
+ { default_count: 8 },
554
+ ))
555
+ : [],
556
+ y: width && height
557
+ ? (cat_axis === `y` ? slot_indices : generate_ticks(
558
+ ranges.current.y,
559
+ y_axis.scale_type ?? `linear`,
560
+ y_axis.ticks,
561
+ scales.y,
562
+ { default_count: 6 },
563
+ ))
564
+ : [],
565
+ y2: width && height && has_secondary && orientation === `vertical`
566
+ ? generate_ticks(ranges.current.y2, y2_axis.scale_type ?? `linear`, y2_axis.ticks, scales.y2, {
567
+ default_count: 6,
568
+ })
569
+ : [],
570
+ x2: width && height && has_secondary && orientation === `horizontal`
571
+ ? generate_ticks(ranges.current.x2, x2_axis.scale_type ?? `linear`, x2_axis.ticks, scales.x2, {
572
+ default_count: 8,
573
+ })
574
+ : [],
575
+ })
576
+
577
+ let tick_label_widths = $derived({
578
+ y_max: measure_max_tick_width(ticks.y, y_axis.format ?? ``),
579
+ y2_max: measure_max_tick_width(ticks.y2, y2_axis.format ?? ``),
580
+ x2_max: measure_max_tick_width(ticks.x2, x2_axis.format ?? ``),
581
+ })
582
+
583
+ // Shared pan/zoom/touch/drag-rect interaction controller
584
+ const pan_zoom = create_pan_zoom({
585
+ ranges: () => ranges.current,
586
+ scale_type: (axis) => ({ x: x_axis, x2: x2_axis, y: y_axis, y2: y2_axis })[axis].scale_type,
587
+ plot_dims: () => ({ width: chart_width, height: chart_height }),
588
+ pan: () => pan,
589
+ set_range: (axis, range) => (ranges.current[axis] = range),
590
+ svg: () => svg_element,
591
+ on_rect_zoom: (start, current) => {
592
+ const next_x = invert_rect_range(scales.x, start.x, current.x)
593
+ if (!next_x) return
594
+ x_axis = { ...x_axis, range: next_x }
595
+ // the secondary value axis is x2 only in horizontal mode, y2 only in vertical
596
+ // (is_secondary keys off orientation); writing the off-orientation axis would
597
+ // store a phantom range from its [0, 1] sentinel scale into the bound prop
598
+ const next_x2 = has_secondary && orientation === `horizontal`
599
+ ? invert_rect_range(scales.x2, start.x, current.x)
600
+ : null
601
+ if (next_x2) x2_axis_prop = { ...x2_axis_prop, range: next_x2 }
602
+ const next_y = invert_rect_range(scales.y, start.y, current.y)
603
+ if (next_y) y_axis = { ...y_axis, range: next_y }
604
+ const next_y2 = has_secondary && orientation === `vertical`
605
+ ? invert_rect_range(scales.y2, start.y, current.y)
606
+ : null
607
+ if (next_y2) y2_axis_prop = { ...y2_axis_prop, range: next_y2 }
608
+ },
609
+ on_reset: () => {
610
+ ranges.current = {
611
+ x: [...ranges.initial.x] as Vec2,
612
+ x2: [...ranges.initial.x2] as Vec2,
613
+ y: [...ranges.initial.y] as Vec2,
614
+ y2: [...ranges.initial.y2] as Vec2,
615
+ }
616
+ x_axis = { ...x_axis, range: [null, null] }
617
+ x2_axis_prop = { ...x2_axis_prop, range: [null, null] }
618
+ y_axis = { ...y_axis, range: [null, null] }
619
+ y2_axis_prop = { ...y2_axis_prop, range: [null, null] }
620
+ },
621
+ })
622
+ onDestroy(() => pan_zoom.destroy())
623
+
624
+ // === Legend ===
625
+ let legend_data = $derived<LegendItem[]>(
626
+ series.map((srs, idx) => ({
627
+ series_idx: idx,
628
+ label: srs.label ?? `Box ${idx + 1}`,
629
+ visible: srs.visible ?? true,
630
+ legend_group: srs.legend_group,
631
+ display_style: { symbol_type: `Square` as const, symbol_color: box_color(idx) },
632
+ })),
633
+ )
634
+
635
+ const legend_vis = create_legend_visibility(() => series, (next) => (series = next))
636
+
637
+ let box_points_for_placement = $derived.by(() => {
638
+ if (!width || !height || visible_boxes.length === 0) return []
639
+ const vertical = orientation === `vertical`
640
+ return visible_boxes
641
+ .map((box_item) => {
642
+ const val_scale = box_val_scale(box_item.series)
643
+ const cat_scale = vertical ? scales.x : scales.y
644
+ const cc = cat_scale(box_item.slot)
645
+ const vc = val_scale(box_item.stats.median)
646
+ return vertical ? { x: cc, y: vc } : { x: vc, y: cc }
647
+ })
648
+ .filter(({ x, y }) => isFinite(x) && isFinite(y))
649
+ })
650
+
651
+ let hovered_legend_series_idx = $state<number | null>(null)
652
+
653
+ let legend_placement = $derived.by(() => {
654
+ if (!should_show_legend || !width || !height) return null
655
+ return compute_element_placement({
656
+ plot_bounds: { x: pad.l, y: pad.t, width: chart_width, height: chart_height },
657
+ element: legend_element,
658
+ element_size: { width: 120, height: 60 },
659
+ axis_clearance: legend?.axis_clearance,
660
+ exclude_rects: [],
661
+ points: box_points_for_placement,
662
+ })
663
+ })
664
+
665
+ // Tweened legend coordinates with shared placement stability gating
666
+ const legend_tween = create_placed_tween({
667
+ placement: () => legend_placement,
668
+ dims: () => ({ width, height }),
669
+ responsive: () => legend?.responsive ?? false,
670
+ element: () => legend_element,
671
+ tween: () => legend?.tween,
672
+ })
673
+
674
+ // === Tooltip / hover ===
675
+ let hover_info = $state<BoxHover | null>(null)
676
+
677
+ function get_box_data(box_item: Box, color: string): BoxHover {
678
+ const vertical = orientation === `vertical`
679
+ const val_scale = box_val_scale(box_item.series)
680
+ const cat_scale = vertical ? scales.x : scales.y
681
+ const cc = cat_scale(box_item.slot)
682
+ const v_hi = val_scale(box_item.stats.whisker_high)
683
+ const v_lo = val_scale(box_item.stats.whisker_low)
684
+ const [cx, cy] = vertical ? [cc, Math.min(v_hi, v_lo)] : [Math.max(v_hi, v_lo), cc]
685
+ const active_y_axis = (vertical ? (box_item.series.y_axis ?? `y1`) : `y1`) as `y1` | `y2`
686
+ const active_x_axis = (vertical ? `x1` : (box_item.series.x_axis ?? `x1`)) as `x1` | `x2`
687
+ return {
688
+ x: vertical ? box_item.slot : box_item.stats.median,
689
+ y: vertical ? box_item.stats.median : box_item.slot,
690
+ stats: box_item.stats,
691
+ color,
692
+ label: box_item.series.label ?? null,
693
+ category_label: slot_list[box_item.slot],
694
+ metadata: box_item.series.metadata,
695
+ series_idx: box_item.idx,
696
+ box_idx: box_item.idx,
697
+ active_x_axis,
698
+ active_y_axis,
699
+ x_axis: active_x_axis === `x2` ? x2_axis : x_axis,
700
+ x2_axis,
701
+ y_axis: active_y_axis === `y2` ? y2_axis : y_axis,
702
+ y2_axis,
703
+ cx,
704
+ cy,
705
+ }
706
+ }
707
+
708
+ const handle_box_hover = (box_item: Box, color: string) => (event: MouseEvent) => {
709
+ hovered = true
710
+ const data = get_box_data(box_item, color)
711
+ // Anchor the tooltip at the cursor (cx/cy default to the box center) so it follows the
712
+ // mouse — boxes/violins are wide, and a center anchor lands far from the pointer.
713
+ const rect = svg_element?.getBoundingClientRect()
714
+ if (rect) {
715
+ data.cx = event.clientX - rect.left
716
+ data.cy = event.clientY - rect.top
717
+ }
718
+ hover_info = data
719
+ change(hover_info)
720
+ on_box_hover?.({ ...hover_info, event })
721
+ }
722
+
723
+ // Set theme-aware background when entering fullscreen
724
+ $effect(() => set_fullscreen_bg(wrapper, fullscreen, `--boxplot-fullscreen-bg`))
725
+
726
+ // Value label helper
727
+ const value_label_for = (stats: Box[`stats`]): string =>
728
+ format_value(value_label_stat === `mean` ? stats.mean : stats.median, value_label_format)
729
+ </script>
730
+
731
+ {#snippet seg(
732
+ p1: Vec2,
733
+ p2: Vec2,
734
+ stroke: string,
735
+ sw: number,
736
+ dash?: string,
737
+ )}
738
+ <line
739
+ x1={p1[0]}
740
+ y1={p1[1]}
741
+ x2={p2[0]}
742
+ y2={p2[1]}
743
+ {stroke}
744
+ stroke-width={sw}
745
+ stroke-dasharray={dash}
746
+ />
747
+ {/snippet}
748
+
749
+ {#snippet ref_lines_layer(lines: IndexedRefLine[])}
750
+ {#each lines as line (line.id ?? line.idx)}
751
+ <ReferenceLine
752
+ ref_line={line}
753
+ line_idx={line.idx}
754
+ x_min={line.x_axis === `x2` ? ranges.current.x2[0] : ranges.current.x[0]}
755
+ x_max={line.x_axis === `x2` ? ranges.current.x2[1] : ranges.current.x[1]}
756
+ y_min={line.y_axis === `y2` ? ranges.current.y2[0] : ranges.current.y[0]}
757
+ y_max={line.y_axis === `y2` ? ranges.current.y2[1] : ranges.current.y[1]}
758
+ x_scale={scales.x}
759
+ x2_scale={scales.x2}
760
+ y_scale={scales.y}
761
+ y2_scale={scales.y2}
762
+ {clip_path_id}
763
+ hovered_line_idx={hovered_ref_line_idx}
764
+ on_click={(event: RefLineEvent) => {
765
+ line.on_click?.(event)
766
+ on_ref_line_click?.(event)
767
+ }}
768
+ on_hover={(event: RefLineEvent | null) => {
769
+ hovered_ref_line_idx = event?.line_idx ?? null
770
+ line.on_hover?.(event)
771
+ on_ref_line_hover?.(event)
772
+ }}
773
+ />
774
+ {/each}
775
+ {/snippet}
776
+
777
+ <svelte:window
778
+ onkeydown={(evt) => {
779
+ if (evt.key === `Escape` && fullscreen) {
780
+ evt.preventDefault()
781
+ fullscreen = false
782
+ }
783
+ pan_zoom.on_window_key_down(evt)
784
+ }}
785
+ onkeyup={pan_zoom.on_window_key_up}
786
+ />
787
+
788
+ <div
789
+ bind:this={wrapper}
790
+ bind:clientWidth={width}
791
+ bind:clientHeight={height}
792
+ {...rest}
793
+ class="box-plot {rest.class ?? ``}"
794
+ class:fullscreen
795
+ >
796
+ {#if width && height}
797
+ <div class="header-controls">
798
+ {@render header_controls?.({ height, width, fullscreen })}
799
+ {#if fullscreen_toggle}
800
+ <FullscreenToggle bind:fullscreen />
801
+ {/if}
802
+ </div>
803
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
804
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
805
+ <svg
806
+ bind:this={svg_element}
807
+ role="application"
808
+ aria-label={rest[`aria-label`] ??
809
+ ([x_axis.label, y_axis.label].filter(Boolean).join(` vs `) || `Box plot`)}
810
+ tabindex="0"
811
+ onfocusin={() => pan_zoom.set_focused(true)}
812
+ onfocusout={() => pan_zoom.set_focused(false)}
813
+ onmousedown={pan_zoom.on_mouse_down}
814
+ ondblclick={pan_zoom.reset_view}
815
+ onkeydown={pan_zoom.on_key_down}
816
+ onmouseleave={() => {
817
+ hovered = false
818
+ hover_info = null
819
+ change(null)
820
+ on_box_hover?.(null)
821
+ }}
822
+ onwheel={pan_zoom.on_wheel}
823
+ ontouchstart={pan_zoom.on_touch_start}
824
+ ontouchmove={pan_zoom.on_touch_move}
825
+ ontouchend={pan_zoom.on_touch_end}
826
+ ontouchcancel={pan_zoom.on_touch_end}
827
+ style:cursor={pan_zoom.cursor}
828
+ >
829
+ <ZoomRect start={pan_zoom.drag_start} current={pan_zoom.drag_current} />
830
+
831
+ {@render user_content?.({
832
+ height,
833
+ width,
834
+ x_scale_fn: scales.x,
835
+ x2_scale_fn: scales.x2,
836
+ y_scale_fn: scales.y,
837
+ y2_scale_fn: scales.y2,
838
+ pad,
839
+ x_range: ranges.current.x,
840
+ x2_range: ranges.current.x2,
841
+ y_range: ranges.current.y,
842
+ y2_range: ranges.current.y2,
843
+ fullscreen,
844
+ })}
845
+
846
+ {@render ref_lines_layer(ref_lines_by_z.below_grid)}
847
+
848
+ <PlotAxis
849
+ side="x"
850
+ ticks={ticks.x as number[]}
851
+ place={scales.x}
852
+ axis={x_axis}
853
+ domain={ranges.current.x as Vec2}
854
+ {pad}
855
+ {width}
856
+ {height}
857
+ show_grid={display.x_grid}
858
+ tick_label={(tick) =>
859
+ get_tick_label(tick, cat_axis === `x` ? effective_cat_ticks : x_axis.ticks)}
860
+ tick_color={cat_axis === `x` ? (tick) => slot_colors.get(tick) : undefined}
861
+ label_x={pad.l + chart_width / 2 + (x_axis.label_shift?.x ?? 0)}
862
+ label_y={height - pad.b / 3 + (x_axis.label_shift?.y ?? 0)}
863
+ />
864
+
865
+ {#if has_secondary && orientation === `horizontal`}
866
+ <PlotAxis
867
+ side="x2"
868
+ ticks={ticks.x2 as number[]}
869
+ place={scales.x2}
870
+ axis={x2_axis}
871
+ domain={ranges.current.x2 as Vec2}
872
+ {pad}
873
+ {width}
874
+ {height}
875
+ show_grid={display.x2_grid}
876
+ tick_label={(tick) => get_tick_label(tick, x2_axis.ticks)}
877
+ label_x={pad.l + chart_width / 2 + (x2_axis.label_shift?.x ?? 0)}
878
+ label_y={Math.max(12, pad.t - (x2_axis.label_shift?.y ?? 40))}
879
+ />
880
+ {/if}
881
+
882
+ <PlotAxis
883
+ side="y"
884
+ ticks={ticks.y as number[]}
885
+ place={scales.y}
886
+ axis={y_axis}
887
+ domain={ranges.current.y as Vec2}
888
+ {pad}
889
+ {width}
890
+ {height}
891
+ show_grid={display.y_grid}
892
+ tick_label={(tick) =>
893
+ get_tick_label(tick, cat_axis === `y` ? effective_cat_ticks : y_axis.ticks)}
894
+ tick_color={cat_axis === `y` ? (tick) => slot_colors.get(tick) : undefined}
895
+ label_x={Math.max(
896
+ 12,
897
+ pad.l - (y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) - LABEL_GAP_DEFAULT,
898
+ ) + (y_axis.label_shift?.x ?? 0)}
899
+ label_y={pad.t + chart_height / 2 + (y_axis.label_shift?.y ?? 0)}
900
+ />
901
+
902
+ {#if has_secondary && orientation === `vertical`}
903
+ <PlotAxis
904
+ side="y2"
905
+ ticks={ticks.y2 as number[]}
906
+ place={scales.y2}
907
+ axis={y2_axis}
908
+ domain={ranges.current.y2 as Vec2}
909
+ {pad}
910
+ {width}
911
+ {height}
912
+ show_grid={display.y2_grid}
913
+ tick_label={(tick) => get_tick_label(tick, y2_axis.ticks)}
914
+ label_x={y2_axis_label_x(y2_axis, width, pad.r, tick_label_widths.y2_max)}
915
+ label_y={pad.t + chart_height / 2 + (y2_axis.label_shift?.y ?? 0)}
916
+ />
917
+ {/if}
918
+
919
+ <defs>
920
+ <clipPath id={clip_path_id}>
921
+ <rect x={pad.l} y={pad.t} width={chart_width} height={chart_height} />
922
+ </clipPath>
923
+ </defs>
924
+
925
+ <!-- Chart content is clipped in two groups so reference lines can interleave
926
+ at their z positions while staying outside the chart clip: each line still
927
+ self-clips to the plot area inside ReferenceLine, only its annotation text
928
+ is allowed to overflow the plot edges. -->
929
+ <g clip-path="url(#{clip_path_id})">
930
+ <ZeroLines
931
+ {display}
932
+ x_scale_fn={scales.x}
933
+ x2_scale_fn={scales.x2}
934
+ y_scale_fn={scales.y}
935
+ y2_scale_fn={scales.y2}
936
+ x_range={ranges.current.x}
937
+ x2_range={ranges.current.x2}
938
+ y_range={ranges.current.y}
939
+ y2_range={ranges.current.y2}
940
+ x_scale_type={x_axis.scale_type}
941
+ x2_scale_type={x2_axis.scale_type}
942
+ y_scale_type={y_axis.scale_type}
943
+ y2_scale_type={y2_axis.scale_type}
944
+ has_x2={has_secondary && orientation === `horizontal`}
945
+ has_y2={has_secondary && orientation === `vertical`}
946
+ {width}
947
+ {height}
948
+ {pad}
949
+ />
950
+ </g>
951
+
952
+ {@render ref_lines_layer(ref_lines_by_z.below_lines)}
953
+
954
+ <!-- Boxes -->
955
+ <g clip-path="url(#{clip_path_id})">
956
+ {#each visible_boxes as box_item (box_item.series.id ?? box_item.idx)}
957
+ {@const stats = box_item.stats}
958
+ {#if Number.isFinite(stats.median)}
959
+ {@const vertical = orientation === `vertical`}
960
+ {@const cat_scale = vertical ? scales.x : scales.y}
961
+ {@const val_scale = box_val_scale(box_item.series)}
962
+ {@const color = box_color(box_item.idx)}
963
+ {@const draw_box = draws_box(box_item.series)}
964
+ {@const kde = violin_kdes.get(box_item.idx)}
965
+ {@const eff_side = box_item.series.side ?? side}
966
+ {@const bw = box_item.series.box_width ??
967
+ (kde ? DEFAULTS.box.violin_box_width : DEFAULTS.box.box_width)}
968
+ {@const c_lo = cat_scale(box_item.slot - bw / 2)}
969
+ {@const c_hi = cat_scale(box_item.slot + bw / 2)}
970
+ {@const c_center = cat_scale(box_item.slot)}
971
+ {@const cap = Math.abs(c_hi - c_lo) * (whisker_state.cap_fraction ?? 0.5) / 2}
972
+ {@const cap_lo = c_center - cap}
973
+ {@const cap_hi = c_center + cap}
974
+ {@const v_q1 = val_scale(stats.q1)}
975
+ {@const v_q3 = val_scale(stats.q3)}
976
+ {@const v_med = val_scale(stats.median)}
977
+ {@const v_wl = val_scale(stats.whisker_low)}
978
+ {@const v_wh = val_scale(stats.whisker_high)}
979
+ {@const v_mean = val_scale(stats.mean)}
980
+ {@const pt = (cross: number, val: number): Vec2 =>
981
+ vertical ? [cross, val] : [val, cross]}
982
+ {@const [q1x, q1y] = pt(c_lo, v_q1)}
983
+ {@const [q3x, q3y] = pt(c_hi, v_q3)}
984
+ {@const [wlx, wly] = pt(c_lo, v_wl)}
985
+ {@const [whx, why] = pt(c_hi, v_wh)}
986
+ {@const box_x = Math.min(q1x, q3x)}
987
+ {@const box_y = Math.min(q1y, q3y)}
988
+ {@const box_w = Math.abs(q3x - q1x)}
989
+ {@const box_h = Math.abs(q3y - q1y)}
990
+ {@const hit_x = Math.min(wlx, whx)}
991
+ {@const hit_y = Math.min(wly, why)}
992
+ {@const hit_w = Math.abs(whx - wlx)}
993
+ {@const hit_h = Math.abs(why - wly)}
994
+ {@const [label_x, label_y] = vertical
995
+ ? [c_center, Math.min(v_wh, v_wl) - 6]
996
+ : [Math.max(v_wh, v_wl) + 6, c_center]}
997
+ {@const violin_half = Math.abs(
998
+ cat_scale(box_item.slot + (box_item.series.violin_width ?? violin_width) / 2) -
999
+ c_center,
1000
+ )}
1001
+ {@const max_density = kde ? (violin_max_density.get(box_item.idx) ?? 0) : 0}
1002
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
1003
+ <g
1004
+ class="box-series"
1005
+ data-box-idx={box_item.idx}
1006
+ role="button"
1007
+ tabindex="0"
1008
+ aria-label={`box ${box_item.idx + 1}: ${box_item.series.label ?? ``}`}
1009
+ style:cursor={on_box_click ? `pointer` : undefined}
1010
+ opacity={hovered_legend_series_idx !== null &&
1011
+ hovered_legend_series_idx !== box_item.idx
1012
+ ? 0.25
1013
+ : 1}
1014
+ onmousemove={handle_box_hover(box_item, color)}
1015
+ onmouseleave={() => {
1016
+ hover_info = null
1017
+ change(null)
1018
+ on_box_hover?.(null)
1019
+ }}
1020
+ onclick={(evt) => on_box_click?.({ ...get_box_data(box_item, color), event: evt })}
1021
+ onkeydown={(evt) => {
1022
+ if (evt.key === `Enter` || evt.key === ` `) {
1023
+ evt.preventDefault()
1024
+ on_box_click?.({ ...get_box_data(box_item, color), event: evt })
1025
+ }
1026
+ }}
1027
+ >
1028
+ <!-- violin (KDE density) -->
1029
+ {#if kde && max_density > 0}
1030
+ {@const grid_px = kde.grid.map((g_val) => val_scale(g_val))}
1031
+ {@const offsets = kde.density.map((den) => (den / max_density) * violin_half)}
1032
+ {@const screen_side = to_screen_side(eff_side, vertical)}
1033
+ <path
1034
+ class="violin-area"
1035
+ d={violin_path(grid_px, offsets, c_center, screen_side, pt)}
1036
+ fill={color}
1037
+ fill-opacity={violin_state.opacity}
1038
+ stroke={color}
1039
+ stroke-width={violin_state.stroke_width}
1040
+ />
1041
+ {/if}
1042
+ {#if draw_box}
1043
+ {@const wc = whisker_state.color}
1044
+ {@const ww = whisker_state.width}
1045
+ <!-- whiskers + caps -->
1046
+ {@render seg(pt(c_center, v_q1), pt(c_center, v_wl), wc, ww)}
1047
+ {@render seg(pt(c_center, v_q3), pt(c_center, v_wh), wc, ww)}
1048
+ {#if cap > 0}
1049
+ {@render seg(pt(cap_lo, v_wl), pt(cap_hi, v_wl), wc, ww)}
1050
+ {@render seg(pt(cap_lo, v_wh), pt(cap_hi, v_wh), wc, ww)}
1051
+ {/if}
1052
+ <!-- IQR box -->
1053
+ <rect
1054
+ class="iqr-box"
1055
+ x={box_x}
1056
+ y={box_y}
1057
+ width={Math.max(1, box_w)}
1058
+ height={Math.max(1, box_h)}
1059
+ rx={box_state.border_radius}
1060
+ ry={box_state.border_radius}
1061
+ fill={color}
1062
+ fill-opacity={box_state.opacity}
1063
+ stroke={box_state.stroke_color}
1064
+ stroke-width={box_state.stroke_width}
1065
+ />
1066
+ <!-- median (solid) and mean (dashed) -->
1067
+ {@render seg(pt(c_lo, v_med), pt(c_hi, v_med), median_state.color, median_state.width)}
1068
+ {#if show_mean}
1069
+ {@render seg(
1070
+ pt(c_lo, v_mean),
1071
+ pt(c_hi, v_mean),
1072
+ median_state.color,
1073
+ median_state.width,
1074
+ `3 2`,
1075
+ )}
1076
+ {/if}
1077
+ <!-- outliers -->
1078
+ {#if show_outliers}
1079
+ {#each stats.outliers as outlier, out_idx (out_idx)}
1080
+ {@const [ox, oy] = pt(c_center, val_scale(outlier))}
1081
+ <circle
1082
+ cx={ox}
1083
+ cy={oy}
1084
+ r={outlier_state.radius}
1085
+ fill={color}
1086
+ fill-opacity={outlier_state.opacity}
1087
+ stroke={box_state.stroke_color}
1088
+ stroke-width={outlier_state.stroke_width}
1089
+ />
1090
+ {/each}
1091
+ {/if}
1092
+ {/if}
1093
+ <!-- value label -->
1094
+ {#if show_value_labels}
1095
+ <text
1096
+ x={label_x}
1097
+ y={label_y}
1098
+ text-anchor={vertical ? `middle` : `start`}
1099
+ dominant-baseline={vertical ? `auto` : `central`}
1100
+ class="value-label"
1101
+ fill={color}
1102
+ >
1103
+ {value_label_for(stats)}
1104
+ </text>
1105
+ {/if}
1106
+ <!-- transparent backing so the box/whisker region is hoverable (the violin
1107
+ path is a painted child and bubbles to the group's pointer handlers too) -->
1108
+ <rect
1109
+ class="hover-target"
1110
+ x={hit_x}
1111
+ y={hit_y}
1112
+ width={Math.max(1, hit_w)}
1113
+ height={Math.max(1, hit_h)}
1114
+ fill="transparent"
1115
+ />
1116
+ </g>
1117
+ {/if}
1118
+ {/each}
1119
+ </g>
1120
+
1121
+ {@render ref_lines_layer(ref_lines_by_z.below_points)}
1122
+ {@render ref_lines_layer(ref_lines_by_z.above_all)}
1123
+ </svg>
1124
+
1125
+ {#if legend && should_show_legend}
1126
+ {@const legend_pos = placed_coords(
1127
+ legend_auto_outside,
1128
+ { x: legend_outside_x, y: legend_outside_y },
1129
+ legend_placement,
1130
+ legend_tween.coords.current,
1131
+ { x: pad.l + 10, y: pad.t + 10 },
1132
+ )}
1133
+ <PlotLegend
1134
+ bind:root_element={legend_element}
1135
+ {...legend}
1136
+ series_data={legend_data}
1137
+ on_toggle={legend?.on_toggle ?? legend_vis.on_toggle}
1138
+ on_group_toggle={legend?.on_group_toggle ?? legend_vis.on_group_toggle}
1139
+ on_double_click={legend?.on_double_click ?? legend_vis.on_double_click}
1140
+ on_hover_change={legend_tween.set_locked}
1141
+ on_item_hover={(item) =>
1142
+ (hovered_legend_series_idx = item != null && item.series_idx >= 0
1143
+ ? item.series_idx
1144
+ : null)}
1145
+ active_series_idx={hover_info?.series_idx ?? hovered_legend_series_idx}
1146
+ style={`position: absolute; left: ${legend_pos.x}px; top: ${legend_pos.y}px; pointer-events: auto; ${
1147
+ legend?.style || ``
1148
+ }`}
1149
+ />
1150
+ {/if}
1151
+
1152
+ {#if hover_info && hovered}
1153
+ <PlotTooltip
1154
+ x={hover_info.cx}
1155
+ y={hover_info.cy}
1156
+ offset={{ x: 10, y: 5 }}
1157
+ constrain_to={{ width, height }}
1158
+ fallback_size={{ width: 140, height: 50 }}
1159
+ bg_color={hover_info.color}
1160
+ >
1161
+ {#if tooltip}
1162
+ {@render tooltip({ ...hover_info, fullscreen })}
1163
+ {:else}
1164
+ {@const fmt = (orientation === `vertical` ? y_axis.format : x_axis.format) || `.3~s`}
1165
+ {@const stat = hover_info.stats}
1166
+ {@const rows = [
1167
+ [`max`, stat.whisker_high],
1168
+ [`q3`, stat.q3],
1169
+ [`median`, stat.median],
1170
+ [`q1`, stat.q1],
1171
+ [`min`, stat.whisker_low],
1172
+ ...(show_mean ? [[`mean`, stat.mean] as const] : []),
1173
+ ] as const}
1174
+ {#if hover_info.category_label}
1175
+ <div><strong>{hover_info.category_label}</strong></div>
1176
+ {/if}
1177
+ {#each rows as [label, value] (label)}
1178
+ <div>{label}: {format_value(value, fmt)}</div>
1179
+ {/each}
1180
+ {#if show_outliers && stat.outliers.length > 0}
1181
+ <div>outliers: {stat.outliers.length}</div>
1182
+ {/if}
1183
+ {/if}
1184
+ </PlotTooltip>
1185
+ {/if}
1186
+
1187
+ {#if show_controls}
1188
+ <BoxPlotControls
1189
+ toggle_props={{
1190
+ ...controls_toggle_props,
1191
+ style: `--ctrl-btn-right: var(--fullscreen-btn-offset, 30px); ${
1192
+ controls_toggle_props?.style ?? ``
1193
+ }`,
1194
+ }}
1195
+ pane_props={controls_pane_props}
1196
+ bind:show_controls
1197
+ bind:controls_open
1198
+ bind:orientation
1199
+ bind:whisker_mode
1200
+ bind:show_outliers
1201
+ bind:show_mean
1202
+ bind:kind
1203
+ bind:side
1204
+ bind:x_axis
1205
+ bind:x2_axis={x2_axis_prop}
1206
+ bind:y_axis
1207
+ bind:y2_axis={y2_axis_prop}
1208
+ bind:display
1209
+ auto_x_range={auto_ranges.x as Vec2}
1210
+ auto_x2_range={auto_ranges.x2 as Vec2}
1211
+ auto_y_range={auto_ranges.y as Vec2}
1212
+ auto_y2_range={auto_ranges.y2 as Vec2}
1213
+ has_x2_points={has_secondary && orientation === `horizontal`}
1214
+ has_y2_points={has_secondary && orientation === `vertical`}
1215
+ children={controls_extra}
1216
+ />
1217
+ {/if}
1218
+ {/if}
1219
+
1220
+ {@render children?.({ height, width, fullscreen })}
1221
+ </div>
1222
+
1223
+ <style>
1224
+ .box-plot {
1225
+ position: relative;
1226
+ width: 100%;
1227
+ height: var(--boxplot-height, auto);
1228
+ min-height: var(--boxplot-min-height, 300px);
1229
+ container-type: size;
1230
+ z-index: var(--boxplot-z-index, auto);
1231
+ border-radius: var(--boxplot-border-radius, var(--border-radius, 3pt));
1232
+ flex: var(--boxplot-flex, 1);
1233
+ display: var(--boxplot-display, flex);
1234
+ flex-direction: column;
1235
+ background: var(--boxplot-bg, var(--plot-bg));
1236
+ }
1237
+ .box-plot.fullscreen {
1238
+ position: fixed;
1239
+ top: 0;
1240
+ left: 0;
1241
+ width: 100vw !important;
1242
+ height: 100vh !important;
1243
+ z-index: var(--boxplot-fullscreen-z-index, var(--z-index-overlay-nav, 100000001));
1244
+ margin: 0;
1245
+ border-radius: 0;
1246
+ background: var(--boxplot-fullscreen-bg, var(--boxplot-bg, var(--plot-bg)));
1247
+ max-height: none !important;
1248
+ overflow: hidden;
1249
+ /* border-top (not padding-top): bind:clientHeight includes padding but excludes
1250
+ borders - padding made the chart overflow + clip its bottom 2em (x-axis title) */
1251
+ border-top: var(--plot-fullscreen-padding-top, 2em) solid
1252
+ var(--boxplot-fullscreen-bg, var(--boxplot-bg, var(--plot-bg, transparent)));
1253
+ box-sizing: border-box;
1254
+ }
1255
+ .header-controls {
1256
+ position: absolute;
1257
+ top: var(--ctrl-btn-top, 5pt);
1258
+ right: var(--fullscreen-btn-right, 4px);
1259
+ z-index: var(--fullscreen-btn-z-index, 10);
1260
+ display: flex;
1261
+ align-items: center;
1262
+ gap: 8px;
1263
+ }
1264
+ .header-controls :global(.fullscreen-toggle) {
1265
+ position: static;
1266
+ opacity: 1;
1267
+ }
1268
+ .box-plot :global(.pane-toggle),
1269
+ .box-plot .header-controls {
1270
+ opacity: 0;
1271
+ transition: opacity 0.2s, background-color 0.2s;
1272
+ }
1273
+ .box-plot:hover :global(.pane-toggle),
1274
+ .box-plot:hover .header-controls,
1275
+ .box-plot :global(.pane-toggle:focus-visible),
1276
+ .box-plot :global(.pane-toggle[aria-expanded='true']),
1277
+ .box-plot .header-controls:focus-within {
1278
+ opacity: 1;
1279
+ }
1280
+ svg {
1281
+ width: var(--boxplot-svg-width, 100%);
1282
+ height: var(--boxplot-svg-height, 100%);
1283
+ flex: var(--boxplot-svg-flex, 1);
1284
+ overflow: var(--boxplot-svg-overflow, visible);
1285
+ fill: var(--text-color);
1286
+ font-weight: var(--scatter-font-weight);
1287
+ font-size: var(--scatter-font-size);
1288
+ }
1289
+ .value-label {
1290
+ font-size: 11px;
1291
+ }
1292
+ </style>