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
@@ -9,14 +9,8 @@
9
9
  } from '../colors'
10
10
  import { normalize_show_controls } from '../controls'
11
11
  import { sanitize_html } from '../sanitize'
12
- import { ClickFeedback, DragOverlay, Spinner } from '../feedback'
13
- import Icon from '../Icon.svelte'
14
- import {
15
- set_fullscreen_bg,
16
- setup_fullscreen_effect,
17
- toggle_fullscreen,
18
- } from '../layout'
19
- import { ColorBar, PlotTooltip } from '../plot'
12
+ import { Spinner } from '../feedback'
13
+ import { ColorBar } from '../plot'
20
14
  import { create_pulse_animation } from '../effects.svelte'
21
15
  import { DEFAULTS } from '../settings'
22
16
  import type { AnyStructure } from '../structure'
@@ -25,38 +19,41 @@
25
19
  compute_4d_coords,
26
20
  TETRAHEDRON_VERTICES,
27
21
  } from './barycentric-coords'
28
- import ConvexHullControls from './ConvexHullControls.svelte'
29
- import ConvexHullInfoPane from './ConvexHullInfoPane.svelte'
30
- import ConvexHullTooltip from './ConvexHullTooltip.svelte'
22
+ import { create_canvas_interactions } from './canvas-interactions.svelte'
23
+ import ConvexHullChrome from './ConvexHullChrome.svelte'
31
24
  import GasPressureControls from './GasPressureControls.svelte'
32
25
  import * as helpers from './helpers'
26
+ import { create_hull_data_pipeline } from './hull-state.svelte'
33
27
  import type { BaseConvexHullProps, Hull3DProps } from './index'
34
28
  import { CONVEX_HULL_STYLE, default_controls, default_hull_config } from './index'
35
- import StructurePopup from './StructurePopup.svelte'
36
29
  import TemperatureSlider from './TemperatureSlider.svelte'
37
30
  import type { Point4D } from './thermodynamics'
38
31
  import * as thermo from './thermodynamics'
39
32
  import type {
40
33
  ConvexHullEntry,
41
34
  HighlightStyle,
42
- HoverData3D,
43
35
  HullFaceColorMode,
36
+ PhaseData,
44
37
  } from './types'
38
+ import { MAGNETIC_ORDERING_CATEGORY } from './types'
45
39
  import { compute_hull_stability } from './helpers'
46
40
 
47
41
  let {
48
42
  entries = [],
49
43
  controls = {},
50
44
  config = {},
45
+ show_controls,
51
46
  on_point_click,
52
47
  on_point_hover,
53
48
  fullscreen = $bindable(DEFAULTS.convex_hull.quaternary.fullscreen),
54
- enable_fullscreen = true,
49
+ fullscreen_toggle = true,
55
50
  enable_info_pane = true,
56
51
  wrapper = $bindable(),
57
52
  label_threshold = 50,
58
53
  show_stable = $bindable(DEFAULTS.convex_hull.quaternary.show_stable),
59
54
  show_unstable = $bindable(DEFAULTS.convex_hull.quaternary.show_unstable),
55
+ entry_category = MAGNETIC_ORDERING_CATEGORY,
56
+ hidden_categories = $bindable([]),
60
57
  show_hull_faces = $bindable(DEFAULTS.convex_hull.quaternary.show_hull_faces),
61
58
  hull_face_opacity = $bindable(DEFAULTS.convex_hull.quaternary.hull_face_opacity),
62
59
  hull_face_color_mode = $bindable(
@@ -68,7 +65,7 @@
68
65
  DEFAULTS.convex_hull.quaternary.color_scale as D3InterpolateName,
69
66
  ),
70
67
  info_pane_open = $bindable(DEFAULTS.convex_hull.quaternary.info_pane_open),
71
- legend_pane_open = $bindable(DEFAULTS.convex_hull.quaternary.legend_pane_open),
68
+ controls_open = $bindable(DEFAULTS.convex_hull.quaternary.legend_pane_open),
72
69
  max_hull_dist_show_phases = $bindable(
73
70
  DEFAULTS.convex_hull.quaternary.max_hull_dist_show_phases,
74
71
  ),
@@ -104,7 +101,7 @@
104
101
  } = $props()
105
102
 
106
103
  const merged_controls = $derived({ ...default_controls, ...controls })
