matterviz 0.4.0 → 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 (326) hide show
  1. package/dist/brillouin/BrillouinZone.svelte +68 -145
  2. package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -14
  3. package/dist/brillouin/BrillouinZoneExportPane.svelte +43 -96
  4. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  5. package/dist/brillouin/BrillouinZoneInfoPane.svelte +9 -32
  6. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
  7. package/dist/brillouin/BrillouinZoneScene.svelte +49 -203
  8. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +3 -23
  9. package/dist/brillouin/ReciprocalVectors.svelte +39 -0
  10. package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
  11. package/dist/brillouin/compute.d.ts +2 -0
  12. package/dist/brillouin/compute.js +80 -77
  13. package/dist/brillouin/geometry.d.ts +8 -0
  14. package/dist/brillouin/geometry.js +57 -0
  15. package/dist/brillouin/index.d.ts +2 -0
  16. package/dist/brillouin/index.js +2 -0
  17. package/dist/brillouin/types.d.ts +2 -2
  18. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
  19. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +100 -191
  20. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
  21. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +176 -464
  22. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
  23. package/dist/chempot-diagram/color.d.ts +3 -6
  24. package/dist/chempot-diagram/color.js +5 -5
  25. package/dist/chempot-diagram/compute.d.ts +3 -3
  26. package/dist/chempot-diagram/compute.js +3 -1
  27. package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
  28. package/dist/chempot-diagram/controls-state.svelte.js +42 -0
  29. package/dist/chempot-diagram/export.d.ts +47 -0
  30. package/dist/chempot-diagram/export.js +133 -0
  31. package/dist/chempot-diagram/index.d.ts +1 -0
  32. package/dist/chempot-diagram/index.js +1 -0
  33. package/dist/chempot-diagram/pointer.d.ts +0 -10
  34. package/dist/chempot-diagram/pointer.js +4 -4
  35. package/dist/chempot-diagram/types.d.ts +3 -3
  36. package/dist/colors/index.js +2 -2
  37. package/dist/composition/FormulaFilter.svelte +6 -5
  38. package/dist/composition/PieChart.svelte +5 -5
  39. package/dist/composition/chem-sys.js +3 -2
  40. package/dist/composition/format.js +3 -2
  41. package/dist/composition/parse.d.ts +0 -1
  42. package/dist/composition/parse.js +17 -19
  43. package/dist/controls.d.ts +1 -0
  44. package/dist/controls.js +0 -1
  45. package/dist/convex-hull/ConvexHull.svelte +8 -10
  46. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
  47. package/dist/convex-hull/ConvexHull2D.svelte +94 -175
  48. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  49. package/dist/convex-hull/ConvexHull3D.svelte +176 -680
  50. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  51. package/dist/convex-hull/ConvexHull4D.svelte +180 -680
  52. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  53. package/dist/convex-hull/ConvexHullChrome.svelte +268 -0
  54. package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
  55. package/dist/convex-hull/ConvexHullControls.svelte +88 -7
  56. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
  57. package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
  58. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
  59. package/dist/convex-hull/ConvexHullStats.svelte +29 -168
  60. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
  61. package/dist/convex-hull/ConvexHullTooltip.svelte +11 -2
  62. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
  63. package/dist/convex-hull/barycentric-coords.d.ts +2 -4
  64. package/dist/convex-hull/barycentric-coords.js +6 -33
  65. package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
  66. package/dist/convex-hull/canvas-interactions.svelte.js +278 -0
  67. package/dist/convex-hull/helpers.d.ts +39 -7
  68. package/dist/convex-hull/helpers.js +154 -69
  69. package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
  70. package/dist/convex-hull/hull-state.svelte.js +124 -0
  71. package/dist/convex-hull/index.d.ts +9 -7
  72. package/dist/convex-hull/index.js +7 -2
  73. package/dist/convex-hull/thermodynamics.js +91 -920
  74. package/dist/convex-hull/types.d.ts +12 -4
  75. package/dist/convex-hull/types.js +12 -0
  76. package/dist/coordination/CoordinationBarPlot.svelte +4 -11
  77. package/dist/element/BohrAtom.svelte +2 -1
  78. package/dist/element/ElementTile.svelte.d.ts +1 -1
  79. package/dist/element/index.d.ts +4 -0
  80. package/dist/element/index.js +18 -0
  81. package/dist/feedback/DragOverlay.svelte +3 -1
  82. package/dist/feedback/DragOverlay.svelte.d.ts +1 -0
  83. package/dist/feedback/StatusMessage.svelte +13 -3
  84. package/dist/fermi-surface/FermiSurface.svelte +67 -146
  85. package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -14
  86. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  87. package/dist/fermi-surface/FermiSurfaceScene.svelte +72 -224
  88. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
  89. package/dist/fermi-surface/compute.js +11 -10
  90. package/dist/fermi-surface/export.js +4 -15
  91. package/dist/fermi-surface/index.d.ts +0 -1
  92. package/dist/fermi-surface/index.js +0 -1
  93. package/dist/fermi-surface/parse.d.ts +1 -1
  94. package/dist/fermi-surface/parse.js +64 -75
  95. package/dist/fermi-surface/types.d.ts +2 -2
  96. package/dist/heatmap-matrix/HeatmapMatrix.svelte +55 -40
  97. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +4 -3
  98. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +3 -2
  99. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
  100. package/dist/heatmap-matrix/index.d.ts +3 -2
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +1 -0
  103. package/dist/io/ExportPane.svelte +166 -0
  104. package/dist/io/ExportPane.svelte.d.ts +17 -0
  105. package/dist/io/decompress.js +1 -2
  106. package/dist/io/export.d.ts +5 -1
  107. package/dist/io/export.js +32 -28
  108. package/dist/io/fetch.d.ts +2 -1
  109. package/dist/io/file-drop.d.ts +7 -0
  110. package/dist/io/file-drop.js +13 -0
  111. package/dist/io/index.d.ts +2 -0
  112. package/dist/io/index.js +10 -0
  113. package/dist/io/types.d.ts +13 -0
  114. package/dist/isosurface/parse.js +46 -44
  115. package/dist/labels.js +1 -1
  116. package/dist/layout/FullscreenButton.svelte +33 -0
  117. package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
  118. package/dist/layout/FullscreenToggle.svelte +8 -14
  119. package/dist/layout/ViewerChrome.svelte +116 -0
  120. package/dist/layout/ViewerChrome.svelte.d.ts +17 -0
  121. package/dist/layout/fullscreen.d.ts +4 -0
  122. package/dist/layout/fullscreen.svelte.d.ts +8 -0
  123. package/dist/layout/fullscreen.svelte.js +37 -0
  124. package/dist/layout/index.d.ts +3 -0
  125. package/dist/layout/index.js +3 -0
  126. package/dist/math.d.ts +7 -3
  127. package/dist/math.js +18 -21
  128. package/dist/overlays/index.d.ts +4 -0
  129. package/dist/periodic-table/PeriodicTable.svelte +9 -8
  130. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  131. package/dist/phase-diagram/PhaseDiagramControls.svelte +3 -2
  132. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
  133. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  134. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
  135. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +47 -132
  136. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
  137. package/dist/phase-diagram/colors.js +1 -1
  138. package/dist/phase-diagram/parse.d.ts +2 -1
  139. package/dist/plot/bar/BarPlot.svelte +79 -316
  140. package/dist/plot/bar/BarPlot.svelte.d.ts +7 -15
  141. package/dist/plot/bar/BarPlotControls.svelte.d.ts +1 -1
  142. package/dist/plot/bar/SpacegroupBarPlot.svelte +2 -1
  143. package/dist/plot/box/BoxPlot.svelte +76 -246
  144. package/dist/plot/box/BoxPlot.svelte.d.ts +4 -3
  145. package/dist/plot/box/BoxPlotControls.svelte.d.ts +1 -1
  146. package/dist/plot/box/Violin.svelte.d.ts +1 -1
  147. package/dist/plot/box/box-plot.d.ts +3 -2
  148. package/dist/plot/box/box-plot.js +5 -2
  149. package/dist/plot/box/kde.d.ts +2 -1
  150. package/dist/plot/box/kde.js +4 -4
  151. package/dist/plot/core/auto-place.d.ts +1 -1
  152. package/dist/plot/core/auto-place.js +4 -1
  153. package/dist/plot/core/components/ColorBar.svelte +5 -5
  154. package/dist/plot/core/components/ColorBar.svelte.d.ts +5 -4
  155. package/dist/plot/core/components/Line.svelte +3 -2
  156. package/dist/plot/core/components/Line.svelte.d.ts +3 -2
  157. package/dist/plot/core/components/PlotAxis.svelte +2 -1
  158. package/dist/plot/core/components/PlotAxis.svelte.d.ts +2 -1
  159. package/dist/plot/core/components/PlotControls.svelte.d.ts +1 -1
  160. package/dist/plot/core/components/ReferenceLine3D.svelte +2 -2
  161. package/dist/plot/core/components/ReferenceLine3D.svelte.d.ts +4 -4
  162. package/dist/plot/core/components/ReferencePlane.svelte +2 -2
  163. package/dist/plot/core/components/ReferencePlane.svelte.d.ts +4 -4
  164. package/dist/plot/core/data-cleaning.js +18 -18
  165. package/dist/plot/core/fill-utils.d.ts +4 -3
  166. package/dist/plot/core/fill-utils.js +6 -3
  167. package/dist/plot/core/interactions.d.ts +5 -1
  168. package/dist/plot/core/interactions.js +14 -0
  169. package/dist/plot/core/pan-zoom.svelte.d.ts +35 -0
  170. package/dist/plot/core/pan-zoom.svelte.js +221 -0
  171. package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
  172. package/dist/plot/core/placed-tween.svelte.js +68 -0
  173. package/dist/plot/core/reference-line.d.ts +10 -10
  174. package/dist/plot/core/reference-line.js +6 -6
  175. package/dist/plot/core/scales.d.ts +17 -25
  176. package/dist/plot/core/scales.js +10 -8
  177. package/dist/plot/core/svg.d.ts +2 -1
  178. package/dist/plot/core/types.d.ts +18 -7
  179. package/dist/plot/core/utils/label-placement.d.ts +1 -1
  180. package/dist/plot/core/utils/label-placement.js +3 -3
  181. package/dist/plot/core/utils.d.ts +2 -1
  182. package/dist/plot/histogram/Histogram.svelte +77 -314
  183. package/dist/plot/histogram/HistogramControls.svelte.d.ts +1 -1
  184. package/dist/plot/sankey/Sankey.svelte +2 -5
  185. package/dist/plot/sankey/Sankey.svelte.d.ts +1 -1
  186. package/dist/plot/sankey/sankey.js +3 -1
  187. package/dist/plot/scatter/BinnedScatterPlot.svelte +3 -5
  188. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +4 -4
  189. package/dist/plot/scatter/ScatterPlot.svelte +160 -450
  190. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +7 -15
  191. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
  192. package/dist/plot/scatter/binned-scatter-types.d.ts +4 -11
  193. package/dist/plot/scatter/index.d.ts +1 -1
  194. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +15 -26
  195. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +6 -14
  196. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +9 -10
  197. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte.d.ts +5 -5
  198. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +122 -121
  199. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +5 -14
  200. package/dist/plot/scatter-3d/Surface3D.svelte +6 -5
  201. package/dist/plot/scatter-3d/Surface3D.svelte.d.ts +4 -3
  202. package/dist/plot/sunburst/Sunburst.svelte +16 -20
  203. package/dist/plot/sunburst/Sunburst.svelte.d.ts +4 -3
  204. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +1 -1
  205. package/dist/plot/sunburst/sunburst.js +4 -1
  206. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  207. package/dist/sanitize.js +13 -2
  208. package/dist/scene/SceneCamera.svelte +62 -0
  209. package/dist/scene/SceneCamera.svelte.d.ts +19 -0
  210. package/dist/scene/bind-renderer.svelte.d.ts +2 -0
  211. package/dist/scene/bind-renderer.svelte.js +14 -0
  212. package/dist/scene/index.d.ts +4 -0
  213. package/dist/scene/index.js +5 -0
  214. package/dist/scene/props.js +52 -0
  215. package/dist/scene/types.d.ts +26 -0
  216. package/dist/scene/types.js +1 -0
  217. package/dist/settings.d.ts +14 -2
  218. package/dist/settings.js +59 -1
  219. package/dist/spectral/Bands.svelte +8 -7
  220. package/dist/spectral/Bands.svelte.d.ts +3 -2
  221. package/dist/spectral/BandsAndDos.svelte +22 -24
  222. package/dist/spectral/BrillouinBandsDos.svelte +3 -3
  223. package/dist/spectral/Dos.svelte +5 -4
  224. package/dist/spectral/Dos.svelte.d.ts +2 -1
  225. package/dist/spectral/helpers.d.ts +6 -6
  226. package/dist/spectral/helpers.js +43 -37
  227. package/dist/state.svelte.d.ts +0 -7
  228. package/dist/state.svelte.js +0 -6
  229. package/dist/structure/Arrow.svelte +2 -4
  230. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  231. package/dist/structure/CanvasTooltip.svelte +1 -0
  232. package/dist/structure/CellSelect.svelte +11 -3
  233. package/dist/structure/CellSelect.svelte.d.ts +2 -1
  234. package/dist/structure/Lattice.svelte +2 -2
  235. package/dist/structure/Structure.svelte +291 -355
  236. package/dist/structure/Structure.svelte.d.ts +5 -15
  237. package/dist/structure/StructureControls.svelte +217 -2
  238. package/dist/structure/StructureControls.svelte.d.ts +5 -3
  239. package/dist/structure/StructureExportPane.svelte +54 -156
  240. package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
  241. package/dist/structure/StructureInfoPane.svelte +5 -3
  242. package/dist/structure/StructureInfoPane.svelte.d.ts +5 -5
  243. package/dist/structure/StructureScene.svelte +365 -198
  244. package/dist/structure/StructureScene.svelte.d.ts +22 -20
  245. package/dist/structure/{label-placement.d.ts → atom-label-placement.d.ts} +3 -3
  246. package/dist/structure/{label-placement.js → atom-label-placement.js} +12 -2
  247. package/dist/structure/atom-properties.d.ts +1 -1
  248. package/dist/structure/atom-properties.js +11 -16
  249. package/dist/structure/bond-order-perception.js +2 -4
  250. package/dist/structure/bonding.d.ts +3 -0
  251. package/dist/structure/bonding.js +91 -48
  252. package/dist/structure/export.d.ts +24 -4
  253. package/dist/structure/export.js +64 -122
  254. package/dist/structure/index.d.ts +2 -0
  255. package/dist/structure/index.js +2 -0
  256. package/dist/structure/parse.d.ts +3 -2
  257. package/dist/structure/parse.js +333 -370
  258. package/dist/structure/partial-occupancy.d.ts +0 -1
  259. package/dist/structure/partial-occupancy.js +1 -1
  260. package/dist/structure/pbc.d.ts +1 -1
  261. package/dist/structure/pbc.js +186 -13
  262. package/dist/structure/polyhedra.d.ts +41 -0
  263. package/dist/structure/polyhedra.js +602 -0
  264. package/dist/structure/site.d.ts +4 -0
  265. package/dist/structure/site.js +1 -0
  266. package/dist/structure/supercell.js +3 -2
  267. package/dist/structure/validation.js +5 -6
  268. package/dist/symmetry/SymmetryElementControls.svelte +69 -0
  269. package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
  270. package/dist/symmetry/SymmetryElements.svelte +354 -0
  271. package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
  272. package/dist/symmetry/SymmetryStats.svelte +111 -6
  273. package/dist/symmetry/WyckoffTable.svelte +68 -7
  274. package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
  275. package/dist/symmetry/cell-transform.js +7 -14
  276. package/dist/symmetry/index.d.ts +14 -4
  277. package/dist/symmetry/index.js +301 -80
  278. package/dist/symmetry/spacegroups.d.ts +5 -1
  279. package/dist/symmetry/spacegroups.js +15 -1
  280. package/dist/symmetry/symmetry-elements.d.ts +33 -0
  281. package/dist/symmetry/symmetry-elements.js +521 -0
  282. package/dist/symmetry/wyckoff-db.d.ts +9 -0
  283. package/dist/symmetry/wyckoff-db.js +87 -0
  284. package/dist/table/HeatmapTable.svelte +4 -15
  285. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  286. package/dist/trajectory/Trajectory.svelte +58 -61
  287. package/dist/trajectory/Trajectory.svelte.d.ts +10 -22
  288. package/dist/trajectory/TrajectoryExportPane.svelte +15 -24
  289. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
  290. package/dist/trajectory/TrajectoryInfoPane.svelte +3 -2
  291. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
  292. package/dist/trajectory/constants.js +6 -2
  293. package/dist/trajectory/extract.js +17 -37
  294. package/dist/trajectory/format-detect.d.ts +0 -1
  295. package/dist/trajectory/format-detect.js +3 -9
  296. package/dist/trajectory/frame-reader.d.ts +0 -1
  297. package/dist/trajectory/frame-reader.js +62 -128
  298. package/dist/trajectory/helpers.d.ts +10 -2
  299. package/dist/trajectory/helpers.js +56 -36
  300. package/dist/trajectory/parse/ase.d.ts +9 -1
  301. package/dist/trajectory/parse/ase.js +47 -32
  302. package/dist/trajectory/parse/diagnostics.d.ts +3 -0
  303. package/dist/trajectory/parse/diagnostics.js +14 -0
  304. package/dist/trajectory/parse/index.d.ts +1 -1
  305. package/dist/trajectory/parse/index.js +54 -102
  306. package/dist/trajectory/parse/lammps.d.ts +0 -2
  307. package/dist/trajectory/parse/lammps.js +8 -6
  308. package/dist/trajectory/parse/pymatgen.d.ts +2 -0
  309. package/dist/trajectory/parse/pymatgen.js +74 -0
  310. package/dist/trajectory/parse/vasp.js +4 -3
  311. package/dist/trajectory/parse/xyz.d.ts +9 -21
  312. package/dist/trajectory/parse/xyz.js +28 -33
  313. package/dist/trajectory/plotting.d.ts +0 -1
  314. package/dist/trajectory/plotting.js +3 -100
  315. package/dist/utils.d.ts +1 -0
  316. package/dist/utils.js +1 -1
  317. package/dist/xrd/XrdPlot.svelte +14 -29
  318. package/dist/xrd/broadening.d.ts +2 -1
  319. package/dist/xrd/calc-xrd.js +6 -11
  320. package/dist/xrd/index.d.ts +2 -2
  321. package/package.json +29 -16
  322. package/dist/element/data.json +0 -11864
  323. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  324. package/dist/fermi-surface/marching-cubes.js +0 -2
  325. package/dist/plot/core/hover-lock.svelte.d.ts +0 -14
  326. package/dist/plot/core/hover-lock.svelte.js +0 -45
