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
@@ -13,18 +13,12 @@
13
13
  import type { FormulaLabelSegment } from '../composition/format'
14
14
  import { normalize_show_controls } from '../controls'
15
15
  import { sanitize_html } from '../sanitize'
16
- import { ClickFeedback, DragOverlay, Spinner } from '../feedback'
17
- import Icon from '../Icon.svelte'
16
+ import { Spinner } from '../feedback'
18
17
  import { format_num } from '../labels'
19
- import {
20
- set_fullscreen_bg,
21
- setup_fullscreen_effect,
22
- toggle_fullscreen,
23
- } from '../layout'
24
- import { to_radians, type Point3D, type Vec3 } from '../math'
25
- import { ColorBar, PlotTooltip } from '../plot'
26
- import { centered_rect, pad_rect, rects_overlap, rect_within_rect } from '../plot/layout'
27
- import type { Rect } from '../plot/layout'
18
+ import { to_radians, type Point3D, type Vec2, type Vec3 } from '../math'
19
+ import { ColorBar } from '../plot'
20
+ import { centered_rect, pad_rect, rects_overlap, rect_within_rect } from '../plot/core/layout'
21
+ import type { Rect } from '../plot/core/layout'
28
22
  import { create_pulse_animation } from '../effects.svelte'
29
23
  import { DEFAULTS } from '../settings'
30
24
  import type { AnyStructure } from '../structure'
@@ -40,39 +34,41 @@
40
34
  get_triangle_vertical_edges,
41
35
  TRIANGLE_VERTICES,
42
36
  } from './barycentric-coords'
43
- import ConvexHullControls from './ConvexHullControls.svelte'
44
- import ConvexHullInfoPane from './ConvexHullInfoPane.svelte'
45
- import ConvexHullTooltip from './ConvexHullTooltip.svelte'
37
+ import { create_canvas_interactions } from './canvas-interactions.svelte'
38
+ import ConvexHullChrome from './ConvexHullChrome.svelte'
46
39
  import GasPressureControls from './GasPressureControls.svelte'
47
40
  import * as helpers from './helpers'
41
+ import { create_hull_data_pipeline } from './hull-state.svelte'
48
42
  import type { BaseConvexHullProps, Hull3DProps } from './index'
49
43
  import { CONVEX_HULL_STYLE, default_controls, default_hull_config } from './index'
50
- import StructurePopup from './StructurePopup.svelte'
51
44
  import TemperatureSlider from './TemperatureSlider.svelte'
52
45
  import * as thermo from './thermodynamics'
53
46
  import type {
54
47
  ConvexHullEntry,
55
48
  ConvexHullTriangle,
56
49
  HighlightStyle,
57
- HoverData3D,
58
50
  HullFaceColorMode,
59
51
  LabelPlacement,
60
52
  } from './types'
61
53
  import { compute_hull_stability } from './helpers'
54
+ import { MAGNETIC_ORDERING_CATEGORY } from './types'
62
55
 
63
56
  let {
64
57
  entries = [],
65
58
  controls = {},
66
59
  config = {},
60
+ show_controls,
67
61
  on_point_click,
68
62
  on_point_hover,
69
63
  fullscreen = $bindable(DEFAULTS.convex_hull.ternary.fullscreen),
70
- enable_fullscreen = true,
64
+ fullscreen_toggle = true,
71
65
  enable_info_pane = true,
72
66
  wrapper = $bindable(),
73
67
  label_threshold = 50,
74
68
  show_stable = $bindable(DEFAULTS.convex_hull.ternary.show_stable),
75
69
  show_unstable = $bindable(DEFAULTS.convex_hull.ternary.show_unstable),
70
+ entry_category = MAGNETIC_ORDERING_CATEGORY,
71
+ hidden_categories = $bindable([]),
76
72
  show_hull_faces = $bindable(DEFAULTS.convex_hull.ternary.show_hull_faces),
77
73
  hull_face_opacity = $bindable(DEFAULTS.convex_hull.ternary.hull_face_opacity),
78
74
  hull_face_color_mode = $bindable(
@@ -84,7 +80,7 @@
84
80
  DEFAULTS.convex_hull.ternary.color_scale as D3InterpolateName,
85
81
  ),
86
82
  info_pane_open = $bindable(DEFAULTS.convex_hull.ternary.info_pane_open),
87
- legend_pane_open = $bindable(DEFAULTS.convex_hull.ternary.legend_pane_open),
83
+ controls_open = $bindable(DEFAULTS.convex_hull.ternary.legend_pane_open),
88
84
  max_hull_dist_show_phases = $bindable(
89
85
  DEFAULTS.convex_hull.ternary.max_hull_dist_show_phases,
90
86
  ),
@@ -119,7 +115,7 @@
119
115
  } = $props()
120
116
 
121
117
  const merged_controls = $derived({ ...default_controls, ...controls })
122
- const controls_config = $derived(normalize_show_controls(merged_controls.show))
118
+ const controls_config = $derived(normalize_show_controls(show_controls))
123
119
  const merged_config = $derived({
124
120
  ...default_hull_config,
125
121
  ...config,
@@ -127,89 +123,36 @@
127
123
  margin: { t: 40, r: 40, b: 60, l: 60, ...config.margin },
128
124
  })
129
125
 