107
- const controls_config = $derived(normalize_show_controls(merged_controls.show))
104
+ const controls_config = $derived(normalize_show_controls(show_controls))
108
105
  const merged_config = $derived({
109
106
  ...default_hull_config,
110
107
  ...config,
@@ -117,87 +114,36 @@
117
114
  $effect(() => watch_dark_mode((dark) => dark_mode = dark))
118
115
  const text_color = $derived(helpers.get_canvas_text_color(dark_mode))
119
116
 
120
- // Temperature-dependent free energy support
121
- const { has_temp_data, available_temperatures } = $derived(
122
- helpers.analyze_temperature_data(entries),
123
- )
124
-
125
- // Initialize or reset temperature when it's undefined or no longer valid
126
- $effect(() => {
127
- if (
128
- has_temp_data &&
129
- available_temperatures.length > 0 &&
130
- (temperature === undefined || !available_temperatures.includes(temperature))
131
- ) temperature = available_temperatures[0]
132
- })
133
-
134
- // Filter entries by temperature when in temperature mode
135
- const temp_filtered_entries = $derived(
136
- has_temp_data && temperature !== undefined
137
- ? helpers.filter_entries_at_temperature(entries, temperature, {
138
- interpolate: interpolate_temperature,
139
- max_interpolation_gap,
140
- })
141
- : entries,
142
- )
143
-
144
- // Gas-dependent chemical potential support (corrections based on T, P)
145
- const {
146
- entries: gas_corrected_entries,
147
- analysis: gas_analysis,
148
- merged_config: merged_gas_config,
149
- } = $derived(
150
- helpers.get_gas_corrected_entries(
151
- temp_filtered_entries,
152
- gas_config,
153
- gas_pressures,
154
- temperature ?? helpers.DEFAULT_GAS_TEMP,
155
- ),
156
- )
157
-
158
- let { // Compute energy mode information
159
- has_precomputed_e_form,
160
- has_precomputed_hull,
161
- can_compute_e_form,
162
- can_compute_hull,
163
- energy_mode,
164
- unary_refs,
165
- } = $derived(
166
- helpers.compute_energy_mode_info(
167
- gas_corrected_entries,
168
- thermo.find_lowest_energy_unary_refs,
169
- energy_source_mode,
170
- ),
171
- )
172
-
173
- const effective_entries = $derived(
174
- helpers.get_effective_entries(
175
- gas_corrected_entries,
176
- energy_mode,
177
- unary_refs,
178
- thermo.compute_e_form_per_atom,
179
- ),
180
- )
181
-
182
- // Process convex hull data with unified PhaseData interface using effective entries
183
- const pd_data = $derived(thermo.process_hull_entries(effective_entries))
184
-
185
- // Pre-compute polymorph stats once for O(1) tooltip lookups
186
- const polymorph_stats_map = $derived(
187
- helpers.compute_all_polymorph_stats(effective_entries),
188
- )
189
-
190
- const elements = $derived.by(() => {
191
- if (pd_data.elements.length > 4) {
192
- console.error(
193
- `ConvexHull4D: Dataset contains ${pd_data.elements.length} elements, but quaternary diagrams require exactly 4. Found: [${
194
- pd_data.elements.join(`, `)
195
- }]`,
196
- )
197
- return []
198
- }
199
- return pd_data.elements
117
+ // Shared reactive data pipeline (temperature → gas → energy mode → hull data → threshold)
118
+ // Explicit generic breaks the circular type inference through the all_enriched_entries thunk
119
+ const hull_data = create_hull_data_pipeline<ConvexHullEntry>({
120
+ dim: 4,
121
+ entries: () => entries,
122
+ temperature: () => temperature,
123
+ interpolate_temperature: () => interpolate_temperature,
124
+ max_interpolation_gap: () => max_interpolation_gap,
125
+ gas_config: () => gas_config,
126
+ gas_pressures: () => gas_pressures,
127
+ energy_source_mode: () => energy_source_mode,
128
+ all_enriched_entries: () => all_enriched_entries,
129
+ max_hull_dist_show_phases: () => max_hull_dist_show_phases,
130
+ show_stable: () => show_stable,
131
+ show_unstable: () => show_unstable,
132
+ entry_category: () => entry_category,
133
+ hidden_categories: () => hidden_categories,
134
+ // Always include stable entries and elemental reference points
135
+ keep_plot_entry: (entry, max_dist) =>
136
+ helpers.entry_is_stable(entry) ||
137
+ (typeof entry.e_above_hull === `number` && entry.e_above_hull <= max_dist),
138
+ set_temperature: (next_temp) => temperature = next_temp,
139
+ set_max_hull_dist_show_phases: (value) => max_hull_dist_show_phases = value,
140
+ set_stable_entries: (value) => stable_entries = value,
141
+ set_unstable_entries: (value) => unstable_entries = value,
200
142
  })
143
+ const merged_gas_config = $derived(hull_data.merged_gas_config)
144
+ const pd_data = $derived(hull_data.pd_data)
145
+ const elements = $derived(hull_data.elements)
146
+ const plot_entries = $derived(hull_data.plot_entries)
201
147
 
202
148
  // Compute 4D hull for visualization (always compute when we have formation energies)
203
149
  const hull_4d = $derived.by(() => {
@@ -234,11 +180,12 @@
234
180
  })
235
181
 
236
182
  // Enrich coords with e_above_hull (before filtering)
237
- const all_enriched_entries = $derived.by(() => {
183
+ // Explicit return type breaks circular type inference with the hull_data pipeline
184
+ const all_enriched_entries = $derived.by((): ConvexHullEntry[] => {
238
185
  if (elements.length !== 4) return []
239
186
  try {
240
187
  const coords = compute_4d_coords(pd_data.entries, elements)
241
- if (energy_mode !== `on-the-fly` || hull_4d.length === 0) return coords
188
+ if (hull_data.energy_mode !== `on-the-fly` || hull_4d.length === 0) return coords
242
189
 
243
190
  // Build 4D points, tracking original indices for mapping hull distances back
244
191
  const valid = coords.flatMap((entry, idx) => {
@@ -267,51 +214,8 @@
267
214
  }
268
215
  })
269
216
 
270
- // Auto threshold: show all for few entries, use default for many, interpolate between
271
- const max_hull_dist_in_data = $derived(
272
- helpers.calc_max_hull_dist_in_data(all_enriched_entries),
273
- )
274
- const auto_default_threshold = $derived(helpers.compute_auto_hull_dist_threshold(
275
- all_enriched_entries.length,
276
- max_hull_dist_in_data,
277
- DEFAULTS.convex_hull.quaternary.max_hull_dist_show_phases,
278
- ))
279
-
280
- const next_auto_threshold = helpers.auto_threshold_reset(
281
- DEFAULTS.convex_hull.quaternary.max_hull_dist_show_phases,
282
- )
283
- $effect(() => {
284
- max_hull_dist_show_phases = next_auto_threshold(
285
- entries,
286
- max_hull_dist_show_phases,
287
- auto_default_threshold,
288
- ) ?? max_hull_dist_show_phases
289
- })
290
-
291
- // Filter by threshold; visibility is a view predicate, not entry state.
292
- const plot_entries = $derived(
293
- all_enriched_entries.filter((entry) => {
294
- // Always include stable entries and elemental reference points
295
- if (helpers.entry_is_stable(entry)) return true
296
- return typeof entry.e_above_hull === `number` &&
297
- entry.e_above_hull <= max_hull_dist_show_phases
298
- }),
299
- )
300
- const visible_entries = $derived(helpers.visible_entries(
301
- plot_entries,
302
- show_stable,
303
- show_unstable,
304
- ))
305
-
306
- // Stable and unstable entries exposed as bindable props
307
- $effect(() => {
308
- stable_entries = plot_entries.filter(helpers.entry_is_stable)
309
- unstable_entries = plot_entries.filter(helpers.entry_is_unstable)
310
- })
311
-
312
217
  let canvas: HTMLCanvasElement | undefined = undefined
313
218
  let ctx: CanvasRenderingContext2D | null = null
314
- let frame_id = 0 // Performance optimization
315
219
 
316
220
  // Camera state - following Materials Project's 3D camera setup
317
221
  let camera = $state({
@@ -322,43 +226,60 @@
322
226
  center_y: 20, // Slight offset to avoid legend overlap
323
227
  })
324
228
 
325
- // Interaction state
326
- let is_dragging = $state(false)
327
- let drag_started = $state(false)
328
- let last_mouse = $state({ x: 0, y: 0 })
329
- let hover_data = $state<HoverData3D<ConvexHullEntry> | null>(null)
330
- let copy_feedback = $state({ visible: false, position: { x: 0, y: 0 } })
331
-
332
- // Drag and drop state
333
- let drag_over = $state(false)
334
-
335
- // Structure popup state
336
- let modal_open = $state(false)
337
- let selected_structure = $state<AnyStructure | null>(null)
338
- let modal_place_right = $state(true)
339
- $effect(() => {
340
- const current_selection = helpers.current_entry(selected_entry, plot_entries)
341
- const stale_selection = selected_entry && !current_selection
342
- if (stale_selection) selected_entry = null
343
- else if (current_selection && current_selection !== selected_entry) {
344
- selected_entry = current_selection
345
- }
346
- const current_hover = helpers.current_entry(hover_data?.entry, plot_entries)
347
- if (hover_data?.entry && !current_hover) {
348
- hover_data = null
349
- on_point_hover?.(null)
350
- } else if (hover_data && current_hover && current_hover !== hover_data.entry) {
351
- hover_data = { ...hover_data, entry: current_hover }
352
- }
353
- if (modal_open) {
354
- const structure = current_selection && extract_structure_from_entry(current_selection)
355
- if (structure) selected_structure = structure
356
- else {
357
- modal_open = false
358
- selected_structure = null
229
+ // Shared canvas-interaction scaffold (mouse/keyboard handlers, hover/drag/popup
230
+ // state, canvas sizing, render scheduler). Rotation math + keydown actions stay local.
231
+ const interactions = create_canvas_interactions({
232
+ wheel_clamp: [1.0, 15],
233
+ fullscreen_bg_var: `--hull-4d-bg-fullscreen`,
234
+ canvas: () => canvas,
235
+ wrapper: () => wrapper,
236
+ ctx: () => ctx,
237
+ set_ctx: (context) => ctx = context,
238
+ set_canvas_dims: (dims) => canvas_dims = dims,
239
+ visible_entries: () => hull_data.visible_entries,
240
+ plot_entries: () => plot_entries,
241
+ selected_entry: () => selected_entry,
242
+ set_selected_entry: (entry) => selected_entry = entry,
243
+ fullscreen: () => fullscreen,
244
+ enable_click_selection: () => enable_click_selection,
245
+ enable_structure_preview: () => enable_structure_preview,
246
+ on_point_click: () => on_point_click,
247
+ on_point_hover: () => on_point_hover,
248
+ on_file_drop: () => on_file_drop,
249
+ entry_category: () => entry_category,
250
+ zoom: () => camera.zoom,
251
+ set_zoom: (zoom) => camera.zoom = zoom,
252
+ project_point: project_3d_point,
253
+ extract_structure: extract_structure_from_entry,
254
+ render_frame,
255
+ on_drag: (dx, dy, panning) => {
256
+ if (panning) {
257
+ camera.center_x += dx
258
+ camera.center_y += dy
259
+ } else {
260
+ camera.rotation_y += dx * 0.005
261
+ camera.rotation_x = Math.max(
262
+ -Math.PI / 3,
263
+ Math.min(Math.PI / 3, camera.rotation_x - dy * 0.005),
264
+ )
359
265
  }
360
- }
266
+ },
267
+ // Reset pan center when entering/exiting fullscreen
268
+ on_fullscreen_change: () => {
269
+ camera.center_x = 0
270
+ camera.center_y = 20
271
+ },
272
+ actions: () => ({
273
+ r: reset_camera,
274
+ b: () => color_mode = color_mode === `stability` ? `energy` : `stability`,
275
+ s: () => show_stable = !show_stable,
276
+ u: () => show_unstable = !show_unstable,
277
+ h: () => show_hull_faces = !show_hull_faces,
278
+ l: () => show_stable_labels = !show_stable_labels,
279
+ }),
361
280
  })
281
+ const { render_once } = interactions
282
+ const sorted_points_cache = $derived(interactions.sorted_points_cache)
362
283
 
363
284
  // Hull face color (customizable via controls)
364
285
  let hull_face_color = $state(`#4caf50`)
@@ -382,32 +303,27 @@
382
303
  // Re-render when important state changes
383
304
  $effect(() => {
384
305
  // oxfmt-ignore
385
- void [show_hull_faces, color_mode, color_scale, camera.rotation_x, camera.rotation_y, camera.zoom, camera.center_x, camera.center_y, plot_entries, hull_face_color, hull_face_opacity, hull_face_color_mode, element_colors, text_color, elements] // track reactively
306
+ void [show_hull_faces, color_mode, color_scale, camera.rotation_x, camera.rotation_y, camera.zoom, camera.center_x, camera.center_y, plot_entries, hull_data.visible_entries, hull_face_color, hull_face_opacity, hull_face_color_mode, element_colors, text_color, elements] // track reactively
386
307
 
387
308
  render_once()
388
309
  })
389
310
 
390
311
  // Visibility toggles are now bindable props
391
312
 
392
- // Smart label defaults - hide labels if too many entries
313
+ // Smart label defaults: hide labels for large datasets. Applied once per dataset
314
+ // (keyed on the entries prop) so later entry-count changes from temperature/gas
315
+ // filtering don't clobber the user's label toggles.
316
+ let label_defaults_applied_for: PhaseData[] | null = null
393
317
  $effect(() => {
394
- const total_entries = effective_entries.length
395
- if (total_entries > label_threshold) {
396
- show_stable_labels = false
397
- show_unstable_labels = false
398
- } else {
399
- // For smaller datasets, show stable labels by default
400
- show_stable_labels = true
401
- show_unstable_labels = false
402
- }
318
+ if (label_defaults_applied_for === entries) return
319
+ label_defaults_applied_for = entries
320
+ show_stable_labels = hull_data.effective_entries.length <= label_threshold
321
+ show_unstable_labels = false
403
322
  })
404
323
 
405
- // Function to extract structure data from a convex hull entry
406
- function extract_structure_from_entry(
407
- entry: ConvexHullEntry,
408
- ): AnyStructure | null {
409
- const orig_entry = entries.find((ent) => ent.entry_id === entry.entry_id)
410
- return orig_entry?.structure as AnyStructure || null
324
+ // function (not const) so the create_canvas_interactions options above can reference it
325
+ function extract_structure_from_entry(entry: ConvexHullEntry): AnyStructure | null {
326
+ return helpers.extract_structure_from_entry(entries, entry)
411
327
  }
412
328
 
413
329
  const reset_camera = () => {
@@ -421,15 +337,16 @@
421
337
  reset_camera()
422
338
  fullscreen = DEFAULTS.convex_hull.quaternary.fullscreen
423
339
  info_pane_open = DEFAULTS.convex_hull.quaternary.info_pane_open
424
- legend_pane_open = DEFAULTS.convex_hull.quaternary.legend_pane_open
340
+ controls_open = DEFAULTS.convex_hull.quaternary.legend_pane_open
425
341
  color_mode = DEFAULTS.convex_hull.quaternary.color_mode
426
342
  color_scale = DEFAULTS.convex_hull.quaternary.color_scale as D3InterpolateName
427
343
  show_stable = DEFAULTS.convex_hull.quaternary.show_stable
428
344
  show_unstable = DEFAULTS.convex_hull.quaternary.show_unstable
345
+ hidden_categories = []
429
346
  show_stable_labels = DEFAULTS.convex_hull.quaternary.show_stable_labels
430
347
  show_unstable_labels = DEFAULTS.convex_hull.quaternary.show_unstable_labels
431
348
  // Use auto-computed threshold based on entry count instead of static default
432
- max_hull_dist_show_phases = auto_default_threshold
349
+ max_hull_dist_show_phases = hull_data.auto_default_threshold
433
350
  max_hull_dist_show_labels =
434
351
  DEFAULTS.convex_hull.quaternary.max_hull_dist_show_labels
435
352
  show_hull_faces = DEFAULTS.convex_hull.quaternary.show_hull_faces
@@ -439,69 +356,6 @@
439
356
  .hull_face_color_mode as HullFaceColorMode
440
357
  }
441
358
 
442
- const handle_keydown = (event: KeyboardEvent) => {
443
- const target = event.target
444
- // Skip if focus is on an interactive element that handles Enter natively
445
- const interactive_selector =
446
- `input,textarea,select,button,a,[contenteditable="true"],[role="button"],[tabindex]:not([tabindex="-1"])`
447
- if (
448
- target instanceof HTMLElement &&
449
- target.matches(interactive_selector) &&
450
- target !== canvas
451
- ) return
452
-
453
- // Prevent double handling from canvas + wrapper bubbling
454
- if (event.target !== event.currentTarget && event.currentTarget !== canvas) return
455
-
456
- // Handle Enter for keyboard accessibility - select hovered entry
457
- if (event.key === `Enter`) {
458
- const entry = hover_data?.entry
459
- if (entry) {
460
- on_point_click?.(entry)
461
- if (enable_click_selection) {
462
- selected_entry = entry
463
- if (enable_structure_preview) {
464
- const structure = extract_structure_from_entry(entry)
465
- if (structure) {
466
- selected_structure = structure
467
- modal_place_right = helpers.calculate_modal_side(wrapper)
468
- modal_open = true
469
- }
470
- }
471
- }
472
- } else if (modal_open) {
473
- close_structure_popup()
474
- }
475
- return
476
- }
477
-
478
- const actions: Record<string, () => void> = {
479
- r: reset_camera,
480
- b: () => color_mode = color_mode === `stability` ? `energy` : `stability`,
481
- s: () => show_stable = !show_stable,
482
- u: () => show_unstable = !show_unstable,
483
- h: () => show_hull_faces = !show_hull_faces,
484
- l: () => show_stable_labels = !show_stable_labels,
485
- }
486
- actions[event.key.toLowerCase()]?.()
487
- }
488
-
489
- async function handle_file_drop(event: DragEvent): Promise<void> {
490
- drag_over = false
491
- const data = await helpers.parse_hull_entries_from_drop(event)
492
- if (data) on_file_drop?.(data)
493
- }
494
-
495
- async function copy_entry_data(
496
- entry: ConvexHullEntry,
497
- position: { x: number; y: number },
498
- ) {
499
- await helpers.copy_entry_to_clipboard(entry, position, (visible, pos) => {
500
- copy_feedback.visible = visible
501
- copy_feedback.position = pos
502
- })
503
- }
504
-
505
359
  const get_point_color = (entry: ConvexHullEntry): string =>
506
360
  helpers.get_point_color_for_entry(
507
361
  entry,
@@ -630,11 +484,8 @@
630
484
  for (let idx = 0; idx < 4; idx++) {
631
485
  const vx = vertices[idx]
632
486
  // Direction from centroid to vertex
633
- const dir = {
634
- x: vx.x - centroid.x,
635
- y: vx.y - centroid.y,
636
- z: vx.z - centroid.z,
637
- }
487
+ const { x: cx, y: cy, z: cz } = centroid
488
+ const dir = { x: vx.x - cx, y: vx.y - cy, z: vx.z - cz }
638
489
  const len = Math.hypot(dir.x, dir.y, dir.z) || 1
639
490
  const label_pos = {
640
491
  x: vx.x + (dir.x / len) * distance,
@@ -740,14 +591,14 @@
740
591
  triangles.sort((a, b) => a.avg_depth - b.avg_depth)
741
592
 
742
593
  // Lazy computation for uniform mode: normalize alpha by formation energy
743
- let norm_alpha: ((w: number) => number) | null = null
594
+ let norm_alpha: ((energy: number) => number) | null = null
744
595
  if (hull_face_color_mode === `uniform`) {
745
596
  norm_alpha = (energy: number) => {
746
- const t = Math.max(
597
+ const frac = Math.max(
747
598
  0,
748
599
  Math.min(1, (0 - energy) / Math.max(1e-6, 0 - formation_energy_min)),
749
600
  )
750
- return t * hull_face_opacity
601
+ return frac * hull_face_opacity
751
602
  }
752
603
  }
753
604
 
@@ -813,59 +664,16 @@
813
664
 
814
665
  function draw_data_points(): void {
815
666
  if (!ctx || sorted_points_cache.length === 0) return
816
-
817
- for (const { entry, projected } of sorted_points_cache) {
818
- const is_stable = helpers.entry_is_stable(entry)
819
- const is_entry_highlighted = is_highlighted(entry)
820
- const color = get_point_color(entry)
821
- const size = (entry.size || (is_stable ? 6 : 4)) * canvas_dims.scale
822
- const marker = entry.marker || `circle`
823
-
824
- // Shadow
825
- const shadow_offset = Math.abs(entry.z) * 2 * canvas_dims.scale
826
- ctx.fillStyle = `rgba(0, 0, 0, 0.2)`
827
- const shadow_path = helpers.create_marker_path(size * 0.8, marker)
828
- ctx.save()
829
- ctx.translate(projected.x + shadow_offset, projected.y + shadow_offset)
830
- ctx.fill(shadow_path)
831
- ctx.restore()
832
-
833
- // Highlights
834
- if (selected_entry && entry.entry_id === selected_entry.entry_id) {
835
- helpers.draw_selection_highlight(
836
- ctx,
837
- projected,
838
- size,
839
- canvas_dims.scale,
840
- pulse.time,
841
- pulse_opacity,
842
- )
843
- }
844
- if (is_entry_highlighted) {
845
- helpers.draw_highlight_effect(
846
- ctx,
847
- projected,
848
- size,
849
- canvas_dims.scale,
850
- pulse.time,
851
- merged_highlight_style,
852
- )
853
- }
854
-
855
- // Main point with marker symbol
856
- ctx.fillStyle =
857
- is_entry_highlighted && merged_highlight_style.effect === `color`
858
- ? merged_highlight_style.color
859
- : color
860
- ctx.strokeStyle = is_stable ? `#ffffff` : `#000000`
861
- ctx.lineWidth = 0.5 * canvas_dims.scale
862
- const marker_path = helpers.create_marker_path(size, marker)
863
- ctx.save()
864
- ctx.translate(projected.x, projected.y)
865
- ctx.fill(marker_path)
866
- ctx.stroke(marker_path)
867
- ctx.restore()
868
- }
667
+ helpers.draw_hull_points(ctx, sorted_points_cache, {
668
+ scale: canvas_dims.scale,
669
+ shadow_factor: 2,
670
+ selected_entry,
671
+ is_highlighted,
672
+ get_point_color,
673
+ highlight_style: merged_highlight_style,
674
+ pulse_time: pulse.time,
675
+ pulse_opacity,
676
+ })
869
677
 
870
678
  if (!merged_config.show_labels) return
871
679
 
@@ -929,169 +737,7 @@
929
737
  draw_data_points() // Draw data points (on top)
930
738
  }
931
739
 
932
- function handle_mouse_down(event: MouseEvent) {
933
- is_dragging = true
934
- drag_started = false
935
- hover_data = null
936
- on_point_hover?.(null)
937
- last_mouse = { x: event.clientX, y: event.clientY }
938
- }
939
-
940
- const handle_mouse_move = (event: MouseEvent) => {
941
- if (!is_dragging) return
942
- const [dx, dy] = [event.clientX - last_mouse.x, event.clientY - last_mouse.y]
943
-
944
- // Mark as drag if any movement occurred
945
- if (dx !== 0 || dy !== 0) drag_started = true
946
-
947
- // With Cmd/Ctrl held: pan the view instead of rotating
948
- if (event.metaKey || event.ctrlKey) {
949
- camera.center_x += dx
950
- camera.center_y += dy
951
- } else {
952
- camera.rotation_y += dx * 0.005
953
- camera.rotation_x = Math.max(
954
- -Math.PI / 3,
955
- Math.min(Math.PI / 3, camera.rotation_x - dy * 0.005),
956
- )
957
- }
958
- last_mouse = { x: event.clientX, y: event.clientY }
959
- }
960
-
961
- const handle_wheel = (event: WheelEvent) => {
962
- event.preventDefault()
963
- camera.zoom = Math.max(
964
- 1.0,
965
- Math.min(15, camera.zoom * (event.deltaY > 0 ? 0.98 : 1.02)),
966
- )
967
- }
968
-
969
- const handle_hover = (event: MouseEvent) => {
970
- if (is_dragging) return
971
- const entry = find_entry_at_mouse(event)
972
- hover_data = entry
973
- ? { entry, position: { x: event.clientX, y: event.clientY } }
974
- : null
975
- on_point_hover?.(hover_data)
976
- }
977
-
978
- const find_entry_at_mouse = (event: MouseEvent): ConvexHullEntry | null =>
979
- helpers.find_hull_entry_at_mouse(
980
- canvas,
981
- event,
982
- visible_entries,
983
- (x: number, y: number, z: number) => {
984
- const projected = project_3d_point(x, y, z)
985
- return { x: projected.x, y: projected.y }
986
- },
987
- )
988
-
989
- const handle_click = (event: MouseEvent) => {
990
- event.stopPropagation()
991
-
992
- // Check if this was a drag operation (any mouse movement during drag)
993
- const was_drag = drag_started
994
- drag_started = false // Reset for next interaction
995
- if (was_drag) return // Don't trigger click if this was a drag
996
-
997
- const entry = find_entry_at_mouse(event)
998
- if (entry) {
999
- on_point_click?.(entry)
1000
- if (enable_click_selection) {
1001
- selected_entry = entry
1002
- if (enable_structure_preview) {
1003
- const structure = extract_structure_from_entry(entry)
1004
- if (structure) {
1005
- selected_structure = structure
1006
- modal_place_right = helpers.calculate_modal_side(wrapper)
1007
- modal_open = true
1008
- }
1009
- }
1010
- }
1011
- } else if (modal_open) close_structure_popup()
1012
- }
1013
-
1014
- function close_structure_popup() {
1015
- modal_open = false
1016
- selected_structure = null
1017
- selected_entry = null
1018
- }
1019
-
1020
- const handle_double_click = (event: MouseEvent) => {
1021
- const entry = find_entry_at_mouse(event)
1022
- if (entry) {
1023
- copy_entry_data(entry, {
1024
- x: event.clientX,
1025
- y: event.clientY,
1026
- })
1027
- }
1028
- }
1029
-
1030
- function render_once() {
1031
- if (!frame_id) {
1032
- frame_id = requestAnimationFrame(() => {
1033
- render_frame()
1034
- frame_id = 0
1035
- })
1036
- }
1037
- }
1038
-
1039
- function update_canvas_size() {
1040
- if (!canvas) return
1041
- const dpr = globalThis.devicePixelRatio || 1
1042
- const container = canvas.parentElement
1043
- const rect = container?.getBoundingClientRect()
1044
- const [width, height] = rect ? [rect.width, rect.height] : [400, 400]
1045
-
1046
- const new_width = Math.max(0, Math.round(width * dpr))
1047
- const new_height = Math.max(0, Math.round(height * dpr))
1048
- canvas_dims = { width, height, scale: Math.min(width, height) / 600 }
1049
-
1050
- if (!ctx || canvas.width !== new_width || canvas.height !== new_height) {
1051
- canvas.width = new_width
1052
- canvas.height = new_height
1053
- ctx = canvas.getContext(`2d`)
1054
- if (ctx) {
1055
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
1056
- ctx.imageSmoothingEnabled = true
1057
- ctx.imageSmoothingQuality = `high`
1058
- }
1059
- }
1060
- render_once()
1061
- }
1062
-
1063
- $effect(() => {
1064
- if (!canvas) return
1065
-
1066
- // Initial setup
1067
- update_canvas_size()
1068
-
1069
- // Watch for resize events - only update canvas, don't reset camera
1070
- const resize_observer = new ResizeObserver(update_canvas_size)
1071
-
1072
- const container = canvas.parentElement
1073
- if (container) resize_observer.observe(container)
1074
-
1075
- return () => { // Cleanup on unmount
1076
- if (frame_id) cancelAnimationFrame(frame_id)
1077
- resize_observer.disconnect()
1078
- }
1079
- })
1080
-
1081
- // Fullscreen handling with camera reset
1082
- let was_fullscreen = $state(fullscreen)
1083
- $effect(() => {
1084
- setup_fullscreen_effect(fullscreen, wrapper, (entering_fullscreen) => {
1085
- if (entering_fullscreen !== was_fullscreen) {
1086
- camera.center_x = 0
1087
- camera.center_y = 20
1088
- was_fullscreen = entering_fullscreen
1089
- }
1090
- })
1091
- set_fullscreen_bg(wrapper, fullscreen, `--hull-4d-bg-fullscreen`)
1092
- })
1093
-
1094
- // Performance: Cache canvas dimensions and pre-compute sorted point projections
740
+ // Performance: Cache canvas dimensions and formation energy minimum
1095
741
  let canvas_dims = $state({ width: 600, height: 600, scale: 1 })
1096
742
  const formation_energy_min = $derived.by(() => {
1097
743
  let min_energy = 0
@@ -1100,57 +746,33 @@
1100
746
  }
1101
747
  return min_energy
1102
748
  })
1103
- const sorted_points_cache = $derived.by(() => {
1104
- if (!canvas || visible_entries.length === 0) return []
1105
- return visible_entries
1106
- .map((entry) => ({
1107
- entry,
1108
- projected: project_3d_point(entry.x, entry.y, entry.z),
1109
- }))
1110
- .sort((a, b) => a.projected.depth - b.projected.depth)
1111
- })
1112
749
 
1113
- let style = $derived(
1114
- `--hull-stable-color:${merged_config.colors?.stable || `#0072B2`};
1115
- --hull-unstable-color:${merged_config.colors?.unstable || `#E69F00`};
1116
- --hull-edge-color:${merged_config.colors?.edge || `var(--text-color, #212121)`};
1117
- --hull-text-color:${
1118
- merged_config.colors?.annotation || `var(--text-color, #212121)`
1119
- }`,
1120
- )
750
+ let style = $derived(helpers.hull_style_css(merged_config.colors))
1121
751
  </script>
1122
752
 
1123
753
  <svelte:document
1124
754
  onfullscreenchange={() => {
1125
- fullscreen = Boolean(document.fullscreenElement)
755
+ // tie fullscreen state to this component's own wrapper, not any fullscreen element
756
+ fullscreen = document.fullscreenElement === wrapper
1126
757
  }}
1127
- onmousemove={handle_mouse_move}
1128
- onmouseup={() => [is_dragging, drag_started] = [false, false]}
758
+ onmousemove={interactions.handle_mouse_move}
759
+ onmouseup={interactions.handle_mouse_up}
1129
760
  />
1130
761
 
1131
762
  <div
1132
763
  {...rest}
1133
764
  class="convex-hull-4d {rest.class ?? ``}"
1134
- class:dragover={drag_over}
765
+ class:dragover={interactions.drag_over}
1135
766
  style={`${style}; ${rest.style ?? ``}`}
1136
767
  data-has-selection={selected_entry !== null}
1137
- data-has-hover={hover_data !== null}
1138
- data-is-dragging={is_dragging}
768
+ data-has-hover={interactions.hover_data !== null}
769
+ data-is-dragging={interactions.is_dragging}
1139
770
  data-rotation-x={camera.rotation_x.toFixed(4)}
1140
771
  data-rotation-y={camera.rotation_y.toFixed(4)}
1141
772
  bind:this={wrapper}
1142
773
  role="application"
1143
774
  tabindex="-1"
1144
- onkeydown={handle_keydown}
1145
- ondrop={handle_file_drop}
1146
- ondragover={(event) => {
1147
- event.preventDefault()
1148
- drag_over = true
1149
- }}
1150
- ondragleave={(event) => {
1151
- event.preventDefault()
1152
- drag_over = false
1153
- }}
775
+ {...interactions.wrapper_handlers}
1154
776
  aria-label="Convex hull visualization"
1155
777
  >
1156
778
  {@render children?.({
@@ -1167,12 +789,7 @@
1167
789
  bind:this={canvas}
1168
790
  tabindex="0"
1169
791
  aria-label={merged_controls.title || phase_stats?.chemical_system || `4D Convex Hull`}
1170
- onmousedown={handle_mouse_down}
1171
- onmousemove={handle_hover}
1172
- onclick={handle_click}
1173
- onkeydown={handle_keydown}
1174
- ondblclick={handle_double_click}
1175
- onwheel={handle_wheel}
792
+ {...interactions.canvas_handlers}
1176
793
  ></canvas>
1177
794
 
1178
795
  {#if entries.length === 0}
@@ -1184,14 +801,9 @@
1184
801
 
1185
802
  <!-- Energy above hull Color Bar -->
1186
803
  {#if color_mode === `energy` && plot_entries.length > 0}
1187
- {@const hull_distances = plot_entries
1188
- .map((entry) => entry.e_above_hull)
1189
- .filter((val): val is number => typeof val === `number`)}
1190
- {@const min_energy = hull_distances.length > 0 ? Math.min(...hull_distances) : 0}
1191
- {@const max_energy = hull_distances.length > 0 ? Math.max(...hull_distances, 0.1) : 0.1}
1192
804
  <ColorBar
1193
805
  title="Energy above hull (eV/atom)"
1194
- range={[min_energy, max_energy]}
806
+ range={helpers.hull_distance_range(plot_entries)}
1195
807
  {color_scale}
1196
808
  wrapper_style="position: absolute; bottom: 2em; left: 1em; width: 200px;"
1197
809
  bar_style="height: 12px;"
@@ -1199,88 +811,52 @@
1199
811
  />
1200
812
  {/if}
1201
813
 
1202
- <!-- Control buttons (top-right corner like Structure.svelte) -->
1203
- {#if controls_config.mode !== `never`}
1204
- <section class="control-buttons {controls_config.class}">
1205
- {#if controls_config.visible(`reset`)}
1206
- <button
1207
- type="button"
1208
- onclick={reset_all}
1209
- title="Reset camera view (R key)"
1210
- class="reset-camera-btn"
1211
- >
1212
- <Icon icon="Reset" />
1213
- </button>
1214
- {/if}
1215
-
1216
- {#if enable_info_pane && phase_stats && controls_config.visible(`info-pane`)}
1217
- <ConvexHullInfoPane
1218
- bind:pane_open={info_pane_open}
1219
- {phase_stats}
1220
- {stable_entries}
1221
- {unstable_entries}
1222
- {show_stable}
1223
- {show_unstable}
1224
- {max_hull_dist_show_phases}
1225
- {max_hull_dist_show_labels}
1226
- {label_threshold}
1227
- toggle_props={{ class: `info-btn` }}
1228
- />
1229
- {/if}
1230
-
1231
- {#if enable_fullscreen && controls_config.visible(`fullscreen`)}
1232
- <button
1233
- type="button"
1234
- onclick={() => toggle_fullscreen(wrapper)}
1235
- title="{fullscreen ? `Exit` : `Enter`} fullscreen"
1236
- class="fullscreen-btn"
1237
- >
1238
- <Icon icon="{fullscreen ? `Exit` : ``}Fullscreen" />
1239
- </button>
1240
- {/if}
1241
-
1242
- <!-- Legend controls pane -->
1243
- {#if controls_config.visible(`controls`)}
1244
- <ConvexHullControls
1245
- bind:controls_open={legend_pane_open}
1246
- bind:color_mode
1247
- bind:color_scale
1248
- bind:show_stable
1249
- bind:show_unstable
1250
- bind:show_stable_labels
1251
- bind:show_unstable_labels
1252
- bind:max_hull_dist_show_phases
1253
- bind:max_hull_dist_show_labels
1254
- {max_hull_dist_in_data}
1255
- {stable_entries}
1256
- {unstable_entries}
1257
- {camera}
1258
- {merged_controls}
1259
- toggle_props={{ class: `legend-controls-btn` }}
1260
- {show_hull_faces}
1261
- on_hull_faces_change={(value: boolean) => show_hull_faces = value}
1262
- {hull_face_color}
1263
- on_hull_face_color_change={(value: string) => hull_face_color = value}
1264
- {hull_face_opacity}
1265
- on_hull_face_opacity_change={(value: number) => hull_face_opacity = value}
1266
- {hull_face_color_mode}
1267
- on_hull_face_color_mode_change={(value: HullFaceColorMode) =>
1268
- hull_face_color_mode = value}
1269
- bind:energy_source_mode
1270
- {has_precomputed_e_form}
1271
- {can_compute_e_form}
1272
- {has_precomputed_hull}
1273
- {can_compute_hull}
1274
- />
1275
- {/if}
1276
- </section>
814
+ <!-- Toolbar + tooltip/copy-feedback/drag/structure-popup chrome -->
815
+ <ConvexHullChrome
816
+ {interactions}
817
+ {hull_data}
818
+ {controls_config}
819
+ {reset_all}
820
+ reset_title="Reset view and settings"
821
+ {enable_info_pane}
822
+ {phase_stats}
823
+ {label_threshold}
824
+ {fullscreen}
825
+ {fullscreen_toggle}
826
+ {wrapper}
827
+ {camera}
828
+ {merged_controls}
829
+ {stable_entries}
830
+ {unstable_entries}
831
+ {get_point_color}
832
+ {merged_highlight_style}
833
+ {is_highlighted}
834
+ {tooltip}
835
+ {selected_entry}
836
+ bind:show_hull_faces
837
+ bind:hull_face_color
838
+ bind:hull_face_opacity
839
+ bind:hull_face_color_mode
840
+ bind:info_pane_open
841
+ bind:controls_open
842
+ bind:color_mode
843
+ bind:color_scale
844
+ bind:show_stable
845
+ bind:show_unstable
846
+ {entry_category}
847
+ bind:hidden_categories
848
+ bind:show_stable_labels
849
+ bind:show_unstable_labels
850
+ bind:max_hull_dist_show_phases
851
+ bind:max_hull_dist_show_labels
852
+ bind:energy_source_mode
853
+ />
854
+
855
+ {#if hull_data.has_temp_data && temperature !== undefined}
856
+ <TemperatureSlider available_temperatures={hull_data.available_temperatures} bind:temperature />
1277
857
  {/if}
1278
858
 
1279
- {#if has_temp_data && temperature !== undefined}
1280
- <TemperatureSlider {available_temperatures} bind:temperature />
1281
- {/if}
1282
-
1283
- {#if gas_analysis.has_gas_dependent_elements && merged_gas_config}
859
+ {#if hull_data.gas_analysis.has_gas_dependent_elements && merged_gas_config}
1284
860
  <GasPressureControls
1285
861
  config={merged_gas_config}
1286
862
  bind:pressures={gas_pressures}
@@ -1288,48 +864,6 @@
1288
864
  />
1289
865
  {/if}
1290
866
 
1291
- <!-- Hover tooltip -->
1292
- {#if hover_data}
1293
- {@const { entry, position } = hover_data}
1294
- {@const entry_highlight = is_highlighted(entry) ? merged_highlight_style : undefined}
1295
- {@const tooltip_style =
1296
- `z-index: ${CONVEX_HULL_STYLE.z_index.tooltip}; backdrop-filter: blur(4px);
1297
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);`}
1298
- <PlotTooltip
1299
- x={position.x}
1300
- y={position.y}
1301
- offset={{ x: 10, y: -10 }}
1302
- bg_color={get_point_color(entry)}
1303
- fixed
1304
- style={tooltip_style}
1305
- >
1306
- <ConvexHullTooltip
1307
- {entry}
1308
- {polymorph_stats_map}
1309
- highlight_style={entry_highlight}
1310
- {tooltip}
1311
- />
1312
- </PlotTooltip>
1313
- {/if}
1314
-
1315
- <!-- Copy-to-clipboard feedback (double-click on point) -->
1316
- <ClickFeedback bind:visible={copy_feedback.visible} position={copy_feedback.position} />
1317
-
1318
- <!-- Drag over overlay -->
1319
- <DragOverlay visible={drag_over} />
1320
-
1321
- {#if modal_open && selected_structure}
1322
- <StructurePopup
1323
- structure={selected_structure}
1324
- place_right={modal_place_right}
1325
- stats={{
1326
- id: selected_entry?.entry_id,
1327
- e_above_hull: selected_entry?.e_above_hull,
1328
- e_form: selected_entry?.e_form_per_atom,
1329
- }}
1330
- onclose={close_structure_popup}
1331
- />
1332
- {/if}
1333
867
  </div>
1334
868
 
1335
869
  <style>
@@ -1357,42 +891,4 @@
1357
891
  canvas:active {
1358
892
  cursor: grabbing;
1359
893
  }
1360
- .control-buttons {
1361
- position: absolute;
1362
- top: 1ex;
1363
- right: 1ex;
1364
- display: flex;
1365
- gap: 8px;
1366
- transition: opacity 0.2s ease-in-out;
1367
- }
1368
- .control-buttons.hover-visible {
1369
- opacity: 0;
1370
- pointer-events: none;
1371
- }
1372
- .convex-hull-4d:hover .control-buttons.hover-visible,
1373
- .convex-hull-4d:focus-within .control-buttons.hover-visible {
1374
- opacity: 1;
1375
- pointer-events: auto;
1376
- }
1377
- .control-buttons.always-visible {
1378
- opacity: 1;
1379
- pointer-events: auto;
1380
- }
1381
- .control-buttons :global(.draggable-pane) {
1382
- z-index: 1001 !important;
1383
- }
1384
- .control-buttons :global(button) {
1385
- background: transparent;
1386
- border: none;
1387
- padding: 4px;
1388
- cursor: pointer;
1389
- border-radius: 3px;
1390
- color: var(--text-color, currentColor);
1391
- transition: background-color 0.2s;
1392
- display: flex;
1393
- font-size: clamp(0.85em, 2cqmin, 1.3em);
1394
- }
1395
- .control-buttons :global(button):hover {
1396
- background-color: color-mix(in srgb, currentColor 8%, transparent);
1397
- }
1398
894
  </style>