@@ -6,9 +6,11 @@
6
6
  import TemperatureSlider from '../convex-hull/TemperatureSlider.svelte'
7
7
  import type { PhaseData } from '../convex-hull/types'
8
8
  import Spinner from '../feedback/Spinner.svelte'
9
- import Icon from '../Icon.svelte'
9
+ import type { ExportSection } from '../io'
10
+ import ExportPane from '../io/ExportPane.svelte'
10
11
  import { format_num } from '../labels'
11
- import { set_fullscreen_bg, SettingsSection, toggle_fullscreen } from '../layout'
12
+ import { FullscreenButton, type FullscreenToggleProp, SettingsSection, toggle_fullscreen } from '../layout'
13
+ import { sync_fullscreen } from '../layout/fullscreen.svelte'
12
14
  import type { Vec2, Vec3 } from '../math'
13
15
  import {
14
16
  convex_hull_2d,
@@ -32,15 +34,29 @@
32
34
  import { Canvas, T } from '@threlte/core'
33
35
  import * as extras from '@threlte/extras'
34
36
  import { scaleLinear } from 'd3-scale'
37
+ import type { Snippet } from 'svelte'
35
38
  import { onDestroy, onMount, untrack } from 'svelte'
36
39
  import { SvelteMap, SvelteSet } from 'svelte/reactivity'
37
40
  import * as THREE from 'three'
38
41
  import type { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
39
- import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'
40
42
  import { ConvexGeometry } from 'three/examples/jsm/geometries/ConvexGeometry.js'
41
43
  import { compute_chempot_async } from './async-compute.svelte'
42
44
  import ChemPotScene3D from './ChemPotScene3D.svelte'
43
45
  import { get_chempot_color_bar_config, make_chempot_color_scale } from './color'
46
+ import {
47
+ CHEMPOT_COLOR_MODE_OPTIONS,
48
+ CHEMPOT_COLOR_SCALE_OPTIONS,
49
+ create_chempot_overrides,
50
+ } from './controls-state.svelte'
51
+ import {
52
+ export_glb_file,
53
+ export_json_file,
54
+ export_png_file,
55
+ export_svg_file,
56
+ export_view_json_file,
57
+ get_json_string,
58
+ get_view_settings,
59
+ } from './export'
44
60
  import {
45
61
  apply_element_padding,
46
62
  bbox_diagonal,
@@ -85,6 +101,11 @@
85
101
  max_interpolation_gap = CHEMPOT_DEFAULTS.max_interpolation_gap,
86
102
  hover_info = $bindable<ChemPotHoverInfo | null>(null),
87
103
  render_local_tooltip = true,
104
+ wrapper = $bindable(),
105
+ fullscreen = $bindable(false),
106
+ fullscreen_toggle = true,
107
+ controls_open = $bindable(false),
108
+ export_pane_open = $bindable(false),
88
109
  }: {
89
110
  entries: PhaseData[]
90
111
  config?: ChemPotDiagramConfig
@@ -95,54 +116,41 @@
95
116
  max_interpolation_gap?: number
96
117
  hover_info?: ChemPotHoverInfo | null
97
118
  render_local_tooltip?: boolean
119
+ // bindable: top-level wrapper element
120
+ wrapper?: HTMLDivElement
121
+ // bindable: fullscreen state
122
+ fullscreen?: boolean
123
+ // show/hide the fullscreen button (or custom snippet to render it)
124
+ fullscreen_toggle?: FullscreenToggleProp
125
+ // bindable: whether the controls pane is currently open
126
+ controls_open?: boolean
127
+ // bindable: whether the export pane is currently open
128
+ export_pane_open?: boolean
98
129
  } = $props()
99
130
 
100
- let formal_chempots_override = $state<boolean | null>(null)
101
- let label_stable_override = $state<boolean | null>(null)
102
- let element_padding_override = $state<number | null>(null)
103
- let default_min_limit_override = $state<number | null>(null)
104
- let draw_formula_meshes_override = $state<boolean | null>(null)
105
- let draw_formula_lines_override = $state<boolean | null>(null)
106
- const formal_chempots = $derived(
107
- formal_chempots_override ??
108
- (config.formal_chempots ?? CHEMPOT_DEFAULTS.formal_chempots),
109
- )
110
- const label_stable = $derived(
111
- label_stable_override ?? (config.label_stable ?? CHEMPOT_DEFAULTS.label_stable),
112
- )
113
- const element_padding = $derived(
114
- element_padding_override ??
115
- (config.element_padding ?? CHEMPOT_DEFAULTS.element_padding),
116
- )
117
- const default_min_limit = $derived(
118
- default_min_limit_override ??
119
- (config.default_min_limit ?? CHEMPOT_DEFAULTS.default_min_limit),
120
- )
121
- let formulas_to_draw_override = $state<string[] | null>(null)
122
- const formulas_to_draw = $derived(
123
- formulas_to_draw_override ?? (config.formulas_to_draw ?? []),
124
- )
125
- const draw_formula_meshes = $derived(
126
- draw_formula_meshes_override ??
127
- (config.draw_formula_meshes ?? CHEMPOT_DEFAULTS.draw_formula_meshes),
128
- )
129
- const draw_formula_lines = $derived(
130
- draw_formula_lines_override ??
131
- (config.draw_formula_lines ?? CHEMPOT_DEFAULTS.draw_formula_lines),
132
- )
133
- let color_mode_override = $state<ChemPotColorMode | null>(null)
134
- let color_scale_override = $state<D3InterpolateName | null>(null)
135
- let reverse_color_scale_override = $state<boolean | null>(null)
136
- const color_mode = $derived(
137
- color_mode_override ?? (config.color_mode ?? `arity`),
138
- )
139
- const color_scale = $derived(
140
- color_scale_override ?? (config.color_scale ?? CHEMPOT_DEFAULTS.color_scale),
141
- )
142
- const reverse_color_scale = $derived(
143
- reverse_color_scale_override ??
144
- (config.reverse_color_scale ?? CHEMPOT_DEFAULTS.reverse_color_scale),
145
- )
131
+ // Control overrides (override ?? config ?? default, cleared by Reset)
132
+ const overrides = create_chempot_overrides(() => config, [
133
+ `formal_chempots`,
134
+ `label_stable`,
135
+ `element_padding`,
136
+ `default_min_limit`,
137
+ `formulas_to_draw`,
138
+ `draw_formula_meshes`,
139
+ `draw_formula_lines`,
140
+ `color_mode`,
141
+ `color_scale`,
142
+ `reverse_color_scale`,
143
+ ], { color_mode: `arity`, formulas_to_draw: [] })
144
+ const formal_chempots = $derived(overrides.resolve(`formal_chempots`))
145
+ const label_stable = $derived(overrides.resolve(`label_stable`))
146
+ const element_padding = $derived(overrides.resolve(`element_padding`))
147
+ const default_min_limit = $derived(overrides.resolve(`default_min_limit`))
148
+ const formulas_to_draw = $derived(overrides.resolve(`formulas_to_draw`))
149
+ const draw_formula_meshes = $derived(overrides.resolve(`draw_formula_meshes`))
150
+ const draw_formula_lines = $derived(overrides.resolve(`draw_formula_lines`))
151
+ const color_mode = $derived(overrides.resolve(`color_mode`))
152
+ const color_scale = $derived(overrides.resolve(`color_scale`))
153
+ const reverse_color_scale = $derived(overrides.resolve(`reverse_color_scale`))
146
154
  const show_tooltip = $derived(config.show_tooltip ?? CHEMPOT_DEFAULTS.show_tooltip)
147
155
  const tooltip_detail_level = $derived(
148
156
  config.tooltip_detail_level ?? CHEMPOT_DEFAULTS.tooltip_detail_level,
@@ -167,11 +175,7 @@
167
175
  return deduped
168
176
  }
169
177
 
170
- let wrapper = $state<HTMLDivElement>()
171
- let fullscreen = $state(false)
172
- let export_pane_open = $state(false)
173
178
  let formula_picker_open = $state(false)
174
- let controls_open = $state(false)
175
179
 
176
180
  // Mutual exclusion: only one pane open at a time.
177
181
  // Separate effects so each reacts to its own pane opening independently —
@@ -180,8 +184,6 @@
180
184
  $effect(() => { if (export_pane_open) { formula_picker_open = false; controls_open = false } })
181
185
  $effect(() => { if (formula_picker_open) { export_pane_open = false; controls_open = false } })
182
186
  $effect(() => { if (controls_open) { export_pane_open = false; formula_picker_open = false } })
183
- let copy_status = $state(false)
184
- let copy_timeout_id: ReturnType<typeof setTimeout> | null = null
185
187
  let container_width = $state(0)
186
188
  let container_height = $state(0)
187
189
  const base_aspect_ratio = $derived(height > 0 && width > 0 ? height / width : 1)
@@ -797,7 +799,7 @@
797
799
  for (const [pa, pb] of get_domain_edges(swizzled)) {
798
800
  const ka = pa.map((coord) => coord.toFixed(4)).join(`,`)
799
801
  const kb = pb.map((coord) => coord.toFixed(4)).join(`,`)
800
- const key = ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`
802
+ const key = edge_key(ka, kb)
801
803
  if (seen.has(key)) continue
802
804
  seen.add(key)
803
805
  positions.push(pa[0], pa[1], pa[2], pb[0], pb[1], pb[2])
@@ -934,7 +936,7 @@
934
936
  // coplanar neighbors, then assign each group to its most-common domain.
935
937
  if (n_faces > 1) {
936
938
  const tol = 1e-3
937
- const round = (v: number): number => Math.round(v / tol)
939
+ const round = (val: number): number => Math.round(val / tol)
938
940
  const vkey = (vert_idx: number): string =>
939
941
  `${round(pos.getX(vert_idx))},${round(pos.getY(vert_idx))},${
940
942
  round(pos.getZ(vert_idx))
@@ -974,12 +976,12 @@
974
976
  }
975
977
  // Union-find for coplanar adjacent faces
976
978
  const parent = Array.from({ length: n_faces }, (_, idx) => idx)
977
- const find = (x: number): number => {
978
- while (parent[x] !== x) {
979
- parent[x] = parent[parent[x]]
980
- x = parent[x]
979
+ const find = (node: number): number => {
980
+ while (parent[node] !== node) {
981
+ parent[node] = parent[parent[node]]
982
+ node = parent[node]
981
983
  }
982
- return x
984
+ return node
983
985
  }
984
986
  const union = (a_idx: number, b_idx: number): void => {
985
987
  const ra = find(a_idx), rb = find(b_idx)
@@ -1194,7 +1196,7 @@
1194
1196
 
1195
1197
  function get_touches_limits(
1196
1198
  points_3d: number[][],
1197
- lims: [number, number][],
1199
+ lims: Vec2[],
1198
1200
  ): string[] {
1199
1201
  const limit_tol = 1e-3
1200
1202
  const touches_limits: string[] = []
@@ -1486,10 +1488,10 @@
1486
1488
  // Place axis label just past the outer end of the axis (the end closer to 0).
1487
1489
  // In isometric 3D, the end near 0 projects outward at the front edge of the
1488
1490
  // bounding box, while the negative end projects inward toward the center.
1489
- const outer_end = (range: [number, number]): number =>
1491
+ const outer_end = (range: Vec2): number =>
1490
1492
  Math.abs(range[0]) <= Math.abs(range[1]) ? range[0] : range[1]
1491
1493
  // Direction from range center toward outer end (to extend the label beyond the grid)
1492
- const outer_direction = (range: [number, number]): number => {
1494
+ const outer_direction = (range: Vec2): number => {
1493
1495
  const end = outer_end(range)
1494
1496
  const mid = (range[0] + range[1]) / 2
1495
1497
  return end >= mid ? 1 : -1
@@ -1759,8 +1761,12 @@
1759
1761
  schedule_label_occlusion_update()
1760
1762
  })
1761
1763
 
1762
- $effect(() => {
1763
- set_fullscreen_bg(wrapper, fullscreen, `--chempot-3d-bg-fullscreen`)
1764
+ // Drive the browser Fullscreen API from the bindable `fullscreen` prop (parent-controllable), keep it in sync with Esc/external exits
1765
+ sync_fullscreen({
1766
+ get_wrapper: () => wrapper,
1767
+ get_fullscreen: () => fullscreen,
1768
+ set_fullscreen: (val) => (fullscreen = val),
1769
+ bg_css_var: `--chempot-3d-bg-fullscreen`,
1764
1770
  })
1765
1771
 
1766
1772
  $effect(() => {
@@ -1789,7 +1795,7 @@
1789
1795
  key: string
1790
1796
  pos: Vec3
1791
1797
  rot: Vec3
1792
- size: [number, number]
1798
+ size: Vec2
1793
1799
  color: string
1794
1800
  }[] = []
1795
1801
  if (projections.xy) {
@@ -1860,17 +1866,8 @@
1860
1866
  })
1861
1867
 
1862
1868
  function reset_controls(): void {
1863
- formal_chempots_override = null
1864
- label_stable_override = null
1865
- element_padding_override = null
1866
- default_min_limit_override = null
1867
- draw_formula_meshes_override = null
1868
- draw_formula_lines_override = null
1869
- color_mode_override = null
1870
- color_scale_override = null
1871
- reverse_color_scale_override = null
1869
+ overrides.reset()
1872
1870
  projection_elements_override = null
1873
- formulas_to_draw_override = null
1874
1871
  formula_filter_query = ``
1875
1872
  }
1876
1873
 
@@ -1902,270 +1899,94 @@
1902
1899
  const selected_formulas = new SvelteSet(formulas_to_draw)
1903
1900
  if (selected_formulas.has(formula)) selected_formulas.delete(formula)
1904
1901
  else selected_formulas.add(formula)
1905
- formulas_to_draw_override = [...selected_formulas]
1902
+ overrides.set(`formulas_to_draw`, [...selected_formulas])
1906
1903
  }
1907
1904
 
1908
1905
  function select_surface_formulas(): void {
1909
- formulas_to_draw_override = render_domains
1910
- .filter((domain) => surface_formulas.has(domain.formula))
1911
- .map((domain) => domain.formula)
1906
+ overrides.set(
1907
+ `formulas_to_draw`,
1908
+ render_domains
1909
+ .filter((domain) => surface_formulas.has(domain.formula))
1910
+ .map((domain) => domain.formula),
1911
+ )
1912
1912
  }
1913
1913
 
1914
1914
  function select_neighbor_formulas(): void {
1915
1915
  if (hover_info?.view !== `3d`) return
1916
1916
  const neighbors = domain_neighbors.get(hover_info.formula) ?? []
1917
- formulas_to_draw_override = [hover_info.formula, ...neighbors]
1918
- }
1919
-
1920
- function download_blob(blob: Blob, filename: string): void {
1921
- const url = URL.createObjectURL(blob)
1922
- const link = document.createElement(`a`)
1923
- link.href = url
1924
- link.download = filename
1925
- link.click()
1926
- URL.revokeObjectURL(url)
1917
+ overrides.set(`formulas_to_draw`, [hover_info.formula, ...neighbors])
1927
1918
  }
1928
1919
 
1929
1920
  let png_dpi = $state(150)
1930
1921
  const export_basename = $derived(`chempot-${plot_elements.join(`-`)}`)
1931
1922
 
1932
- function get_view_settings(): Record<string, unknown> {
1933
- const view_camera_position = orbit_controls_ref?.object?.position
1934
- const view_camera_target = orbit_controls_ref?.target
1935
- return {
1923
+ const current_view_settings = (): Record<string, unknown> =>
1924
+ get_view_settings({
1936
1925
  elements: plot_elements,
1937
1926
  camera_projection,
1938
1927
  auto_rotate,
1939
1928
  color_mode,
1940
1929
  color_scale,
1941
1930
  reverse_color_scale,
1942
- camera_position: view_camera_position
1943
- ? [view_camera_position.x, view_camera_position.y, view_camera_position.z]
1944
- : null,
1945
- camera_target: view_camera_target
1946
- ? [view_camera_target.x, view_camera_target.y, view_camera_target.z]
1947
- : null,
1948
- }
1949
- }
1950
-
1951
- interface OverlayTextItem {
1952
- x: number
1953
- y: number
1954
- text: string
1955
- font: string
1956
- font_size: string
1957
- font_family: string
1958
- font_weight: string
1959
- color: string
1960
- }
1961
- function get_overlay_text_items(canvas_rect: DOMRect): OverlayTextItem[] {
1962
- if (!wrapper) return []
1963
- const text_items: OverlayTextItem[] = []
1964
- for (
1965
- const element of wrapper.querySelectorAll(
1966
- `.tick-label, .axis-label, .domain-label`,
1967
- )
1968
- ) {
1969
- const html_element = element as HTMLElement
1970
- const style = getComputedStyle(html_element)
1971
- if (style.display === `none` || style.visibility === `hidden`) continue
1972
- const element_rect = html_element.getBoundingClientRect()
1973
- text_items.push({
1974
- x: element_rect.left + element_rect.width / 2 - canvas_rect.left,
1975
- y: element_rect.top + element_rect.height / 2 - canvas_rect.top,
1976
- text: html_element.textContent ?? ``,
1977
- font: style.font || `${style.fontSize} ${style.fontFamily}`,
1978
- font_size: style.fontSize || `11px`,
1979
- font_family: style.fontFamily || `sans-serif`,
1980
- font_weight: style.fontWeight || `400`,
1981
- color: style.color || `#333`,
1982
- })
1983
- }
1984
- return text_items
1985
- }
1986
-
1987
- function export_png_file(): void {
1988
- if (!wrapper) return
1989
- const gl_canvas = wrapper.querySelector(`canvas`)
1990
- if (!(gl_canvas instanceof HTMLCanvasElement)) return
1991
-
1992
- // Composite WebGL canvas + HTML overlay labels into a single image
1993
- const rect = gl_canvas.getBoundingClientRect()
1994
- const scale = Math.min(png_dpi / 72, 10)
1995
- const out = document.createElement(`canvas`)
1996
- out.width = Math.round(rect.width * scale)
1997
- out.height = Math.round(rect.height * scale)
1998
- const ctx = out.getContext(`2d`)
1999
- if (!ctx) return
2000
- ctx.scale(scale, scale)
2001
-
2002
- // Draw the WebGL canvas as background
2003
- ctx.drawImage(gl_canvas, 0, 0, rect.width, rect.height)
2004
-
2005
- // Draw all HTML overlay text (tick labels, axis labels, domain labels)
2006
- for (const text_item of get_overlay_text_items(rect)) {
2007
- ctx.font = text_item.font
2008
- ctx.fillStyle = text_item.color
2009
- ctx.textAlign = `center`
2010
- ctx.textBaseline = `middle`
2011
- ctx.fillText(text_item.text, text_item.x, text_item.y)
2012
- }
2013
-
2014
- out.toBlob((blob) => {
2015
- if (!blob) return
2016
- download_blob(blob, `${export_basename}.png`)
2017
- }, `image/png`)
2018
- }
2019
-
2020
- const xml_escape = (text: string): string =>
2021
- text
2022
- .replaceAll(`&`, `&amp;`)
2023
- .replaceAll(`<`, `&lt;`)
2024
- .replaceAll(`>`, `&gt;`)
2025
- .replaceAll(`"`, `&quot;`)
2026
- .replaceAll(`'`, `&#39;`)
2027
-
2028
- function export_svg_file(): void {
2029
- if (!wrapper) return
2030
- const gl_canvas = wrapper.querySelector(`canvas`)
2031
- if (!(gl_canvas instanceof HTMLCanvasElement)) return
2032
- const canvas_rect = gl_canvas.getBoundingClientRect()
2033
- if (canvas_rect.width === 0 || canvas_rect.height === 0) return
2034
- const png_data_url = gl_canvas.toDataURL(`image/png`)
2035
- const text_nodes = get_overlay_text_items(canvas_rect).map((text_item) =>
2036
- `<text x="${text_item.x.toFixed(2)}" y="${
2037
- text_item.y.toFixed(2)
2038
- }" text-anchor="middle" dominant-baseline="central" fill="${
2039
- xml_escape(text_item.color)
2040
- }" font-size="${xml_escape(text_item.font_size)}" font-family="${
2041
- xml_escape(text_item.font_family)
2042
- }" font-weight="${xml_escape(text_item.font_weight)}">${
2043
- xml_escape(text_item.text)
2044
- }</text>`
2045
- )
2046
- const metadata = xml_escape(JSON.stringify(get_view_settings()))
2047
- const svg = [
2048
- `<?xml version="1.0" encoding="UTF-8"?>`,
2049
- `<svg xmlns="http://www.w3.org/2000/svg" width="${canvas_rect.width}" height="${canvas_rect.height}" viewBox="0 0 ${canvas_rect.width} ${canvas_rect.height}">`,
2050
- `<metadata>${metadata}</metadata>`,
2051
- `<image href="${png_data_url}" x="0" y="0" width="${canvas_rect.width}" height="${canvas_rect.height}" />`,
2052
- ...text_nodes,
2053
- `</svg>`,
2054
- ].join(``)
2055
- download_blob(
2056
- new Blob([svg], { type: `image/svg+xml` }),
2057
- `${export_basename}.svg`,
2058
- )
2059
- }
2060
-
2061
- function export_view_json_file(): void {
2062
- const json_text = JSON.stringify(get_view_settings(), null, 2)
2063
- download_blob(
2064
- new Blob([json_text], { type: `application/json` }),
2065
- `${export_basename}-view.json`,
2066
- )
2067
- }
2068
-
2069
- function export_glb_file(): void {
2070
- const gltf_exporter = new GLTFExporter()
2071
- const export_root = new THREE.Group()
2072
- if (colored_hull_geometry) {
2073
- export_root.add(
2074
- new THREE.Mesh(
2075
- colored_hull_geometry.clone(),
2076
- new THREE.MeshBasicMaterial({
2077
- vertexColors: true,
2078
- transparent: true,
2079
- opacity: color_mode === `none` ? 0.25 : 0.4,
2080
- side: THREE.DoubleSide,
2081
- }),
2082
- ),
2083
- )
2084
- }
2085
- export_root.add(
2086
- new THREE.LineSegments(
2087
- edge_geometry.clone(),
2088
- new THREE.LineBasicMaterial({ color: 0x333333 }),
2089
- ),
2090
- )
2091
- for (const { geometry, color } of formula_mesh_data) {
2092
- export_root.add(
2093
- new THREE.Mesh(
2094
- geometry.clone(),
2095
- new THREE.MeshBasicMaterial({
2096
- color: new THREE.Color(color),
2097
- transparent: true,
2098
- opacity: 0.13,
2099
- side: THREE.DoubleSide,
2100
- }),
2101
- ),
2102
- )
2103
- }
2104
- if (draw_formula_lines) {
2105
- for (const { geometry, color } of formula_edge_data) {
2106
- export_root.add(
2107
- new THREE.LineSegments(
2108
- geometry.clone(),
2109
- new THREE.LineBasicMaterial({ color: new THREE.Color(color) }),
2110
- ),
2111
- )
2112
- }
2113
- }
2114
- gltf_exporter.parse(
2115
- export_root,
2116
- (result) => {
2117
- if (!(result instanceof ArrayBuffer)) return
2118
- download_blob(
2119
- new Blob([result], { type: `model/gltf-binary` }),
2120
- `${export_basename}.glb`,
2121
- )
2122
- },
2123
- (err) => {
2124
- console.error(`Failed to export GLB:`, err)
2125
- },
2126
- { binary: true, onlyVisible: false },
2127
- )
2128
- }
2129
-
2130
- const get_json_string = (): string =>
2131
- JSON.stringify(
2132
- {
2133
- elements: diagram_data?.elements ?? [],
2134
- domains: render_domains.map((domain) => ({
2135
- formula: domain.formula,
2136
- points_3d: domain.points_3d,
2137
- })),
2138
- lims: diagram_data?.lims ?? [],
2139
- view: get_view_settings(),
2140
- },
2141
- null,
2142
- 2,
2143
- )
1931
+ camera_position: orbit_controls_ref?.object?.position ?? null,
1932
+ camera_target: orbit_controls_ref?.target ?? null,
1933
+ })
2144
1934
 
2145
- function export_json_file(): void {
2146
- download_blob(
2147
- new Blob([get_json_string()], { type: `application/json` }),
2148
- `${export_basename}.json`,
2149
- )
2150
- }
1935
+ const export_json_payload = (): Record<string, unknown> => ({
1936
+ elements: diagram_data?.elements ?? [],
1937
+ domains: render_domains.map((domain) => ({
1938
+ formula: domain.formula,
1939
+ points_3d: domain.points_3d,
1940
+ })),
1941
+ lims: diagram_data?.lims ?? [],
1942
+ view: current_view_settings(),
1943
+ })
2151
1944
 
2152
- async function copy_json(): Promise<void> {
2153
- try {
2154
- await navigator.clipboard.writeText(get_json_string())
2155
- copy_status = true
2156
- } catch (err) {
2157
- copy_status = false
2158
- console.error(`Failed to copy JSON to clipboard:`, err)
2159
- }
2160
- if (copy_timeout_id !== null) clearTimeout(copy_timeout_id)
2161
- copy_timeout_id = setTimeout(() => {
2162
- copy_status = false
2163
- copy_timeout_id = null
2164
- }, 1000)
2165
- }
1945
+ const export_sections = $derived<ExportSection[]>([
1946
+ {
1947
+ title: `Export Image`,
1948
+ items: [
1949
+ {
1950
+ label: `SVG`,
1951
+ on_download: () =>
1952
+ export_svg_file(wrapper, export_basename, current_view_settings()),
1953
+ },
1954
+ {
1955
+ label: `PNG`,
1956
+ show_dpi: true,
1957
+ on_download: () => export_png_file(wrapper, export_basename, png_dpi),
1958
+ },
1959
+ ],
1960
+ },
1961
+ {
1962
+ title: `Export Data`,
1963
+ items: [
1964
+ {
1965
+ label: `JSON`,
1966
+ on_download: () => export_json_file(export_json_payload(), export_basename),
1967
+ copy_text: () => get_json_string(export_json_payload()),
1968
+ },
1969
+ {
1970
+ label: `View`,
1971
+ on_download: () =>
1972
+ export_view_json_file(current_view_settings(), export_basename),
1973
+ },
1974
+ {
1975
+ label: `GLB`,
1976
+ on_download: () =>
1977
+ export_glb_file({
1978
+ hull_geometry: colored_hull_geometry,
1979
+ hull_opacity: color_mode === `none` ? 0.25 : 0.4,
1980
+ edge_geometry,
1981
+ formula_meshes: formula_mesh_data,
1982
+ formula_edges: draw_formula_lines ? formula_edge_data : [],
1983
+ }, export_basename),
1984
+ },
1985
+ ],
1986
+ },
1987
+ ])
2166
1988
 
2167
1989
  onDestroy(() => {
2168
- if (copy_timeout_id !== null) clearTimeout(copy_timeout_id)
2169
1990
  if (label_occlusion_frame !== null) cancelAnimationFrame(label_occlusion_frame)
2170
1991
  })
2171
1992
 
@@ -2212,25 +2033,13 @@
2212
2033
  }
2213
2034
 
2214
2035
  // Color mode cycling (keyboard shortcut 'c')
2215
- const color_modes: ChemPotColorMode[] = [
2216
- `none`,
2217
- `energy`,
2218
- `formation_energy`,
2219
- `arity`,
2220
- `entries`,
2221
- ]
2036
+ const color_modes = CHEMPOT_COLOR_MODE_OPTIONS.map(([value]) => value)
2222
2037
  function cycle_color_mode(): void {
2223
2038
  const idx = color_modes.indexOf(color_mode)
2224
- color_mode_override = color_modes[(idx + 1) % color_modes.length]
2039
+ overrides.set(`color_mode`, color_modes[(idx + 1) % color_modes.length])
2225
2040
  }
2226
2041
  </script>
2227
2042
 
2228
- <svelte:document
2229
- onfullscreenchange={() => {
2230
- fullscreen = document.fullscreenElement === wrapper
2231
- }}
2232
- />
2233
-
2234
2043
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
2235
2044
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
2236
2045
  <div
@@ -2250,7 +2059,7 @@
2250
2059
  ) return
2251
2060
  if (event.key === `Escape`) clear_hover_lock()
2252
2061
  else if (event.key === `c`) cycle_color_mode()
2253
- else if (event.key === `f`) toggle_fullscreen(wrapper)
2062
+ else if (event.key === `f` && fullscreen_toggle) toggle_fullscreen(wrapper)
2254
2063
  }}
2255
2064
  onpointerdown={(event) => {
2256
2065
  const target = event.target
@@ -2263,72 +2072,16 @@
2263
2072
  }}
2264
2073
  >
2265
2074
  <section>
2266
- <DraggablePane
2267
- bind:show={export_pane_open}
2268
- open_icon="Cross"
2269
- closed_icon="Export"
2075
+ <ExportPane
2076
+ bind:export_pane_open
2077
+ bind:png_dpi
2078
+ sections={export_sections}
2270
2079
  pane_props={{ class: `chempot-export-pane` }}
2271
2080
  toggle_props={{
2272
2081
  class: `chempot-export-toggle`,
2273
2082
  title: `Export chemical potential diagram`,
2274
2083
  }}
2275
- >
2276
- <h4 id="export-image">Export Image</h4>
2277
- <div class="export-row">
2278
- <label>
2279
- SVG
2280
- <button type="button" onclick={export_svg_file} title="SVG snapshot export">
2281
-
2282
- </button>
2283
- </label>
2284
- <label>
2285
- PNG
2286
- <button type="button" onclick={export_png_file} title="PNG ({png_dpi} DPI)">
2287
-
2288
- </button>
2289
- &nbsp;(DPI: <input
2290
- type="number"
2291
- min={50}
2292
- max={500}
2293
- bind:value={png_dpi}
2294
- title="Export resolution in dots per inch"
2295
- style="margin: 0 0 0 2pt"
2296
- />)
2297
- </label>
2298
- </div>
2299
- <h4 id="export-data">Export Data</h4>
2300
- <div class="export-row">
2301
- <label>
2302
- JSON
2303
- <button type="button" onclick={export_json_file} aria-label="Download JSON">
2304
-
2305
- </button>
2306
- <button
2307
- type="button"
2308
- onclick={copy_json}
2309
- aria-label="Copy JSON to clipboard"
2310
- >
2311
- {copy_status ? `✅` : `📋`}
2312
- </button>
2313
- </label>
2314
- <label>
2315
- View
2316
- <button
2317
- type="button"
2318
- onclick={export_view_json_file}
2319
- aria-label="Download view JSON"
2320
- >
2321
-
2322
- </button>
2323
- </label>
2324
- <label>
2325
- GLB
2326
- <button type="button" onclick={export_glb_file} aria-label="Download GLB">
2327
-
2328
- </button>
2329
- </label>
2330
- </div>
2331
- </DraggablePane>
2084
+ />
2332
2085
  <DraggablePane
2333
2086
  bind:show={formula_picker_open}
2334
2087
  open_icon="Cross"
@@ -2341,7 +2094,7 @@
2341
2094
  >
2342
2095
  <h4 id="formula-overlays">Formula Overlays</h4>
2343
2096
  <div class="overlay-actions">
2344
- <button type="button" onclick={() => formulas_to_draw_override = []}>
2097
+ <button type="button" onclick={() => overrides.set(`formulas_to_draw`, [])}>
2345
2098
  Clear
2346
2099
  </button>
2347
2100
  <button type="button" onclick={select_surface_formulas}>Surface</button>
@@ -2464,36 +2217,29 @@
2464
2217
  <input
2465
2218
  type="checkbox"
2466
2219
  checked={formal_chempots}
2467
- onchange={() => {
2468
- formal_chempots_override = !formal_chempots
2469
- }}
2220
+ onchange={() => overrides.set(`formal_chempots`, !formal_chempots)}
2470
2221
  /> Formal
2471
2222
  </label>
2472
2223
  <label>
2473
2224
  <input
2474
2225
  type="checkbox"
2475
2226
  checked={label_stable}
2476
- onchange={() => {
2477
- label_stable_override = !label_stable
2478
- }}
2227
+ onchange={() => overrides.set(`label_stable`, !label_stable)}
2479
2228
  /> Labels
2480
2229
  </label>
2481
2230
  <label>
2482
2231
  <input
2483
2232
  type="checkbox"
2484
2233
  checked={draw_formula_meshes}
2485
- onchange={() => {
2486
- draw_formula_meshes_override = !draw_formula_meshes
2487
- }}
2234
+ onchange={() =>
2235
+ overrides.set(`draw_formula_meshes`, !draw_formula_meshes)}
2488
2236
  /> Meshes
2489
2237
  </label>
2490
2238
  <label>
2491
2239
  <input
2492
2240
  type="checkbox"
2493
2241
  checked={draw_formula_lines}
2494
- onchange={() => {
2495
- draw_formula_lines_override = !draw_formula_lines
2496
- }}
2242
+ onchange={() => overrides.set(`draw_formula_lines`, !draw_formula_lines)}
2497
2243
  /> Lines
2498
2244
  </label>
2499
2245
  </div>
@@ -2505,9 +2251,8 @@
2505
2251
  min="0"
2506
2252
  step="0.1"
2507
2253
  value={element_padding}
2508
- oninput={(event) => {
2509
- element_padding_override = Number(event.currentTarget.value)
2510
- }}
2254
+ oninput={(event) =>
2255
+ overrides.set(`element_padding`, Number(event.currentTarget.value))}
2511
2256
  />
2512
2257
  </label>
2513
2258
  <label>
@@ -2517,11 +2262,8 @@
2517
2262
  max="0"
2518
2263
  step="1"
2519
2264
  value={default_min_limit}
2520
- oninput={(event) => {
2521
- default_min_limit_override = Number(
2522
- event.currentTarget.value,
2523
- )
2524
- }}
2265
+ oninput={(event) =>
2266
+ overrides.set(`default_min_limit`, Number(event.currentTarget.value))}
2525
2267
  />
2526
2268
  </label>
2527
2269
  </div>
@@ -2530,16 +2272,12 @@
2530
2272
  <select
2531
2273
  id="chempot-color-mode"
2532
2274
  value={color_mode}
2533
- onchange={(event) => {
2534
- color_mode_override = event.currentTarget
2535
- .value as ChemPotColorMode
2536
- }}
2275
+ onchange={(event) =>
2276
+ overrides.set(`color_mode`, event.currentTarget.value as ChemPotColorMode)}
2537
2277
  >
2538
- <option value="none">None</option>
2539
- <option value="energy">Energy/atom</option>
2540
- <option value="formation_energy">Formation energy</option>
2541
- <option value="arity">Element count</option>
2542
- <option value="entries">Entry count</option>
2278
+ {#each CHEMPOT_COLOR_MODE_OPTIONS as [value, label] (value)}
2279
+ <option {value}>{label}</option>
2280
+ {/each}
2543
2281
  </select>
2544
2282
  </div>
2545
2283
  {#if color_mode !== `none` && color_mode !== `arity`}
@@ -2548,27 +2286,22 @@
2548
2286
  <select
2549
2287
  id="chempot-color-scale"
2550
2288
  value={color_scale}
2551
- onchange={(event) => {
2552
- color_scale_override = event.currentTarget
2553
- .value as D3InterpolateName
2554
- }}
2289
+ onchange={(event) =>
2290
+ overrides.set(
2291
+ `color_scale`,
2292
+ event.currentTarget.value as D3InterpolateName,
2293
+ )}
2555
2294
  >
2556
- <option value="interpolateViridis">Viridis</option>
2557
- <option value="interpolatePlasma">Plasma</option>
2558
- <option value="interpolateInferno">Inferno</option>
2559
- <option value="interpolateMagma">Magma</option>
2560
- <option value="interpolateCividis">Cividis</option>
2561
- <option value="interpolateTurbo">Turbo</option>
2562
- <option value="interpolateRdYlBu">RdYlBu</option>
2563
- <option value="interpolateSpectral">Spectral</option>
2295
+ {#each CHEMPOT_COLOR_SCALE_OPTIONS as [value, label] (value)}
2296
+ <option {value}>{label}</option>
2297
+ {/each}
2564
2298
  </select>
2565
2299
  <label>
2566
2300
  <input
2567
2301
  type="checkbox"
2568
2302
  checked={reverse_color_scale}
2569
- onchange={() => {
2570
- reverse_color_scale_override = !reverse_color_scale
2571
- }}
2303
+ onchange={() =>
2304
+ overrides.set(`reverse_color_scale`, !reverse_color_scale)}
2572
2305
  /> Rev
2573
2306
  </label>
2574
2307
  </div>
@@ -2576,13 +2309,9 @@
2576
2309
  </SettingsSection>
2577
2310
  </ScatterPlot3DControls>
2578
2311
 
2579
- <button
2580
- type="button"
2581
- onclick={() => toggle_fullscreen(wrapper)}
2582
- title="{fullscreen ? `Exit` : `Enter`} fullscreen"
2583
- >
2584
- <Icon icon="{fullscreen ? `Exit` : ``}Fullscreen" />
2585
- </button>
2312
+ {#if fullscreen_toggle}
2313
+ <FullscreenButton {fullscreen} toggle={fullscreen_toggle} {wrapper} />
2314
+ {/if}
2586
2315
  </section>
2587
2316
  {#if show_temperature_slider && temperature !== undefined}
2588
2317
  <TemperatureSlider
@@ -2963,23 +2692,6 @@
2963
2692
  gap: 4pt;
2964
2693
  font-size: 0.9em;
2965
2694
  }
2966
- .chempot-diagram-3d :global(.export-row) {
2967
- display: flex;
2968
- flex-wrap: wrap;
2969
- gap: 4pt 10pt;
2970
- margin: 0 0 4pt;
2971
- }
2972
- .chempot-diagram-3d :global(.export-row > label) {
2973
- margin: 0;
2974
- }
2975
- .chempot-diagram-3d :global(.export-row button) {
2976
- width: 1.4em;
2977
- height: 1.4em;
2978
- padding: 0;
2979
- display: inline-flex;
2980
- align-items: center;
2981
- justify-content: center;
2982
- }
2983
2695
  .chempot-diagram-3d :global(.chempot-checks) {
2984
2696
  display: flex;
2985
2697
  flex-wrap: wrap;