130
- // Temperature-dependent free energy support
131
- const { has_temp_data, available_temperatures } = $derived(
132
- helpers.analyze_temperature_data(entries),
133
- )
134
-
135
- // Initialize or reset temperature when it's undefined or no longer valid
136
- $effect(() => {
137
- if (
138
- has_temp_data &&
139
- available_temperatures.length > 0 &&
140
- (temperature === undefined || !available_temperatures.includes(temperature))
141
- ) temperature = available_temperatures[0]
142
- })
143
-
144
- // Filter entries by temperature when in temperature mode
145
- const temp_filtered_entries = $derived(
146
- has_temp_data && temperature !== undefined
147
- ? helpers.filter_entries_at_temperature(entries, temperature, {
148
- interpolate: interpolate_temperature,
149
- max_interpolation_gap,
150
- })
151
- : entries,
152
- )
153
-
154
- // Gas-dependent chemical potential support (corrections based on T, P)
155
- // Default to DEFAULT_GAS_TEMP (room temperature) when no temperature specified
156
- const {
157
- entries: gas_corrected_entries,
158
- analysis: gas_analysis,
159
- merged_config: merged_gas_config,
160
- } = $derived(
161
- helpers.get_gas_corrected_entries(
162
- temp_filtered_entries,
163
- gas_config,
164
- gas_pressures,
165
- temperature ?? helpers.DEFAULT_GAS_TEMP,
166
- ),
167
- )
168
-
169
- let { // Compute energy mode information
170
- has_precomputed_e_form,
171
- has_precomputed_hull,
172
- can_compute_e_form,
173
- can_compute_hull,
174
- energy_mode,
175
- unary_refs,
176
- } = $derived(
177
- helpers.compute_energy_mode_info(
178
- gas_corrected_entries,
179
- thermo.find_lowest_energy_unary_refs,
180
- energy_source_mode,
181
- ),
182
- )
183
-
184
- const effective_entries = $derived(
185
- helpers.get_effective_entries(
186
- gas_corrected_entries,
187
- energy_mode,
188
- unary_refs,
189
- thermo.compute_e_form_per_atom,
190
- ),
191
- )
192
-
193
- // Process convex hull data with unified PhaseData interface using effective entries
194
- const pd_data = $derived(thermo.process_hull_entries(effective_entries))
195
-
196
- // Pre-compute polymorph stats once for O(1) tooltip lookups
197
- const polymorph_stats_map = $derived(
198
- helpers.compute_all_polymorph_stats(effective_entries),
199
- )
200
-
201
- const elements = $derived.by(() => {
202
- if (pd_data.elements.length > 3) {
203
- console.error(
204
- `ConvexHull3D: Dataset contains ${pd_data.elements.length} elements, but ternary diagrams require exactly 3. Found: [${
205
- pd_data.elements.join(`, `)
206
- }]`,
207
- )
208
- return []
209
- }
210
-
211
- return pd_data.elements
126
+ // Shared reactive data pipeline (temperature → gas → energy mode → hull data → threshold)
127
+ // Explicit generic breaks the circular type inference through the all_enriched_entries thunk
128
+ const hull_data = create_hull_data_pipeline<ConvexHullEntry>({
129
+ dim: 3,
130
+ entries: () => entries,
131
+ temperature: () => temperature,
132
+ interpolate_temperature: () => interpolate_temperature,
133
+ max_interpolation_gap: () => max_interpolation_gap,
134
+ gas_config: () => gas_config,
135
+ gas_pressures: () => gas_pressures,
136
+ energy_source_mode: () => energy_source_mode,
137
+ all_enriched_entries: () => all_enriched_entries,
138
+ max_hull_dist_show_phases: () => max_hull_dist_show_phases,
139
+ show_stable: () => show_stable,
140
+ show_unstable: () => show_unstable,
141
+ entry_category: () => entry_category,
142
+ hidden_categories: () => hidden_categories,
143
+ keep_plot_entry: (entry, max_dist) => (entry.e_above_hull ?? 0) <= max_dist,
144
+ set_temperature: (next_temp) => temperature = next_temp,
145
+ set_max_hull_dist_show_phases: (value) => max_hull_dist_show_phases = value,
146
+ set_stable_entries: (value) => stable_entries = value,
147
+ set_unstable_entries: (value) => unstable_entries = value,
212
148
  })
149
+ const has_temp_data = $derived(hull_data.has_temp_data)
150
+ const gas_analysis = $derived(hull_data.gas_analysis)
151
+ const merged_gas_config = $derived(hull_data.merged_gas_config)
152
+ const pd_data = $derived(hull_data.pd_data)
153
+ const elements = $derived(hull_data.elements)
154
+ const plot_entries = $derived(hull_data.plot_entries)
155
+ const visible_entries = $derived(hull_data.visible_entries)
213
156
 
214
157
  // 1) Raw 3D coordinates (formation-energy z), independent of hull state
215
158
  const coords_entries = $derived.by(() => {
@@ -250,7 +193,7 @@
250
193
  // Enrich coords with e_above_hull from cached hull model (before filtering)
251
194
  const all_enriched_entries = $derived.by(() => {
252
195
  if (coords_entries.length === 0) return []
253
- if (energy_mode !== `on-the-fly`) return coords_entries
196
+ if (hull_data.energy_mode !== `on-the-fly`) return coords_entries
254
197
  const pts = coords_entries.map(({ x, y, z }) => ({ x, y, z }))
255
198
  const raw_dists = thermo.compute_e_above_hull_for_points(pts, hull_model)
256
199
  return coords_entries.map((entry, idx) => ({
@@ -258,51 +201,10 @@
258
201
  }))
259
202
  })
260
203
 
261
- // Auto threshold: show all for few entries, use default for many, interpolate between
262
- const max_hull_dist_in_data = $derived(
263
- helpers.calc_max_hull_dist_in_data(all_enriched_entries),
264
- )
265
- const auto_default_threshold = $derived(helpers.compute_auto_hull_dist_threshold(
266
- all_enriched_entries.length,
267
- max_hull_dist_in_data,
268
- DEFAULTS.convex_hull.ternary.max_hull_dist_show_phases,
269
- ))
270
-
271
- const next_auto_threshold = helpers.auto_threshold_reset(
272
- DEFAULTS.convex_hull.ternary.max_hull_dist_show_phases,
273
- )
274
- $effect(() => {
275
- max_hull_dist_show_phases = next_auto_threshold(
276
- entries,
277
- max_hull_dist_show_phases,
278
- auto_default_threshold,
279
- ) ?? max_hull_dist_show_phases
280
- })
281
-
282
- // Filter by threshold; visibility is a view predicate, not entry state.
283
- const plot_entries = $derived(
284
- all_enriched_entries.filter((entry) =>
285
- (entry.e_above_hull ?? 0) <= max_hull_dist_show_phases
286
- ),
287
- )
288
- const visible_entries = $derived(helpers.visible_entries(
289
- plot_entries,
290
- show_stable,
291
- show_unstable,
292
- ))
293
-
294
- $effect(() => {
295
- stable_entries = plot_entries.filter(helpers.entry_is_stable)
296
- unstable_entries = plot_entries.filter(helpers.entry_is_unstable)
297
- })
298
-
299
204
  // Canvas rendering
300
205
  let canvas: HTMLCanvasElement | undefined = undefined
301
206
  let ctx: CanvasRenderingContext2D | null = null
302
207
 
303
- // Performance optimization
304
- let frame_id = 0
305
-
306
208
  const camera_default = {
307
209
  elevation: DEFAULTS.convex_hull.ternary.camera_elevation,
308
210
  azimuth: DEFAULTS.convex_hull.ternary.camera_azimuth,
@@ -418,43 +320,64 @@
418
320
  }
419
321
  })
420
322
 
421
- // Interaction state
422
- let is_dragging = $state(false)
423
- let drag_started = $state(false)
424
- let last_mouse = $state({ x: 0, y: 0 })
425
- let hover_data = $state<HoverData3D<ConvexHullEntry> | null>(null)
426
- let copy_feedback = $state({ visible: false, position: { x: 0, y: 0 } })
427
-
428
- // Drag and drop state
429
- let drag_over = $state(false)
430
-
431
- // Structure popup state
432
- let modal_open = $state(false)
433
- let selected_structure = $state<AnyStructure | null>(null)
434
- let modal_place_right = $state(true)
435
- $effect(() => {
436
- const current_selection = helpers.current_entry(selected_entry, plot_entries)
437
- const stale_selection = selected_entry && !current_selection
438
- if (stale_selection) selected_entry = null
439
- else if (current_selection && current_selection !== selected_entry) {
440
- selected_entry = current_selection
441
- }
442
- const current_hover = helpers.current_entry(hover_data?.entry, plot_entries)
443
- if (hover_data?.entry && !current_hover) {
444
- hover_data = null
445
- on_point_hover?.(null)
446
- } else if (hover_data && current_hover && current_hover !== hover_data.entry) {
447
- hover_data = { ...hover_data, entry: current_hover }
448
- }
449
- if (modal_open) {
450
- const structure = current_selection && extract_structure_from_entry(current_selection)
451
- if (structure) selected_structure = structure
452
- else {
453
- modal_open = false
454
- selected_structure = null
323
+ // Shared canvas-interaction scaffold (mouse/keyboard handlers, hover/drag/popup
324
+ // state, canvas sizing, render scheduler). Rotation math + keydown actions stay local.
325
+ const interactions = create_canvas_interactions({
326
+ wheel_clamp: [0.5, 10],
327
+ fullscreen_bg_var: `--hull-3d-bg-fullscreen`,
328
+ canvas: () => canvas,
329
+ wrapper: () => wrapper,
330
+ ctx: () => ctx,
331
+ set_ctx: (context) => ctx = context,
332
+ set_canvas_dims: (dims) => canvas_dims = dims,
333
+ visible_entries: () => visible_entries,
334
+ plot_entries: () => plot_entries,
335
+ selected_entry: () => selected_entry,
336
+ set_selected_entry: (entry) => selected_entry = entry,
337
+ fullscreen: () => fullscreen,
338
+ enable_click_selection: () => enable_click_selection,
339
+ enable_structure_preview: () => enable_structure_preview,
340
+ on_point_click: () => on_point_click,
341
+ on_point_hover: () => on_point_hover,
342
+ on_file_drop: () => on_file_drop,
343
+ entry_category: () => entry_category,
344
+ zoom: () => camera.zoom,
345
+ set_zoom: (zoom) => camera.zoom = zoom,
346
+ project_point: project_3d_point,
347
+ extract_structure: extract_structure_from_entry,
348
+ render_frame,
349
+ on_drag: (dx, dy, panning) => {
350
+ if (panning) {
351
+ camera.center_x += dx
352
+ camera.center_y += dy
353
+ } else {
354
+ // Horizontal drag -> azimuth rotation around z-axis
355
+ camera.azimuth += dx * 0.3 // Positive dx (drag right) rotates clockwise
356
+ // Vertical drag -> elevation angle (full range)
357
+ camera.elevation -= dy * 0.3 // Positive dy (drag down) tilts view down
455
358
  }
456
- }
359
+ },
360
+ // Reset pan center when entering/exiting fullscreen
361
+ on_fullscreen_change: () => {
362
+ camera.center_x = 0
363
+ camera.center_y = -50
364
+ },
365
+ actions: () => ({
366
+ r: reset_camera,
367
+ t: () => {
368
+ camera.elevation = 0
369
+ camera.azimuth = 0
370
+ center_camera(0)
371
+ },
372
+ b: () => color_mode = color_mode === `stability` ? `energy` : `stability`,
373
+ s: () => show_stable = !show_stable,
374
+ u: () => show_unstable = !show_unstable,
375
+ h: () => show_hull_faces = !show_hull_faces,
376
+ l: () => show_stable_labels = !show_stable_labels,
377
+ }),
457
378
  })
379
+ const { render_once } = interactions
380
+ const sorted_points_cache = $derived(interactions.sorted_points_cache)
458
381
 
459
382
  // Hull face color (customizable via controls)
460
383
  let hull_face_color = $state(`#4caf50`)
@@ -478,17 +401,14 @@
478
401
  // Re-render when important state changes
479
402
  $effect(() => {
480
403
  // oxfmt-ignore
481
- void [show_hull_faces, color_mode, color_scale, show_stable_labels, show_unstable_labels, max_hull_dist_show_labels, camera.elevation, camera.azimuth, camera.zoom, camera.center_x, camera.center_y, plot_entries, hull_face_color, hull_face_opacity, hull_face_color_mode, element_colors, highlighted_entries, text_color] // track reactively
404
+ void [show_hull_faces, color_mode, color_scale, show_stable_labels, show_unstable_labels, max_hull_dist_show_labels, camera.elevation, camera.azimuth, camera.zoom, camera.center_x, camera.center_y, plot_entries, visible_entries, hull_face_color, hull_face_opacity, hull_face_color_mode, element_colors, highlighted_entries, text_color] // track reactively
482
405
 
483
406
  render_once()
484
407
  })
485
408
 
486
- // Function to extract structure data from a convex hull entry
487
- function extract_structure_from_entry(
488
- entry: ConvexHullEntry,
489
- ): AnyStructure | null {
490
- const orig_entry = entries.find((ent) => ent.entry_id === entry.entry_id)
491
- return orig_entry?.structure as AnyStructure || null
409
+ // function (not const) so the create_canvas_interactions options above can reference it
410
+ function extract_structure_from_entry(entry: ConvexHullEntry): AnyStructure | null {
411
+ return helpers.extract_structure_from_entry(entries, entry)
492
412
  }
493
413
 
494
414
  const reset_camera = () => Object.assign(camera, camera_default)
@@ -496,16 +416,17 @@
496
416
  reset_camera()
497
417
  fullscreen = DEFAULTS.convex_hull.ternary.fullscreen
498
418
  info_pane_open = DEFAULTS.convex_hull.ternary.info_pane_open
499
- legend_pane_open = DEFAULTS.convex_hull.ternary.legend_pane_open
419
+ controls_open = DEFAULTS.convex_hull.ternary.legend_pane_open
500
420
  color_mode = DEFAULTS.convex_hull.ternary.color_mode
501
421
  color_scale = DEFAULTS.convex_hull.ternary.color_scale as D3InterpolateName
502
422
  show_stable = DEFAULTS.convex_hull.ternary.show_stable
503
423
  show_unstable = DEFAULTS.convex_hull.ternary.show_unstable
424
+ hidden_categories = []
504
425
  show_stable_labels = DEFAULTS.convex_hull.ternary.show_stable_labels
505
426
  show_unstable_labels = DEFAULTS.convex_hull.ternary.show_unstable_labels
506
427
  max_hull_dist_show_labels = DEFAULTS.convex_hull.ternary.max_hull_dist_show_labels
507
428
  // Use auto-computed threshold based on entry count instead of static default
508
- max_hull_dist_show_phases = auto_default_threshold
429
+ max_hull_dist_show_phases = hull_data.auto_default_threshold
509
430
  show_hull_faces = DEFAULTS.convex_hull.ternary.show_hull_faces
510
431
  hull_face_color = DEFAULTS.convex_hull.ternary.hull_face_color
511
432
  hull_face_opacity = DEFAULTS.convex_hull.ternary.hull_face_opacity
@@ -513,75 +434,6 @@
513
434
  .hull_face_color_mode as HullFaceColorMode
514
435
  }
515
436
 
516
- const handle_keydown = (event: KeyboardEvent) => {
517
- const target = event.target
518
- if (target instanceof HTMLElement && target.tagName.match(/INPUT|TEXTAREA/)) return
519
-
520
- // Stop propagation if event came from canvas to prevent wrapper's handler
521
- // from running again (both have onkeydown, causing duplicate handling)
522
- if (target === canvas) {
523
- event.stopPropagation()
524
- }
525
-
526
- if (event.key === `Escape` && modal_open) {
527
- close_structure_popup()
528
- return
529
- }
530
-
531
- // Handle Enter for keyboard accessibility - select hovered entry
532
- if (event.key === `Enter`) {
533
- const entry = hover_data?.entry
534
- if (entry) {
535
- on_point_click?.(entry)
536
- if (enable_click_selection) {
537
- selected_entry = entry
538
- if (enable_structure_preview) {
539
- const structure = extract_structure_from_entry(entry)
540
- if (structure) {
541
- selected_structure = structure
542
- modal_place_right = helpers.calculate_modal_side(wrapper)
543
- modal_open = true
544
- }
545
- }
546
- }
547
- } else if (modal_open) {
548
- close_structure_popup()
549
- }
550
- return
551
- }
552
-
553
- const actions: Record<string, () => void> = {
554
- r: reset_camera,
555
- t: () => {
556
- camera.elevation = 0
557
- camera.azimuth = 0
558
- center_camera(0)
559
- },
560
- b: () => color_mode = color_mode === `stability` ? `energy` : `stability`,
561
- s: () => show_stable = !show_stable,
562
- u: () => show_unstable = !show_unstable,
563
- h: () => show_hull_faces = !show_hull_faces,
564
- l: () => show_stable_labels = !show_stable_labels,
565
- }
566
- actions[event.key.toLowerCase()]?.()
567
- }
568
-
569
- async function handle_file_drop(event: DragEvent): Promise<void> {
570
- drag_over = false
571
- const data = await helpers.parse_hull_entries_from_drop(event)
572
- if (data) on_file_drop?.(data)
573
- }
574
-
575
- async function copy_entry_data(
576
- entry: ConvexHullEntry,
577
- position: { x: number; y: number },
578
- ) {
579
- await helpers.copy_entry_to_clipboard(entry, position, (visible, pos) => {
580
- copy_feedback.visible = visible
581
- copy_feedback.position = pos
582
- })
583
- }
584
-
585
437
  const get_point_color = (entry: ConvexHullEntry): string =>
586
438
  helpers.get_point_color_for_entry(
587
439
  entry,
@@ -841,12 +693,14 @@
841
693
  return energy_face_scale?.(avg_e_form - min_face_e_form) ?? hull_face_color
842
694
  }
843
695
  if (hull_face_color_mode === `dominant_element`) {
844
- // Find element vertex closest to face centroid in 2D ternary space
696
+ // Find element vertex closest to face centroid in 2D ternary space (single-pass argmin)
845
697
  const { x: cx, y: cy } = tri.centroid
846
- const dists = TRIANGLE_VERTICES.map(([tx, ty]) =>
847
- Math.hypot(cx - tx, cy - ty)
848
- )
849
- const el = elements[dists.indexOf(Math.min(...dists))]
698
+ let [closest_idx, min_dist] = [0, Infinity]
699
+ for (const [idx, [tx, ty]] of TRIANGLE_VERTICES.entries()) {
700
+ const dist = Math.hypot(cx - tx, cy - ty)
701
+ if (dist < min_dist) [closest_idx, min_dist] = [idx, dist]
702
+ }
703
+ const el = elements[closest_idx]
850
704
  return element_colors[el] ?? `#888888`
851
705
  }
852
706
  if (hull_face_color_mode === `facet_index`) {
@@ -962,8 +816,8 @@
962
816
  }
963
817
 
964
818
  // Formation energy color bar helpers
965
- const e_form_range = $derived.by((): [number, number] => {
966
- const min_fe = plot_entries.length ? energy_range.min : -1
819
+ const e_form_range = $derived.by((): Vec2 => {
820
+ const min_fe = plot_entries.length > 0 ? energy_range.min : -1
967
821
  return [min_fe, 0]
968
822
  })
969
823
 
@@ -980,59 +834,16 @@
980
834
 
981
835
  function draw_data_points(): void {
982
836
  if (!ctx || sorted_points_cache.length === 0) return
983
-
984
- for (const { entry, projected } of sorted_points_cache) {
985
- const is_stable = helpers.entry_is_stable(entry)
986
- const is_entry_highlighted = is_highlighted(entry)
987
- const color = get_point_color(entry)
988
- const size = (entry.size || (is_stable ? 6 : 4)) * canvas_dims.scale
989
- const marker = entry.marker || `circle`
990
-
991
- // Shadow
992
- const shadow_offset = Math.abs(entry.z) * 0.1 * canvas_dims.scale
993
- ctx.fillStyle = `rgba(0, 0, 0, 0.2)`
994
- const shadow_path = helpers.create_marker_path(size * 0.8, marker)
995
- ctx.save()
996
- ctx.translate(projected.x + shadow_offset, projected.y + shadow_offset)
997
- ctx.fill(shadow_path)
998
- ctx.restore()
999
-
1000
- // Highlights
1001
- if (selected_entry && entry.entry_id === selected_entry.entry_id) {
1002
- helpers.draw_selection_highlight(
1003
- ctx,
1004
- projected,
1005
- size,
1006
- canvas_dims.scale,
1007
- pulse.time,
1008
- pulse_opacity,
1009
- )
1010
- }
1011
- if (is_entry_highlighted) {
1012
- helpers.draw_highlight_effect(
1013
- ctx,
1014
- projected,
1015
- size,
1016
- canvas_dims.scale,
1017
- pulse.time,
1018
- merged_highlight_style,
1019
- )
1020
- }
1021
-
1022
- // Main point with marker symbol
1023
- ctx.fillStyle =
1024
- is_entry_highlighted && merged_highlight_style.effect === `color`
1025
- ? merged_highlight_style.color
1026
- : color
1027
- ctx.strokeStyle = is_stable ? `#ffffff` : `#000000`
1028
- ctx.lineWidth = 0.5 * canvas_dims.scale
1029
- const marker_path = helpers.create_marker_path(size, marker)
1030
- ctx.save()
1031
- ctx.translate(projected.x, projected.y)
1032
- ctx.fill(marker_path)
1033
- ctx.stroke(marker_path)
1034
- ctx.restore()
1035
- }
837
+ helpers.draw_hull_points(ctx, sorted_points_cache, {
838
+ scale: canvas_dims.scale,
839
+ shadow_factor: 0.1,
840
+ selected_entry,
841
+ is_highlighted,
842
+ get_point_color,
843
+ highlight_style: merged_highlight_style,
844
+ pulse_time: pulse.time,
845
+ pulse_opacity,
846
+ })
1036
847
  }
1037
848
 
1038
849
  const hull_label_font_size = 12
@@ -1216,180 +1027,11 @@
1216
1027
  draw_element_labels()
1217
1028
  }
1218
1029
 
1219
- function handle_mouse_down(event: MouseEvent) {
1220
- is_dragging = true
1221
- drag_started = false
1222
- hover_data = null
1223
- on_point_hover?.(null)
1224
- last_mouse = { x: event.clientX, y: event.clientY }
1225
- }
1226
-
1227
- const handle_mouse_move = (event: MouseEvent) => {
1228
- if (!is_dragging) return
1229
- const [dx, dy] = [event.clientX - last_mouse.x, event.clientY - last_mouse.y]
1230
-
1231
- // Mark as drag if any movement occurred
1232
- if (dx !== 0 || dy !== 0) drag_started = true
1233
-
1234
- // With Cmd/Ctrl held: pan the view instead of rotating
1235
- if (event.metaKey || event.ctrlKey) {
1236
- camera.center_x += dx
1237
- camera.center_y += dy
1238
- } else {
1239
- // Horizontal drag -> azimuth rotation around z-axis
1240
- camera.azimuth += dx * 0.3 // Positive dx (drag right) rotates clockwise
1241
-
1242
- // Vertical drag -> elevation angle (full range)
1243
- camera.elevation -= dy * 0.3 // Positive dy (drag down) tilts view down
1244
- }
1245
-
1246
- last_mouse = { x: event.clientX, y: event.clientY }
1247
- }
1248
-
1249
- const handle_wheel = (event: WheelEvent) => {
1250
- event.preventDefault()
1251
- camera.zoom = Math.max(
1252
- 0.5,
1253
- Math.min(10, camera.zoom * (event.deltaY > 0 ? 0.98 : 1.02)),
1254
- )
1255
- }
1256
-
1257
- const handle_hover = (event: MouseEvent) => {
1258
- if (is_dragging) return
1259
- const entry = find_entry_at_mouse(event)
1260
- hover_data = entry
1261
- ? { entry, position: { x: event.clientX, y: event.clientY } }
1262
- : null
1263
- on_point_hover?.(hover_data)
1264
- }
1265
-
1266
- const find_entry_at_mouse = (event: MouseEvent): ConvexHullEntry | null =>
1267
- helpers.find_hull_entry_at_mouse(
1268
- canvas,
1269
- event,
1270
- visible_entries,
1271
- (x: number, y: number, z: number) => {
1272
- const pt = project_3d_point(x, y, z)
1273
- return { x: pt.x, y: pt.y }
1274
- },
1275
- )
1276
-
1277
- const handle_click = (event: MouseEvent) => {
1278
- event.stopPropagation()
1279
- // Check if this was a drag operation (any mouse movement during drag)
1280
- const was_drag = drag_started
1281
- drag_started = false // Reset for next interaction
1282
- if (was_drag) return // Don't trigger click if this was a drag
1283
-
1284
- const entry = find_entry_at_mouse(event)
1285
- if (!entry) {
1286
- if (modal_open) close_structure_popup()
1287
- return
1288
- }
1289
-
1290
- on_point_click?.(entry)
1291
-
1292
- if (enable_click_selection) {
1293
- selected_entry = entry
1294
- if (enable_structure_preview) {
1295
- const structure = extract_structure_from_entry(entry)
1296
- if (structure) {
1297
- selected_structure = structure
1298
- modal_place_right = helpers.calculate_modal_side(wrapper)
1299
- modal_open = true
1300
- }
1301
- }
1302
- }
1303
- }
1304
-
1305
- function close_structure_popup() {
1306
- modal_open = false
1307
- selected_structure = null
1308
- selected_entry = null
1309
- }
1310
-
1311
- const handle_double_click = (event: MouseEvent) => {
1312
- const entry = find_entry_at_mouse(event)
1313
- if (entry) {
1314
- copy_entry_data(entry, {
1315
- x: event.clientX,
1316
- y: event.clientY,
1317
- })
1318
- }
1319
- }
1320
-
1321
- function render_once() {
1322
- if (!frame_id) {
1323
- frame_id = requestAnimationFrame(() => {
1324
- render_frame()
1325
- frame_id = 0
1326
- })
1327
- }
1328
- }
1329
-
1330
- function update_canvas_size() {
1331
- if (!canvas) return
1332
- const dpr = globalThis.devicePixelRatio || 1
1333
- const container = canvas.parentElement
1334
- const rect = container?.getBoundingClientRect()
1335
- const [w, h] = rect ? [rect.width, rect.height] : [400, 400]
1336
-
1337
- // Only update canvas dimensions if they actually changed
1338
- // (assigning canvas.width/height clears the canvas even if values are the same)
1339
- const new_width = Math.max(0, Math.round(w * dpr))
1340
- const new_height = Math.max(0, Math.round(h * dpr))
1341
- if (!ctx || canvas.width !== new_width || canvas.height !== new_height) {
1342
- canvas.width = new_width
1343
- canvas.height = new_height
1344
- ctx = canvas.getContext(`2d`)
1345
- if (ctx) {
1346
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
1347
- ctx.imageSmoothingEnabled = true
1348
- ctx.imageSmoothingQuality = `high`
1349
- }
1350
- }
1351
- canvas_dims = { width: w, height: h, scale: Math.min(w, h) / 600 }
1352
- render_once()
1353
- }
1354
-
1355
1030
  // Reactive dark mode detection for canvas text color
1356
1031
  let dark_mode = $state(is_dark_mode())
1357
1032
  $effect(() => watch_dark_mode((dark) => dark_mode = dark))
1358
1033
  const text_color = $derived(helpers.get_canvas_text_color(dark_mode))
1359
1034
 
1360
- $effect(() => {
1361
- if (!canvas) return
1362
-
1363
- // Initial setup
1364
- update_canvas_size()
1365
-
1366
- // Watch for resize events - only update canvas, don't reset camera
1367
- const resize_observer = new ResizeObserver(update_canvas_size)
1368
-
1369
- const container = canvas.parentElement
1370
- if (container) {
1371
- resize_observer.observe(container)
1372
- }
1373
-
1374
- return () => { // Cleanup on unmount
1375
- if (frame_id) cancelAnimationFrame(frame_id)
1376
- resize_observer.disconnect()
1377
- }
1378
- })
1379
-
1380
- // Fullscreen handling with camera reset
1381
- let was_fullscreen = $state(fullscreen)
1382
- $effect(() => {
1383
- setup_fullscreen_effect(fullscreen, wrapper, (entering_fullscreen) => {
1384
- if (entering_fullscreen !== was_fullscreen) {
1385
- camera.center_x = 0
1386
- camera.center_y = -50
1387
- was_fullscreen = entering_fullscreen
1388
- }
1389
- })
1390
- set_fullscreen_bg(wrapper, fullscreen, `--hull-3d-bg-fullscreen`)
1391
- })
1392
-
1393
1035
  // Performance: Cache canvas dimensions and formation energy range
1394
1036
  let canvas_dims = $state({ width: 600, height: 600, scale: 1 })
1395
1037
  const energy_range = $derived.by(() => {
@@ -1404,56 +1046,30 @@
1404
1046
  return { min, max, center: (min + max) / 2, z_scale }
1405
1047
  })
1406
1048
 
1407
- // Performance: Pre-compute and cache all point projections + depth sorting
1408
- const sorted_points_cache = $derived.by(() => {
1409
- if (!canvas || visible_entries.length === 0) return []
1410
- return visible_entries
1411
- .map((entry) => ({
1412
- entry,
1413
- projected: project_3d_point(entry.x, entry.y, entry.z),
1414
- }))
1415
- .sort((left, right) => left.projected.depth - right.projected.depth)
1416
- })
1417
-
1418
- let style = $derived(
1419
- `--hull-stable-color:${merged_config.colors?.stable || `#0072B2`};
1420
- --hull-unstable-color:${merged_config.colors?.unstable || `#E69F00`};
1421
- --hull-edge-color:${merged_config.colors?.edge || `var(--text-color, #212121)`};
1422
- --hull-text-color:${
1423
- merged_config.colors?.annotation || `var(--text-color, #212121)`
1424
- }`,
1425
- )
1049
+ let style = $derived(helpers.hull_style_css(merged_config.colors))
1426
1050
  </script>
1427
1051
 
1428
1052
  <svelte:document
1429
1053
  onfullscreenchange={() => {
1430
- fullscreen = Boolean(document.fullscreenElement)
1054
+ // tie fullscreen state to this component's own wrapper, not any fullscreen element
1055
+ fullscreen = document.fullscreenElement === wrapper
1431
1056
  }}
1432
- onmousemove={handle_mouse_move}
1433
- onmouseup={() => ([is_dragging, drag_started] = [false, false])}
1057
+ onmousemove={interactions.handle_mouse_move}
1058
+ onmouseup={interactions.handle_mouse_up}
1434
1059
  />
1435
1060
 
1436
1061
  <div
1437
1062
  {...rest}
1438
1063
  class="convex-hull-3d {rest.class ?? ``}"
1439
- class:dragover={drag_over}
1064
+ class:dragover={interactions.drag_over}
1440
1065
  style={`${style}; ${rest.style ?? ``}`}
1441
1066
  data-has-selection={selected_entry !== null}
1442
- data-has-hover={hover_data !== null}
1443
- data-is-dragging={is_dragging}
1067
+ data-has-hover={interactions.hover_data !== null}
1068
+ data-is-dragging={interactions.is_dragging}
1444
1069
  bind:this={wrapper}
1445
1070
  role="application"
1446
1071
  tabindex="-1"
1447
- onkeydown={handle_keydown}
1448
- ondrop={handle_file_drop}
1449
- ondragover={(event) => {
1450
- event.preventDefault()
1451
- drag_over = true
1452
- }}
1453
- ondragleave={(event) => {
1454
- event.preventDefault()
1455
- drag_over = false
1456
- }}
1072
+ {...interactions.wrapper_handlers}
1457
1073
  aria-label="Ternary convex hull visualization"
1458
1074
  >
1459
1075
  {@render children?.({
@@ -1469,12 +1085,7 @@
1469
1085
  bind:this={canvas}
1470
1086
  tabindex="0"
1471
1087
  aria-label={merged_controls.title || phase_stats?.chemical_system || `3D Convex Hull`}
1472
- onmousedown={handle_mouse_down}
1473
- onmousemove={handle_hover}
1474
- onclick={handle_click}
1475
- onkeydown={handle_keydown}
1476
- ondblclick={handle_double_click}
1477
- onwheel={handle_wheel}
1088
+ {...interactions.canvas_handlers}
1478
1089
  ></canvas>
1479
1090
 
1480
1091
  {#if entries.length === 0}
@@ -1486,14 +1097,9 @@
1486
1097
 
1487
1098
  <!-- Formation Energy Color Bar (bottom-left corner) -->
1488
1099
  {#if color_mode === `energy` && plot_entries.length > 0}
1489
- {@const hull_distances = plot_entries
1490
- .map((entry) => entry.e_above_hull)
1491
- .filter((value): value is number => typeof value === `number`)}
1492
- {@const min_energy = hull_distances.length > 0 ? Math.min(...hull_distances) : 0}
1493
- {@const max_energy = hull_distances.length > 0 ? Math.max(...hull_distances, 0.1) : 0.1}
1494
1100
  <ColorBar
1495
1101
  title="Energy above hull (eV/atom)"
1496
- range={[min_energy, max_energy]}
1102
+ range={helpers.hull_distance_range(plot_entries)}
1497
1103
  {color_scale}
1498
1104
  wrapper_style="position: absolute; bottom: 16px; left: 1em; width: 200px;"
1499
1105
  bar_style="height: 12px;"
@@ -1517,82 +1123,46 @@
1517
1123
  />
1518
1124
  {/if}
1519
1125
 
1520
- <!-- Control buttons (top-right corner) -->
1521
- {#if controls_config.mode !== `never`}
1522
- <section class="control-buttons {controls_config.class}">
1523
- {#if controls_config.visible(`reset`)}
1524
- <button
1525
- type="button"
1526
- onclick={reset_all}
1527
- title="Reset view and settings"
1528
- class="reset-camera-btn"
1529
- >
1530
- <Icon icon="Reset" />
1531
- </button>
1532
- {/if}
1533
-
1534
- {#if enable_info_pane && phase_stats && controls_config.visible(`info-pane`)}
1535
- <ConvexHullInfoPane
1536
- bind:pane_open={info_pane_open}
1537
- {phase_stats}
1538
- {stable_entries}
1539
- {unstable_entries}
1540
- {show_stable}
1541
- {show_unstable}
1542
- {max_hull_dist_show_phases}
1543
- {max_hull_dist_show_labels}
1544
- {label_threshold}
1545
- toggle_props={{ class: `info-btn` }}
1546
- />
1547
- {/if}
1548
-
1549
- {#if enable_fullscreen && controls_config.visible(`fullscreen`)}
1550
- <button
1551
- type="button"
1552
- onclick={() => toggle_fullscreen(wrapper)}
1553
- title="{fullscreen ? `Exit` : `Enter`} fullscreen"
1554
- class="fullscreen-btn"
1555
- >
1556
- <Icon icon="{fullscreen ? `Exit` : ``}Fullscreen" />
1557
- </button>
1558
- {/if}
1559
-
1560
- <!-- Legend controls pane -->
1561
- {#if controls_config.visible(`controls`)}
1562
- <ConvexHullControls
1563
- bind:controls_open={legend_pane_open}
1564
- bind:color_mode
1565
- bind:color_scale
1566
- bind:show_stable
1567
- bind:show_unstable
1568
- bind:show_stable_labels
1569
- bind:show_unstable_labels
1570
- bind:max_hull_dist_show_phases
1571
- bind:max_hull_dist_show_labels
1572
- {max_hull_dist_in_data}
1573
- {stable_entries}
1574
- {unstable_entries}
1575
- {camera}
1576
- {merged_controls}
1577
- toggle_props={{ class: `legend-controls-btn` }}
1578
- {show_hull_faces}
1579
- on_hull_faces_change={(value: boolean) => show_hull_faces = value}
1580
- {hull_face_color}
1581
- on_hull_face_color_change={(value: string) => hull_face_color = value}
1582
- {hull_face_opacity}
1583
- on_hull_face_opacity_change={(value: number) => hull_face_opacity = value}
1584
- {hull_face_color_mode}
1585
- on_hull_face_color_mode_change={(value: HullFaceColorMode) =>
1586
- hull_face_color_mode = value}
1587
- bind:energy_source_mode
1588
- {has_precomputed_e_form}
1589
- {can_compute_e_form}
1590
- {has_precomputed_hull}
1591
- {can_compute_hull}
1592
- />
1593
- {/if}
1594
- </section>
1595
- {/if}
1126
+ <!-- Toolbar + tooltip/copy-feedback/drag/structure-popup chrome -->
1127
+ <ConvexHullChrome
1128
+ {interactions}
1129
+ {hull_data}
1130
+ {controls_config}
1131
+ {reset_all}
1132
+ reset_title="Reset view and settings"
1133
+ {enable_info_pane}
1134
+ {phase_stats}
1135
+ {label_threshold}
1136
+ {fullscreen}
1137
+ {fullscreen_toggle}
1138
+ {wrapper}
1139
+ {camera}
1140
+ {merged_controls}
1141
+ {stable_entries}
1142
+ {unstable_entries}
1143
+ {get_point_color}
1144
+ {merged_highlight_style}
1145
+ {is_highlighted}
1146
+ {tooltip}
1147
+ {selected_entry}
1148
+ bind:show_hull_faces
1149
+ bind:hull_face_color
1150
+ bind:hull_face_opacity
1151
+ bind:hull_face_color_mode
1152
+ bind:info_pane_open
1153
+ bind:controls_open
1154
+ bind:color_mode
1155
+ bind:color_scale
1156
+ bind:show_stable
1157
+ bind:show_unstable
1158
+ {entry_category}
1159
+ bind:hidden_categories
1160
+ bind:show_stable_labels
1161
+ bind:show_unstable_labels
1162
+ bind:max_hull_dist_show_phases
1163
+ bind:max_hull_dist_show_labels
1164
+ bind:energy_source_mode
1165
+ />
1596
1166
 
1597
1167
  <!-- Orientation gizmo (configurable placement, default top-right) -->
1598
1168
  {#if gizmo && typeof WebGLRenderingContext !== `undefined`}
@@ -1633,7 +1203,7 @@
1633
1203
  (gas_analysis.has_gas_dependent_elements && merged_gas_config)}
1634
1204
  <div class="right-controls">
1635
1205
  {#if has_temp_data && temperature !== undefined}
1636
- <TemperatureSlider {available_temperatures} bind:temperature />
1206
+ <TemperatureSlider available_temperatures={hull_data.available_temperatures} bind:temperature />
1637
1207
  {/if}
1638
1208
  {#if gas_analysis.has_gas_dependent_elements && merged_gas_config}
1639
1209
  <GasPressureControls
@@ -1645,45 +1215,6 @@
1645
1215
  </div>
1646
1216
  {/if}
1647
1217
 
1648
- <!-- Hover tooltip -->
1649
- {#if hover_data}
1650
- {@const { entry, position } = hover_data}
1651
- {@const entry_highlight = is_highlighted(entry) ? merged_highlight_style : undefined}
1652
- {@const tooltip_style =
1653
- `z-index: ${CONVEX_HULL_STYLE.z_index.tooltip}; backdrop-filter: blur(4px);
1654
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);`}
1655
- <PlotTooltip
1656
- x={position.x}
1657
- y={position.y}
1658
- offset={{ x: 10, y: -10 }}
1659
- bg_color={get_point_color(entry)}
1660
- fixed
1661
- style={tooltip_style}
1662
- >
1663
- <ConvexHullTooltip
1664
- {entry}
1665
- {polymorph_stats_map}
1666
- highlight_style={entry_highlight}
1667
- {tooltip}
1668
- />
1669
- </PlotTooltip>
1670
- {/if}
1671
-
1672
- <ClickFeedback bind:visible={copy_feedback.visible} position={copy_feedback.position} />
1673
- <DragOverlay visible={drag_over} />
1674
-
1675
- {#if modal_open && selected_structure}
1676
- <StructurePopup
1677
- structure={selected_structure}
1678
- place_right={modal_place_right}
1679
- stats={{
1680
- id: selected_entry?.entry_id,
1681
- e_above_hull: selected_entry?.e_above_hull,
1682
- e_form: selected_entry?.e_form_per_atom,
1683
- }}
1684
- onclose={close_structure_popup}
1685
- />
1686
- {/if}
1687
1218
  </div>
1688
1219
 
1689
1220
  <style>
@@ -1763,39 +1294,4 @@
1763
1294
  opacity: 1;
1764
1295
  pointer-events: auto;
1765
1296
  }
1766
- .control-buttons {
1767
- position: absolute;
1768
- top: 1ex;
1769
- right: 1ex;
1770
- display: flex;
1771
- gap: 8px;
1772
- transition: opacity 0.2s ease-in-out;
1773
- }
1774
- .control-buttons.hover-visible {
1775
- opacity: 0;
1776
- pointer-events: none;
1777
- }
1778
- .convex-hull-3d:hover .control-buttons.hover-visible,
1779
- .convex-hull-3d:focus-within .control-buttons.hover-visible {
1780
- opacity: 1;
1781
- pointer-events: auto;
1782
- }
1783
- .control-buttons.always-visible {
1784
- opacity: 1;
1785
- pointer-events: auto;
1786
- }
1787
- .control-buttons :global(button) {
1788
- background: transparent;
1789
- border: none;
1790
- padding: 4px;
1791
- cursor: pointer;
1792
- border-radius: 3px;
1793
- color: var(--text-color, currentColor);
1794
- transition: background-color 0.2s;
1795
- display: flex;
1796
- font-size: clamp(0.85em, 2cqmin, 1.3em);
1797
- }
1798
- .control-buttons :global(button):hover {
1799
- background-color: color-mix(in srgb, currentColor 8%, transparent);
1800
- }
1801
1297
  </style>