matterviz 0.4.1 → 0.4.3

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 (499) hide show
  1. package/dist/EmptyState.svelte +10 -4
  2. package/dist/FilePicker.svelte +48 -60
  3. package/dist/Icon.svelte +7 -4
  4. package/dist/MillerIndexInput.svelte +2 -2
  5. package/dist/api/optimade.js +10 -7
  6. package/dist/app.css +68 -0
  7. package/dist/brillouin/BrillouinZone.svelte +90 -91
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
  9. package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
  11. package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
  12. package/dist/brillouin/BrillouinZoneScene.svelte +46 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  14. package/dist/brillouin/compute.js +10 -16
  15. package/dist/brillouin/geometry.js +2 -2
  16. package/dist/chempot-diagram/ChemPotDiagram.svelte +48 -50
  17. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +61 -169
  18. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
  19. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +559 -755
  20. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
  21. package/dist/chempot-diagram/async-compute.svelte.js +13 -14
  22. package/dist/chempot-diagram/color.d.ts +21 -1
  23. package/dist/chempot-diagram/color.js +70 -1
  24. package/dist/chempot-diagram/compute.d.ts +7 -0
  25. package/dist/chempot-diagram/compute.js +85 -26
  26. package/dist/chempot-diagram/controls-state.svelte.js +1 -1
  27. package/dist/chempot-diagram/export.js +1 -6
  28. package/dist/chempot-diagram/temperature.js +9 -10
  29. package/dist/colors/index.d.ts +0 -4
  30. package/dist/colors/index.js +19 -20
  31. package/dist/composition/BarChart.svelte +30 -26
  32. package/dist/composition/BubbleChart.svelte +17 -17
  33. package/dist/composition/Composition.svelte +10 -19
  34. package/dist/composition/Formula.svelte +20 -27
  35. package/dist/composition/FormulaFilter.svelte +115 -184
  36. package/dist/composition/PieChart.svelte +55 -41
  37. package/dist/composition/format.js +7 -9
  38. package/dist/composition/index.d.ts +2 -0
  39. package/dist/composition/index.js +14 -0
  40. package/dist/composition/parse.d.ts +2 -2
  41. package/dist/composition/parse.js +61 -115
  42. package/dist/constants.d.ts +0 -1
  43. package/dist/constants.js +17 -26
  44. package/dist/convex-hull/ConvexHull.svelte +12 -15
  45. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHull2D.svelte +93 -94
  47. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  48. package/dist/convex-hull/ConvexHull3D.svelte +127 -186
  49. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  50. package/dist/convex-hull/ConvexHull4D.svelte +131 -180
  51. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  52. package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
  53. package/dist/convex-hull/ConvexHullControls.svelte +46 -48
  54. package/dist/convex-hull/ConvexHullStats.svelte +193 -195
  55. package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
  56. package/dist/convex-hull/GasPressureControls.svelte +5 -12
  57. package/dist/convex-hull/StructurePopup.svelte +18 -22
  58. package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
  59. package/dist/convex-hull/TemperatureSlider.svelte +4 -10
  60. package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
  61. package/dist/convex-hull/gas-thermodynamics.js +21 -53
  62. package/dist/convex-hull/helpers.d.ts +8 -10
  63. package/dist/convex-hull/helpers.js +37 -43
  64. package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
  65. package/dist/convex-hull/thermodynamics.js +37 -44
  66. package/dist/coordination/CoordinationBarPlot.svelte +49 -53
  67. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  68. package/dist/coordination/calc-coordination.d.ts +1 -1
  69. package/dist/coordination/calc-coordination.js +28 -25
  70. package/dist/element/BohrAtom.svelte +5 -9
  71. package/dist/element/ElementHeading.svelte +4 -1
  72. package/dist/element/ElementPhoto.svelte +11 -9
  73. package/dist/element/ElementStats.svelte +12 -10
  74. package/dist/element/ElementTile.svelte +44 -67
  75. package/dist/element/Nucleus.svelte +4 -2
  76. package/dist/element/data.d.ts +3 -2
  77. package/dist/element/data.js +5 -1
  78. package/dist/element/helpers.d.ts +4 -0
  79. package/dist/element/helpers.js +18 -0
  80. package/dist/element/index.d.ts +2 -5
  81. package/dist/element/index.js +4 -19
  82. package/dist/feedback/DragOverlay.svelte +5 -5
  83. package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
  84. package/dist/feedback/StatusMessage.svelte +7 -4
  85. package/dist/fermi-surface/FermiSlice.svelte +21 -18
  86. package/dist/fermi-surface/FermiSurface.svelte +35 -44
  87. package/dist/fermi-surface/FermiSurface.svelte.d.ts +7 -13
  88. package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
  89. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  90. package/dist/fermi-surface/FermiSurfaceScene.svelte +64 -136
  91. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  92. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
  93. package/dist/fermi-surface/compute.js +25 -58
  94. package/dist/fermi-surface/parse.js +54 -88
  95. package/dist/file-viewer/JsonBrowser.svelte +1043 -0
  96. package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
  97. package/dist/file-viewer/PlotPanel.svelte +350 -0
  98. package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
  99. package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
  100. package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
  101. package/dist/file-viewer/detect.d.ts +11 -0
  102. package/dist/file-viewer/detect.js +381 -0
  103. package/dist/file-viewer/eligibility.d.ts +4 -0
  104. package/dist/file-viewer/eligibility.js +53 -0
  105. package/dist/file-viewer/host-protocol.d.ts +50 -0
  106. package/dist/file-viewer/host-transfer.d.ts +25 -0
  107. package/dist/file-viewer/host-transfer.js +42 -0
  108. package/dist/file-viewer/index.d.ts +5 -0
  109. package/dist/file-viewer/index.js +5 -0
  110. package/dist/file-viewer/main.d.ts +44 -0
  111. package/dist/file-viewer/main.js +535 -0
  112. package/dist/file-viewer/parse.d.ts +9 -0
  113. package/dist/file-viewer/parse.js +170 -0
  114. package/dist/file-viewer/plot-utils.d.ts +24 -0
  115. package/dist/file-viewer/plot-utils.js +191 -0
  116. package/dist/file-viewer/types.d.ts +4 -0
  117. package/dist/file-viewer/types.js +5 -0
  118. package/dist/heatmap-matrix/HeatmapMatrix.svelte +153 -219
  119. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
  120. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
  121. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  122. package/dist/heatmap-matrix/index.d.ts +5 -0
  123. package/dist/icons.d.ts +4 -0
  124. package/dist/icons.js +4 -0
  125. package/dist/index.d.ts +2 -2
  126. package/dist/index.js +1 -1
  127. package/dist/io/ExportPane.svelte +11 -7
  128. package/dist/io/decompress.d.ts +4 -1
  129. package/dist/io/decompress.js +24 -36
  130. package/dist/io/export.d.ts +11 -4
  131. package/dist/io/export.js +75 -19
  132. package/dist/io/fetch.d.ts +0 -3
  133. package/dist/io/fetch.js +0 -11
  134. package/dist/io/file-drop.js +60 -35
  135. package/dist/io/index.d.ts +0 -1
  136. package/dist/io/index.js +2 -9
  137. package/dist/io/is-binary.d.ts +9 -0
  138. package/dist/io/is-binary.js +45 -1
  139. package/dist/io/url-drop.d.ts +2 -0
  140. package/dist/io/url-drop.js +40 -40
  141. package/dist/isosurface/Isosurface.svelte +700 -215
  142. package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
  143. package/dist/isosurface/IsosurfaceControls.svelte +590 -116
  144. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
  145. package/dist/isosurface/VolumeSlice.svelte +224 -0
  146. package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
  147. package/dist/isosurface/coloring.d.ts +20 -0
  148. package/dist/isosurface/coloring.js +118 -0
  149. package/dist/isosurface/geometry-worker-types.d.ts +38 -0
  150. package/dist/isosurface/geometry.worker.d.ts +1 -0
  151. package/dist/isosurface/geometry.worker.js +59 -0
  152. package/dist/isosurface/grid.d.ts +10 -0
  153. package/dist/isosurface/grid.js +31 -0
  154. package/dist/isosurface/index.d.ts +4 -0
  155. package/dist/isosurface/index.js +4 -0
  156. package/dist/isosurface/parse-vaspwave.d.ts +3 -0
  157. package/dist/isosurface/parse-vaspwave.js +138 -0
  158. package/dist/isosurface/parse.js +6 -6
  159. package/dist/isosurface/profile.d.ts +12 -0
  160. package/dist/isosurface/profile.js +19 -0
  161. package/dist/isosurface/sampling.d.ts +34 -0
  162. package/dist/isosurface/sampling.js +408 -0
  163. package/dist/isosurface/slice-rendering.d.ts +13 -0
  164. package/dist/isosurface/slice-rendering.js +79 -0
  165. package/dist/isosurface/slice.d.ts +22 -2
  166. package/dist/isosurface/slice.js +200 -116
  167. package/dist/isosurface/types.d.ts +24 -1
  168. package/dist/isosurface/types.js +120 -8
  169. package/dist/json-path.d.ts +4 -0
  170. package/dist/json-path.js +89 -0
  171. package/dist/labels.d.ts +6 -2
  172. package/dist/labels.js +44 -14
  173. package/dist/layout/FullscreenButton.svelte +2 -1
  174. package/dist/layout/FullscreenToggle.svelte +10 -4
  175. package/dist/layout/InfoCard.svelte +8 -21
  176. package/dist/layout/InfoTag.svelte +1 -3
  177. package/dist/layout/NumberRangeInput.svelte +46 -0
  178. package/dist/layout/NumberRangeInput.svelte.d.ts +12 -0
  179. package/dist/layout/PropertyFilter.svelte +2 -6
  180. package/dist/layout/SettingsSection.svelte +57 -62
  181. package/dist/layout/SettingsSection.svelte.d.ts +6 -7
  182. package/dist/layout/SubpageGrid.svelte +3 -1
  183. package/dist/layout/ViewerChrome.svelte +16 -11
  184. package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
  185. package/dist/layout/fullscreen.js +3 -5
  186. package/dist/layout/fullscreen.svelte.js +1 -2
  187. package/dist/layout/index.d.ts +1 -0
  188. package/dist/layout/index.js +1 -0
  189. package/dist/layout/json-tree/JsonNode.svelte +22 -59
  190. package/dist/layout/json-tree/JsonTree.svelte +101 -172
  191. package/dist/layout/json-tree/JsonValue.svelte +9 -32
  192. package/dist/layout/json-tree/utils.d.ts +0 -3
  193. package/dist/layout/json-tree/utils.js +50 -188
  194. package/dist/marching-cubes.d.ts +8 -1
  195. package/dist/marching-cubes.js +115 -48
  196. package/dist/math.d.ts +5 -2
  197. package/dist/math.js +48 -38
  198. package/dist/overlays/ContextMenu.svelte +19 -34
  199. package/dist/overlays/CopyButton.svelte +11 -5
  200. package/dist/overlays/DragControlTab.svelte +6 -1
  201. package/dist/overlays/DraggablePane.svelte +110 -74
  202. package/dist/overlays/DraggablePane.svelte.d.ts +2 -1
  203. package/dist/overlays/GlassChip.svelte +32 -0
  204. package/dist/overlays/GlassChip.svelte.d.ts +8 -0
  205. package/dist/overlays/InfoPaneCards.svelte +11 -23
  206. package/dist/overlays/index.d.ts +7 -0
  207. package/dist/overlays/index.js +26 -0
  208. package/dist/overlays/portal.d.ts +2 -0
  209. package/dist/overlays/portal.js +14 -0
  210. package/dist/periodic-table/PeriodicTable.svelte +113 -145
  211. package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
  212. package/dist/periodic-table/PeriodicTableControls.svelte +37 -115
  213. package/dist/periodic-table/TableInset.svelte +9 -4
  214. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +68 -105
  215. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
  216. package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
  217. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  218. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  219. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
  220. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
  221. package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
  222. package/dist/phase-diagram/build-diagram.js +4 -9
  223. package/dist/phase-diagram/diagram-input.d.ts +1 -2
  224. package/dist/phase-diagram/parse.js +15 -15
  225. package/dist/phase-diagram/svg-to-diagram.js +28 -27
  226. package/dist/phase-diagram/utils.js +2 -3
  227. package/dist/plot/bar/BarPlot.svelte +461 -426
  228. package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
  229. package/dist/plot/bar/BarPlotControls.svelte +1 -3
  230. package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
  231. package/dist/plot/bar/SpacegroupBarPlot.svelte +46 -77
  232. package/dist/plot/bar/data.d.ts +5 -4
  233. package/dist/plot/bar/data.js +2 -1
  234. package/dist/plot/box/BoxPlot.svelte +322 -226
  235. package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
  236. package/dist/plot/box/Violin.svelte.d.ts +1 -0
  237. package/dist/plot/box/box-plot.js +1 -1
  238. package/dist/plot/core/auto-place.d.ts +1 -1
  239. package/dist/plot/core/auto-place.js +2 -2
  240. package/dist/plot/core/axis-utils.d.ts +12 -7
  241. package/dist/plot/core/axis-utils.js +48 -48
  242. package/dist/plot/core/components/AxisLabel.svelte +82 -20
  243. package/dist/plot/core/components/AxisLabel.svelte.d.ts +1 -0
  244. package/dist/plot/core/components/ColorBar.svelte +101 -106
  245. package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
  246. package/dist/plot/core/components/FillArea.svelte +10 -17
  247. package/dist/plot/core/components/HierarchyControls.svelte +249 -0
  248. package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
  249. package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
  250. package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
  251. package/dist/plot/core/components/Line.svelte +18 -9
  252. package/dist/plot/core/components/Line.svelte.d.ts +2 -0
  253. package/dist/plot/core/components/PlotAxis.svelte +40 -17
  254. package/dist/plot/core/components/PlotControls.svelte +65 -41
  255. package/dist/plot/core/components/PlotLegend.svelte +31 -35
  256. package/dist/plot/core/components/PlotMarginals.svelte +616 -0
  257. package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
  258. package/dist/plot/core/components/PlotTooltip.svelte +1 -1
  259. package/dist/plot/core/components/PortalSelect.svelte +4 -8
  260. package/dist/plot/core/components/ReferenceLine.svelte +27 -24
  261. package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
  262. package/dist/plot/core/components/ReferencePlane.svelte +7 -8
  263. package/dist/plot/core/components/ZeroLines.svelte +4 -8
  264. package/dist/plot/core/components/ZoomRect.svelte +1 -2
  265. package/dist/plot/core/components/index.d.ts +1 -0
  266. package/dist/plot/core/components/index.js +1 -0
  267. package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
  268. package/dist/plot/core/data-cleaning-signal.js +467 -0
  269. package/dist/plot/core/data-cleaning.d.ts +2 -42
  270. package/dist/plot/core/data-cleaning.js +27 -474
  271. package/dist/plot/core/data-transform.d.ts +1 -2
  272. package/dist/plot/core/data-transform.js +9 -15
  273. package/dist/plot/core/fill-utils.d.ts +3 -1
  274. package/dist/plot/core/fill-utils.js +28 -6
  275. package/dist/plot/core/index.d.ts +1 -0
  276. package/dist/plot/core/index.js +1 -0
  277. package/dist/plot/core/interactions.js +0 -2
  278. package/dist/plot/core/layout.d.ts +15 -6
  279. package/dist/plot/core/layout.js +57 -38
  280. package/dist/plot/core/marginals.d.ts +170 -0
  281. package/dist/plot/core/marginals.js +436 -0
  282. package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
  283. package/dist/plot/core/pan-zoom.svelte.js +34 -17
  284. package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
  285. package/dist/plot/core/placed-tween.svelte.js +68 -21
  286. package/dist/plot/core/reference-line.js +1 -1
  287. package/dist/plot/core/scales.d.ts +23 -0
  288. package/dist/plot/core/scales.js +39 -13
  289. package/dist/plot/core/svg.js +0 -1
  290. package/dist/plot/core/types/fills.d.ts +101 -0
  291. package/dist/plot/core/types/fills.js +15 -0
  292. package/dist/plot/core/types/plot-3d.d.ts +87 -0
  293. package/dist/plot/core/types/plot-3d.js +4 -0
  294. package/dist/plot/core/types/reference-lines.d.ts +140 -0
  295. package/dist/plot/core/types/reference-lines.js +9 -0
  296. package/dist/plot/core/types.d.ts +29 -324
  297. package/dist/plot/core/types.js +23 -20
  298. package/dist/plot/core/utils/hierarchy-chart.d.ts +52 -0
  299. package/dist/plot/core/utils/hierarchy-chart.js +191 -0
  300. package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
  301. package/dist/plot/core/utils/hierarchy-labels.js +100 -0
  302. package/dist/plot/core/utils/label-placement.js +2 -2
  303. package/dist/plot/histogram/Histogram.svelte +314 -340
  304. package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
  305. package/dist/plot/histogram/HistogramControls.svelte +30 -28
  306. package/dist/plot/histogram/histogram.d.ts +31 -0
  307. package/dist/plot/histogram/histogram.js +70 -0
  308. package/dist/plot/histogram/index.d.ts +1 -0
  309. package/dist/plot/histogram/index.js +1 -0
  310. package/dist/plot/index.d.ts +1 -0
  311. package/dist/plot/index.js +1 -0
  312. package/dist/plot/sankey/Sankey.svelte +78 -69
  313. package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
  314. package/dist/plot/sankey/SankeyControls.svelte +22 -16
  315. package/dist/plot/sankey/sankey.js +13 -9
  316. package/dist/plot/scatter/BinnedScatterPlot.svelte +311 -141
  317. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
  318. package/dist/plot/scatter/ElementScatter.svelte +4 -1
  319. package/dist/plot/scatter/ScatterPlot.svelte +517 -410
  320. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
  321. package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
  322. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
  323. package/dist/plot/scatter/ScatterPoint.svelte +16 -9
  324. package/dist/plot/scatter/adaptive-density.d.ts +1 -1
  325. package/dist/plot/scatter/adaptive-density.js +36 -20
  326. package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
  327. package/dist/plot/scatter/binned-scatter-types.js +6 -1
  328. package/dist/plot/scatter/scatter-data.d.ts +1 -1
  329. package/dist/plot/scatter/scatter-data.js +8 -7
  330. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
  331. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
  332. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
  333. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
  334. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
  335. package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
  336. package/dist/plot/sunburst/Sunburst.svelte +269 -347
  337. package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
  338. package/dist/plot/sunburst/index.d.ts +1 -1
  339. package/dist/plot/sunburst/index.js +2 -1
  340. package/dist/plot/sunburst/render.d.ts +2 -2
  341. package/dist/plot/sunburst/render.js +47 -22
  342. package/dist/plot/sunburst/sunburst.d.ts +5 -1
  343. package/dist/plot/sunburst/sunburst.js +10 -3
  344. package/dist/plot/treemap/Treemap.svelte +1022 -0
  345. package/dist/plot/treemap/Treemap.svelte.d.ts +90 -0
  346. package/dist/plot/treemap/index.d.ts +4 -0
  347. package/dist/plot/treemap/index.js +4 -0
  348. package/dist/plot/treemap/labels.d.ts +38 -0
  349. package/dist/plot/treemap/labels.js +80 -0
  350. package/dist/plot/treemap/treemap.d.ts +28 -0
  351. package/dist/plot/treemap/treemap.js +102 -0
  352. package/dist/rdf/RdfPlot.svelte +37 -59
  353. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  354. package/dist/rdf/calc-rdf.js +63 -52
  355. package/dist/rdf/index.d.ts +0 -1
  356. package/dist/sanitize.js +85 -27
  357. package/dist/scene/SceneCamera.svelte +1 -1
  358. package/dist/scene/SceneCamera.svelte.d.ts +1 -1
  359. package/dist/scene/bind-renderer.svelte.js +14 -6
  360. package/dist/scene/index.d.ts +1 -2
  361. package/dist/scene/index.js +1 -1
  362. package/dist/scene/{props.js → props.svelte.js} +15 -1
  363. package/dist/settings.d.ts +19 -9
  364. package/dist/settings.js +229 -525
  365. package/dist/spectral/Bands.svelte +207 -221
  366. package/dist/spectral/BandsAndDos.svelte +11 -9
  367. package/dist/spectral/BrillouinBandsDos.svelte +19 -42
  368. package/dist/spectral/Dos.svelte +92 -93
  369. package/dist/spectral/helpers.d.ts +3 -3
  370. package/dist/spectral/helpers.js +65 -140
  371. package/dist/state.svelte.js +5 -18
  372. package/dist/structure/Arrow.svelte +5 -18
  373. package/dist/structure/ArrowInstances.svelte +160 -0
  374. package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
  375. package/dist/structure/AtomLegend.svelte +120 -85
  376. package/dist/structure/AtomLegend.svelte.d.ts +2 -3
  377. package/dist/structure/Bond.svelte +31 -23
  378. package/dist/structure/CanvasTooltip.svelte +15 -3
  379. package/dist/structure/CellSelect.svelte +45 -35
  380. package/dist/structure/Cylinder.svelte +24 -40
  381. package/dist/structure/InstancedAtoms.svelte +114 -0
  382. package/dist/structure/InstancedAtoms.svelte.d.ts +15 -0
  383. package/dist/structure/Lattice.svelte +85 -149
  384. package/dist/structure/SiteLabels.svelte +122 -0
  385. package/dist/structure/SiteLabels.svelte.d.ts +18 -0
  386. package/dist/structure/Structure.svelte +636 -504
  387. package/dist/structure/Structure.svelte.d.ts +4 -2
  388. package/dist/structure/StructureCarousel.svelte +637 -0
  389. package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
  390. package/dist/structure/StructureControls.svelte +165 -425
  391. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  392. package/dist/structure/StructureExportPane.svelte +17 -21
  393. package/dist/structure/StructureInfoPane.svelte +128 -133
  394. package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
  395. package/dist/structure/StructureScene.svelte +535 -578
  396. package/dist/structure/StructureScene.svelte.d.ts +6 -2
  397. package/dist/structure/StructureViewport.svelte +451 -0
  398. package/dist/structure/StructureViewport.svelte.d.ts +65 -0
  399. package/dist/structure/atom-label-placement.d.ts +16 -12
  400. package/dist/structure/atom-label-placement.js +70 -37
  401. package/dist/structure/atom-properties.d.ts +2 -0
  402. package/dist/structure/atom-properties.js +105 -38
  403. package/dist/structure/bond-order-perception.js +3 -8
  404. package/dist/structure/bonding.d.ts +9 -3
  405. package/dist/structure/bonding.js +95 -46
  406. package/dist/structure/edit-history.d.ts +6 -0
  407. package/dist/structure/edit-history.js +27 -0
  408. package/dist/structure/export.d.ts +1 -0
  409. package/dist/structure/export.js +89 -105
  410. package/dist/structure/format-detect.d.ts +1 -0
  411. package/dist/structure/format-detect.js +20 -0
  412. package/dist/structure/geometry.d.ts +9 -0
  413. package/dist/structure/geometry.js +27 -0
  414. package/dist/structure/index.d.ts +16 -1
  415. package/dist/structure/index.js +15 -10
  416. package/dist/structure/measure.d.ts +1 -1
  417. package/dist/structure/measure.js +6 -12
  418. package/dist/structure/parse.d.ts +2 -1
  419. package/dist/structure/parse.js +272 -313
  420. package/dist/structure/partial-occupancy.js +10 -5
  421. package/dist/structure/pbc.js +85 -107
  422. package/dist/structure/polyhedra.d.ts +0 -1
  423. package/dist/structure/polyhedra.js +7 -21
  424. package/dist/structure/prop-groups.d.ts +85 -0
  425. package/dist/structure/prop-groups.js +18 -0
  426. package/dist/structure/supercell.d.ts +1 -2
  427. package/dist/structure/supercell.js +7 -13
  428. package/dist/symmetry/SymmetryElements.svelte +11 -15
  429. package/dist/symmetry/SymmetryStats.svelte +55 -71
  430. package/dist/symmetry/WyckoffTable.svelte +10 -28
  431. package/dist/symmetry/cell-transform.js +6 -14
  432. package/dist/symmetry/index.d.ts +1 -0
  433. package/dist/symmetry/index.js +13 -10
  434. package/dist/symmetry/spacegroups.js +3 -2
  435. package/dist/table/HeatmapTable.svelte +1142 -447
  436. package/dist/table/HeatmapTable.svelte.d.ts +12 -2
  437. package/dist/table/ToggleMenu.svelte +168 -53
  438. package/dist/table/ToggleMenu.svelte.d.ts +1 -1
  439. package/dist/table/index.d.ts +21 -6
  440. package/dist/table/index.js +34 -21
  441. package/dist/theme/ThemeControl.svelte +16 -8
  442. package/dist/theme/embedded.d.ts +18 -0
  443. package/dist/theme/embedded.js +206 -0
  444. package/dist/theme/index.d.ts +5 -5
  445. package/dist/theme/index.js +5 -12
  446. package/dist/theme/themes.mjs +44 -66
  447. package/dist/tooltip/KCoords.svelte +4 -1
  448. package/dist/tooltip/TooltipContent.svelte +3 -6
  449. package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
  450. package/dist/tooltip/index.d.ts +8 -1
  451. package/dist/trajectory/Trajectory.svelte +613 -386
  452. package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
  453. package/dist/trajectory/TrajectoryError.svelte +12 -74
  454. package/dist/trajectory/TrajectoryExportPane.svelte +14 -32
  455. package/dist/trajectory/TrajectoryInfoPane.svelte +127 -164
  456. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
  457. package/dist/trajectory/extract.js +61 -49
  458. package/dist/trajectory/format-detect.d.ts +2 -0
  459. package/dist/trajectory/format-detect.js +17 -13
  460. package/dist/trajectory/frame-reader.d.ts +2 -0
  461. package/dist/trajectory/frame-reader.js +35 -32
  462. package/dist/trajectory/helpers.d.ts +2 -0
  463. package/dist/trajectory/helpers.js +34 -56
  464. package/dist/trajectory/index.d.ts +1 -0
  465. package/dist/trajectory/index.js +5 -8
  466. package/dist/trajectory/parse/ase.js +1 -1
  467. package/dist/trajectory/parse/h5-utils.d.ts +13 -0
  468. package/dist/trajectory/parse/h5-utils.js +109 -0
  469. package/dist/trajectory/parse/hdf5.d.ts +1 -1
  470. package/dist/trajectory/parse/hdf5.js +116 -106
  471. package/dist/trajectory/parse/index.d.ts +6 -6
  472. package/dist/trajectory/parse/index.js +25 -33
  473. package/dist/trajectory/parse/lammps.js +8 -12
  474. package/dist/trajectory/parse/vasp.js +10 -7
  475. package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
  476. package/dist/trajectory/parse/vaspout-electronic.js +207 -0
  477. package/dist/trajectory/parse/vaspout-h5.d.ts +4 -0
  478. package/dist/trajectory/parse/vaspout-h5.js +281 -0
  479. package/dist/trajectory/parse/xyz.js +59 -9
  480. package/dist/trajectory/plotting.d.ts +8 -9
  481. package/dist/trajectory/plotting.js +113 -64
  482. package/dist/utils.d.ts +3 -0
  483. package/dist/utils.js +7 -0
  484. package/dist/xrd/XrdPlot.svelte +79 -118
  485. package/dist/xrd/broadening.js +2 -1
  486. package/dist/xrd/calc-xrd.js +24 -62
  487. package/dist/xrd/parse.d.ts +0 -1
  488. package/dist/xrd/parse.js +69 -95
  489. package/package.json +94 -34
  490. package/readme.md +6 -5
  491. package/dist/api/mp.d.ts +0 -6
  492. package/dist/api/mp.js +0 -22
  493. package/dist/plot/sunburst/SunburstControls.svelte +0 -200
  494. package/dist/scene/types.d.ts +0 -26
  495. package/dist/tooltip/types.d.ts +0 -8
  496. package/dist/trajectory/constants.d.ts +0 -6
  497. package/dist/trajectory/constants.js +0 -11
  498. /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
  499. /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
@@ -9,22 +9,20 @@
9
9
  import type { ExportSection } from '../io'
10
10
  import ExportPane from '../io/ExportPane.svelte'
11
11
  import { format_num } from '../labels'
12
- import { FullscreenButton, type FullscreenToggleProp, SettingsSection, toggle_fullscreen } from '../layout'
12
+ import {
13
+ FullscreenButton,
14
+ type FullscreenToggleProp,
15
+ SettingsSection,
16
+ toggle_fullscreen,
17
+ } from '../layout'
13
18
  import { sync_fullscreen } from '../layout/fullscreen.svelte'
14
19
  import type { Vec2, Vec3 } from '../math'
15
- import {
16
- convex_hull_2d,
17
- cross_3d,
18
- merge_coplanar_triangles,
19
- normalize_vec,
20
- } from '../math'
20
+ import { convex_hull_2d, cross_3d, merge_coplanar_triangles, normalize_vec } from '../math'
21
21
  import DraggablePane from '../overlays/DraggablePane.svelte'
22
22
  import { ColorBar, ScatterPlot3DControls } from '../plot'
23
- import {
24
- constrain_tooltip_position,
25
- pad_rect,
26
- rects_overlap,
27
- } from '../plot/core/layout'
23
+ import { page_visibility } from '../scene'
24
+ import { sanitize_html } from '../sanitize'
25
+ import { constrain_tooltip_position, pad_rect, rects_overlap } from '../plot/core/layout'
28
26
  import type {
29
27
  AxisConfig3D,
30
28
  CameraProjection3D,
@@ -42,7 +40,7 @@
42
40
  import { ConvexGeometry } from 'three/examples/jsm/geometries/ConvexGeometry.js'
43
41
  import { compute_chempot_async } from './async-compute.svelte'
44
42
  import ChemPotScene3D from './ChemPotScene3D.svelte'
45
- import { get_chempot_color_bar_config, make_chempot_color_scale } from './color'
43
+ import { ARITY_COLORS, get_chempot_color_bar_config, get_domain_color_data } from './color'
46
44
  import {
47
45
  CHEMPOT_COLOR_MODE_OPTIONS,
48
46
  CHEMPOT_COLOR_SCALE_OPTIONS,
@@ -60,12 +58,10 @@
60
58
  import {
61
59
  apply_element_padding,
62
60
  bbox_diagonal,
63
- best_form_energy_for_formula,
64
61
  build_axis_ranges,
65
62
  dedup_points,
66
- formula_key_from_composition,
67
63
  get_3d_domain_simplexes_and_ann_loc,
68
- get_energy_per_atom,
64
+ get_energy_stats_by_formula,
69
65
  get_min_entries_and_el_refs,
70
66
  get_ternary_combinations,
71
67
  get_visible_domain_labels,
@@ -129,18 +125,22 @@
129
125
  } = $props()
130
126
 
131
127
  // 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: [] })
128
+ const overrides = create_chempot_overrides(
129
+ () => config,
130
+ [
131
+ `formal_chempots`,
132
+ `label_stable`,
133
+ `element_padding`,
134
+ `default_min_limit`,
135
+ `formulas_to_draw`,
136
+ `draw_formula_meshes`,
137
+ `draw_formula_lines`,
138
+ `color_mode`,
139
+ `color_scale`,
140
+ `reverse_color_scale`,
141
+ ],
142
+ { color_mode: `arity`, formulas_to_draw: [] },
143
+ )
144
144
  const formal_chempots = $derived(overrides.resolve(`formal_chempots`))
145
145
  const label_stable = $derived(overrides.resolve(`label_stable`))
146
146
  const element_padding = $derived(overrides.resolve(`element_padding`))
@@ -156,9 +156,7 @@
156
156
  config.tooltip_detail_level ?? CHEMPOT_DEFAULTS.tooltip_detail_level,
157
157
  )
158
158
  const formula_colors = $derived(
159
- config.formula_colors?.length
160
- ? config.formula_colors
161
- : CHEMPOT_DEFAULTS.formula_colors,
159
+ config.formula_colors?.length ? config.formula_colors : CHEMPOT_DEFAULTS.formula_colors,
162
160
  )
163
161
 
164
162
  const formula_label_segments = (formula: string): FormulaLabelSegment[] =>
@@ -181,21 +179,38 @@
181
179
  // Separate effects so each reacts to its own pane opening independently —
182
180
  // a single $derived ternary would create priority ordering where opening
183
181
  // a "lower" pane while a "higher" one is open fails silently.
184
- $effect(() => { if (export_pane_open) { formula_picker_open = false; controls_open = false } })
185
- $effect(() => { if (formula_picker_open) { export_pane_open = false; controls_open = false } })
186
- $effect(() => { if (controls_open) { export_pane_open = false; formula_picker_open = false } })
182
+ $effect(() => {
183
+ if (export_pane_open) {
184
+ formula_picker_open = false
185
+ controls_open = false
186
+ }
187
+ })
188
+ $effect(() => {
189
+ if (formula_picker_open) {
190
+ export_pane_open = false
191
+ controls_open = false
192
+ }
193
+ })
194
+ $effect(() => {
195
+ if (controls_open) {
196
+ export_pane_open = false
197
+ formula_picker_open = false
198
+ }
199
+ })
187
200
  let container_width = $state(0)
188
201
  let container_height = $state(0)
189
202
  const base_aspect_ratio = $derived(height > 0 && width > 0 ? height / width : 1)
190
203
  const render_width = $derived(container_width > 0 ? container_width : width)
191
204
  const render_height = $derived(
192
205
  fullscreen
193
- ? (container_height > 0 ? container_height : height)
206
+ ? container_height > 0
207
+ ? container_height
208
+ : height
194
209
  : Math.round(render_width * base_aspect_ratio),
195
210
  )
196
211
 
197
212
  let mounted = $state(false)
198
- onMount(() => mounted = true)
213
+ onMount(() => (mounted = true))
199
214
  let orbit_controls_ref = $state<OrbitControls | undefined>(undefined)
200
215
  // Backside tracking: axes/ticks/labels render on the far side from the camera
201
216
  // back[i] = backside data coordinate value for data axis i
@@ -248,25 +263,20 @@
248
263
  if (next_temperature !== temperature) temperature = next_temperature
249
264
  })
250
265
 
251
- const show_temperature_slider = $derived(
252
- has_temp_data && available_temperatures.length > 0,
253
- )
266
+ const show_temperature_slider = $derived(has_temp_data && available_temperatures.length > 0)
254
267
 
255
268
  const projection_source_entries = $derived(
256
269
  get_projection_source_entries(entries, temp_filtered_entries),
257
270
  )
258
271
 
259
- const all_entry_elements = $derived.by(() =>
260
- Array.from(
261
- new SvelteSet(
262
- projection_source_entries.flatMap((entry) =>
263
- Object.entries(entry.composition)
264
- .filter(([, amount]) => amount > 0)
265
- .map(([element]) => element)
266
- ),
267
- ),
268
- ).sort()
269
- )
272
+ const all_entry_elements = $derived.by(() => {
273
+ const elements = projection_source_entries.flatMap((entry) =>
274
+ Object.entries(entry.composition)
275
+ .filter(([, amount]) => amount > 0)
276
+ .map(([element]) => element),
277
+ )
278
+ return Array.from(new SvelteSet(elements)).sort()
279
+ })
270
280
  const has_multinary_system = $derived(all_entry_elements.length > 3)
271
281
  let projection_elements_override = $state<string[] | null>(null)
272
282
  const config_projection_elements = $derived(
@@ -274,22 +284,18 @@
274
284
  )
275
285
  const projection_elements = $derived.by(() => {
276
286
  if (all_entry_elements.length < 3) return []
277
- if (!has_multinary_system) {
278
- return config_projection_elements ?? all_entry_elements.slice(0, 3)
279
- }
280
- const override_projection = normalize_projection_triplet(
281
- projection_elements_override ?? undefined,
282
- all_entry_elements,
283
- )
284
- if (override_projection) return override_projection
285
- if (config_projection_elements) return config_projection_elements
286
- return all_entry_elements.slice(0, 3)
287
+ // User-picked projection axes only apply to multinary (4+ element) systems
288
+ const override_projection = has_multinary_system
289
+ ? normalize_projection_triplet(
290
+ projection_elements_override ?? undefined,
291
+ all_entry_elements,
292
+ )
293
+ : null
294
+ return override_projection ?? config_projection_elements ?? all_entry_elements.slice(0, 3)
287
295
  })
288
296
  const effective_config = $derived({
289
297
  ...config,
290
- elements: projection_elements.length === 3
291
- ? projection_elements
292
- : config.elements,
298
+ elements: projection_elements.length === 3 ? projection_elements : config.elements,
293
299
  formal_chempots,
294
300
  label_stable,
295
301
  element_padding,
@@ -319,7 +325,9 @@
319
325
  diagram_data = null
320
326
  diagram_computing = false
321
327
  })
322
- return () => { cancelled = true }
328
+ return () => {
329
+ cancelled = true
330
+ }
323
331
  })
324
332
 
325
333
  const plot_elements = $derived(diagram_data?.elements ?? projection_elements)
@@ -348,15 +356,11 @@
348
356
  })
349
357
  const current_projection_key = $derived(plot_elements.join(`|`))
350
358
  let formula_filter_query = $state(``)
351
- const available_formulas = $derived.by(() =>
352
- Object.keys(diagram_data?.domains ?? {}).sort()
353
- )
359
+ const available_formulas = $derived(Object.keys(diagram_data?.domains ?? {}).sort())
354
360
  const filtered_formulas = $derived.by(() => {
355
361
  const query = formula_filter_query.trim().toLowerCase()
356
362
  if (!query) return available_formulas
357
- return available_formulas.filter((formula) =>
358
- formula.toLowerCase().includes(query)
359
- )
363
+ return available_formulas.filter((formula) => formula.toLowerCase().includes(query))
360
364
  })
361
365
 
362
366
  // Process domains for rendering
@@ -374,51 +378,49 @@
374
378
  info: ChemPotHoverInfo3D
375
379
  }
376
380
 
377
- interface FormulaEnergyStats {
378
- matching_entry_count: number
379
- min_energy_per_atom: number | null
380
- max_energy_per_atom: number | null
381
+ const domain_annotation_cache = new Map<string, number[]>()
382
+
383
+ function get_domain_ann_loc(points_3d: number[][]): number[] {
384
+ if (points_3d.length >= 3) {
385
+ const cache_key = points_3d.map((point) => point.join(`,`)).join(`;`)
386
+ const cached = domain_annotation_cache.get(cache_key)
387
+ if (cached) return cached
388
+ const ann_loc = get_3d_domain_simplexes_and_ann_loc(points_3d).ann_loc
389
+ domain_annotation_cache.set(cache_key, ann_loc)
390
+ return ann_loc
391
+ }
392
+ return points_3d[0].map(
393
+ (_, col_idx) =>
394
+ points_3d.reduce((sum, point) => sum + point[col_idx], 0) / points_3d.length,
395
+ )
381
396
  }
382
- type NumericColorMode = Exclude<ChemPotColorMode, `none` | `arity`>
383
397
 
384
398
  const render_domains = $derived.by((): DomainRenderData[] => {
385
399
  if (!diagram_data || plot_elements.length < 2) return []
386
400
 
387
401
  const dim = diagram_data.elements.length
388
402
  const indices = Array.from({ length: dim }, (_, idx) => idx)
389
- const new_lims = element_padding > 0
390
- ? apply_element_padding(
391
- diagram_data.domains,
392
- indices,
393
- element_padding,
394
- default_min_limit,
395
- )
396
- : null
403
+ const new_lims =
404
+ element_padding > 0
405
+ ? apply_element_padding(
406
+ diagram_data.domains,
407
+ indices,
408
+ element_padding,
409
+ default_min_limit,
410
+ )
411
+ : null
397
412
 
398
413
  const result: DomainRenderData[] = []
399
414
  for (const [formula, pts] of Object.entries(diagram_data.domains)) {
400
415
  const padded = new_lims
401
- ? pad_domain_points(
402
- pts,
403
- indices,
404
- new_lims,
405
- default_min_limit,
406
- element_padding,
407
- )
416
+ ? pad_domain_points(pts, indices, new_lims, default_min_limit, element_padding)
408
417
  : pts
409
418
  if (padded.length < 2) continue
410
- const is_draw = formulas_to_draw.includes(formula)
411
- const centroid = padded[0].map((_, col_idx) =>
412
- padded.reduce((sum, point) => sum + point[col_idx], 0) / padded.length
413
- )
414
- const ann_loc = padded.length >= 3
415
- ? get_3d_domain_simplexes_and_ann_loc(padded).ann_loc
416
- : centroid
417
419
  result.push({
418
420
  formula,
419
421
  points_3d: padded,
420
- ann_loc,
421
- is_draw_formula: is_draw,
422
+ ann_loc: get_domain_ann_loc(padded),
423
+ is_draw_formula: formulas_to_draw.includes(formula),
422
424
  label_font_size: bbox_diagonal(padded),
423
425
  })
424
426
  }
@@ -431,129 +433,26 @@
431
433
  return result
432
434
  })
433
435
 
434
- const entry_energy_stats_by_formula = $derived.by(
435
- (): SvelteMap<string, FormulaEnergyStats> => {
436
- const stats_by_formula = new SvelteMap<string, FormulaEnergyStats>()
437
- for (const entry of temp_filtered_entries) {
438
- const formula_key = formula_key_from_composition(entry.composition)
439
- const energy_per_atom = get_energy_per_atom(entry)
440
- const existing = stats_by_formula.get(formula_key)
441
- if (!existing) {
442
- stats_by_formula.set(formula_key, {
443
- matching_entry_count: 1,
444
- min_energy_per_atom: energy_per_atom,
445
- max_energy_per_atom: energy_per_atom,
446
- })
447
- continue
448
- }
449
- stats_by_formula.set(formula_key, {
450
- matching_entry_count: existing.matching_entry_count + 1,
451
- min_energy_per_atom: Math.min(
452
- existing.min_energy_per_atom ?? energy_per_atom,
453
- energy_per_atom,
454
- ),
455
- max_energy_per_atom: Math.max(
456
- existing.max_energy_per_atom ?? energy_per_atom,
457
- energy_per_atom,
458
- ),
459
- })
460
- }
461
- return stats_by_formula
462
- },
436
+ const entry_energy_stats_by_formula = $derived(
437
+ get_energy_stats_by_formula(temp_filtered_entries),
463
438
  )
464
439
 
465
440
  // === Region coloring ===
466
- // Categorical palette for arity mode (element count)
467
- const arity_colors = [`#3498db`, `#2ecc71`, `#e67e22`, `#9b59b6`] as const
468
-
469
441
  // Original (non-renormalized) elemental references for formation energy computation.
470
442
  // diagram_data.el_refs may be renormalized to zero when formal_chempots is true,
471
443
  // so we compute our own from the raw entries to get true DFT reference energies.
472
- const raw_el_refs = $derived(
473
- get_min_entries_and_el_refs(temp_filtered_entries).el_refs,
474
- )
475
-
476
- const color_mode_labels: Record<NumericColorMode, string> = {
477
- energy: `Energy per atom (eV)`,
478
- formation_energy: `Formation energy (eV/atom)`,
479
- entries: `Entry count`,
480
- }
481
- function get_numeric_color_value(
482
- formula: string,
483
- active_color_mode: NumericColorMode,
484
- ): number | null {
485
- if (active_color_mode === `energy`) {
486
- return entry_energy_stats_by_formula.get(formula)?.min_energy_per_atom ?? null
487
- }
488
- if (active_color_mode === `formation_energy`) {
489
- return best_form_energy_for_formula(
490
- temp_filtered_entries,
491
- formula,
492
- raw_el_refs,
493
- ) ?? null
494
- }
495
- return entry_energy_stats_by_formula.get(formula)?.matching_entry_count ?? 0
496
- }
497
- const domain_color_values = $derived.by(
498
- (): { value_by_formula: SvelteMap<string, number>; values: number[] } | null => {
499
- if (color_mode === `none` || color_mode === `arity`) return null
500
- const active_color_mode = color_mode as NumericColorMode
501
- const value_by_formula = new SvelteMap<string, number>()
502
- const values: number[] = []
503
- for (const domain of render_domains) {
504
- const value = get_numeric_color_value(domain.formula, active_color_mode)
505
- if (value == null || !Number.isFinite(value)) continue
506
- values.push(value)
507
- value_by_formula.set(domain.formula, value)
508
- }
509
- return { value_by_formula, values }
510
- },
511
- )
512
-
513
- // Per-domain color map keyed by formula
514
- const domain_colors = $derived.by((): SvelteMap<string, string> => {
515
- const colors = new SvelteMap<string, string>()
516
- if (color_mode === `none`) return colors
444
+ const raw_el_refs = $derived(get_min_entries_and_el_refs(temp_filtered_entries).el_refs)
517
445
 
518
- if (color_mode === `arity`) {
519
- for (const domain of render_domains) {
520
- const n_elements = extract_formula_elements(domain.formula).length
521
- const idx = Math.min(n_elements, arity_colors.length) - 1
522
- colors.set(domain.formula, arity_colors[Math.max(0, idx)])
523
- }
524
- return colors
525
- }
526
- const values_payload = domain_color_values
527
- const scale = make_chempot_color_scale(
528
- values_payload?.values ?? [],
446
+ const { colors: domain_colors, color_range } = $derived(
447
+ get_domain_color_data({
448
+ formulas: render_domains.map((domain) => domain.formula),
449
+ color_mode,
529
450
  color_scale,
530
451
  reverse_color_scale,
531
- )
532
- for (const domain of render_domains) {
533
- const value = values_payload?.value_by_formula.get(domain.formula)
534
- colors.set(domain.formula, value != null && scale ? scale(value) : `#999`)
535
- }
536
- return colors
537
- })
538
-
539
- // Range and label for the color bar (null for none/arity which are categorical)
540
- const color_range = $derived.by(
541
- (): { min: number; max: number; label: string } | null => {
542
- const values = domain_color_values?.values ?? []
543
- if (values.length === 0) return null
544
- let lo = values[0], hi = values[0]
545
- for (let idx = 1; idx < values.length; idx++) {
546
- if (values[idx] < lo) lo = values[idx]
547
- if (values[idx] > hi) hi = values[idx]
548
- }
549
- return {
550
- min: lo,
551
- max: Math.max(hi, lo + 1e-6),
552
- label: color_mode === `none` || color_mode === `arity`
553
- ? ``
554
- : color_mode_labels[color_mode],
555
- }
556
- },
452
+ entries: temp_filtered_entries,
453
+ el_refs: raw_el_refs,
454
+ energy_stats: entry_energy_stats_by_formula,
455
+ }),
557
456
  )
558
457
 
559
458
  const arity_legend_labels = $derived.by((): string[] => {
@@ -574,9 +473,12 @@
574
473
  const render_axis_scale = $derived.by((): Vec3 => {
575
474
  const points = render_domains.flatMap((domain) => domain.points_3d)
576
475
  if (points.length === 0) return [1, 1, 1]
577
- let min0 = Infinity, max0 = -Infinity
578
- let min1 = Infinity, max1 = -Infinity
579
- let min2 = Infinity, max2 = -Infinity
476
+ let min0 = Infinity,
477
+ max0 = -Infinity
478
+ let min1 = Infinity,
479
+ max1 = -Infinity
480
+ let min2 = Infinity,
481
+ max2 = -Infinity
580
482
  for (const point of points) {
581
483
  if (point[0] < min0) min0 = point[0]
582
484
  if (point[0] > max0) max0 = point[0]
@@ -596,10 +498,7 @@
596
498
  ]
597
499
  })
598
500
 
599
- function to_render_xyz(point: number[]): Vec3 {
600
- const [scale_x, scale_y, scale_z] = render_axis_scale
601
- return [point[1] * scale_x, point[2] * scale_y, point[0] * scale_z]
602
- }
501
+ const to_render_xyz = (point: number[]): Vec3 => swiz(point[0], point[1], point[2])
603
502
 
604
503
  // Compute data center and extent for camera positioning (in swizzled coords)
605
504
  const { data_center, data_extent } = $derived.by(() => {
@@ -616,11 +515,7 @@
616
515
  sum_z += z_val
617
516
  }
618
517
  const n_points = points.length
619
- const center = new THREE.Vector3(
620
- sum_x / n_points,
621
- sum_y / n_points,
622
- sum_z / n_points,
623
- )
518
+ const center = new THREE.Vector3(sum_x / n_points, sum_y / n_points, sum_z / n_points)
624
519
  // Compute max distance from center
625
520
  let max_dist = 0
626
521
  for (const point of points) {
@@ -635,30 +530,22 @@
635
530
  data_center.y + data_extent,
636
531
  data_center.z + data_extent,
637
532
  ])
638
- const default_camera_target = $derived<Vec3>([
639
- data_center.x,
640
- data_center.y,
641
- data_center.z,
642
- ])
533
+ const default_camera_target = $derived<Vec3>([data_center.x, data_center.y, data_center.z])
643
534
  const default_orthographic_zoom = $derived(
644
535
  Math.min(render_width, render_height) / (data_extent * 1.6),
645
536
  )
646
537
  let camera_position_override = $state<Vec3 | null>(null)
647
538
  let camera_target_override = $state<Vec3 | null>(null)
648
539
  let orthographic_zoom_override = $state<number | null>(null)
649
- const camera_position = $derived(
650
- camera_position_override ?? default_camera_position,
651
- )
652
- const camera_target = $derived(
653
- camera_target_override ?? default_camera_target,
654
- )
655
- const orthographic_zoom = $derived(
656
- orthographic_zoom_override ?? default_orthographic_zoom,
657
- )
540
+ const camera_position = $derived(camera_position_override ?? default_camera_position)
541
+ const camera_target = $derived(camera_target_override ?? default_camera_target)
542
+ const orthographic_zoom = $derived(orthographic_zoom_override ?? default_orthographic_zoom)
658
543
  // Label scale factor: zoom relative to default, so labels grow/shrink with zoom
659
544
  // Labels scale sub-linearly with zoom so they grow but don't dominate when zoomed in
660
545
  const zoom_scale = $derived(
661
- default_orthographic_zoom > 0 ? Math.sqrt(orthographic_zoom / default_orthographic_zoom) : 1,
546
+ default_orthographic_zoom > 0
547
+ ? Math.sqrt(orthographic_zoom / default_orthographic_zoom)
548
+ : 1,
662
549
  )
663
550
  let last_data_center: Vec3 | null = null
664
551
  let last_data_extent: number | null = null
@@ -667,14 +554,16 @@
667
554
  // Each domain in a chem pot diagram is a convex polygon/polyhedron. We project
668
555
  // to 2D (trying all 3 axis-aligned planes) and use the best projection's
669
556
  // convex hull boundary. This reliably handles both flat and 3D domains.
670
- function get_domain_edges(
671
- pts: number[][],
672
- ): [number[], number[]][] {
557
+ function get_domain_edges(pts: number[][]): [number[], number[]][] {
673
558
  const unique = dedup_3d(pts)
674
559
  if (unique.length < 2) return []
675
560
  if (unique.length === 2) return [[unique[0], unique[1]]]
676
561
  if (unique.length === 3) {
677
- return [[unique[0], unique[1]], [unique[1], unique[2]], [unique[0], unique[2]]]
562
+ return [
563
+ [unique[0], unique[1]],
564
+ [unique[1], unique[2]],
565
+ [unique[0], unique[2]],
566
+ ]
678
567
  }
679
568
  return get_2d_hull_edges(unique)
680
569
  }
@@ -694,9 +583,7 @@
694
583
  // (largest non-degenerate hull area). Unioning multiple projections can add
695
584
  // non-physical diagonals for nearly coplanar domains.
696
585
  // Called only from get_domain_edges with 4+ unique points
697
- function get_2d_hull_edges(
698
- pts: number[][],
699
- ): [number[], number[]][] {
586
+ function get_2d_hull_edges(pts: number[][]): [number[], number[]][] {
700
587
  let selected_hull: Vec2[] = []
701
588
  let selected_coord_to_idx: SvelteMap<string, number> | null = null
702
589
  let selected_hull_area = -1
@@ -706,15 +593,20 @@
706
593
 
707
594
  // Skip this projection if points collapse to a line (near-zero range in
708
595
  // either projected axis). This avoids spurious edges from edge-on views.
709
- let min0 = Infinity, max0 = -Infinity, min1 = Infinity, max1 = -Infinity
596
+ let min0 = Infinity,
597
+ max0 = -Infinity,
598
+ min1 = Infinity,
599
+ max1 = -Infinity
710
600
  for (const pt of pts) {
711
- const v0 = pt[axes[0]], v1 = pt[axes[1]]
601
+ const v0 = pt[axes[0]],
602
+ v1 = pt[axes[1]]
712
603
  if (v0 < min0) min0 = v0
713
604
  if (v0 > max0) max0 = v0
714
605
  if (v1 < min1) min1 = v1
715
606
  if (v1 > max1) max1 = v1
716
607
  }
717
- const range0 = max0 - min0, range1 = max1 - min1
608
+ const range0 = max0 - min0,
609
+ range1 = max1 - min1
718
610
  const max_2d_range = Math.max(range0, range1)
719
611
  if (max_2d_range < 1e-6 || Math.min(range0, range1) < max_2d_range * 0.01) {
720
612
  continue
@@ -751,18 +643,8 @@
751
643
  const point_b_idx = selected_coord_to_idx.get(
752
644
  `${point_b[0].toFixed(6)},${point_b[1].toFixed(6)}`,
753
645
  )
754
- if (
755
- point_a_idx == null || point_b_idx == null || point_a_idx >= pts.length ||
756
- point_b_idx >= pts.length
757
- ) {
758
- console.warn(`get_2d_hull_edges: invalid edge`, {
759
- point_a,
760
- point_b,
761
- point_a_idx,
762
- point_b_idx,
763
- })
764
- continue
765
- }
646
+ // Hull vertices come from pts_2d, so lookups always succeed
647
+ if (point_a_idx == null || point_b_idx == null) continue
766
648
  edges.push([pts[point_a_idx], pts[point_b_idx]])
767
649
  }
768
650
 
@@ -846,7 +728,9 @@
846
728
  const n_verts = pos.count
847
729
  const n_faces = n_verts / 3
848
730
  // Hull centroid for orienting face normals outward
849
- let hx = 0, hy = 0, hz = 0
731
+ let hx = 0,
732
+ hy = 0,
733
+ hz = 0
850
734
  for (let vert_idx = 0; vert_idx < n_verts; vert_idx++) {
851
735
  hx += pos.getX(vert_idx)
852
736
  hy += pos.getY(vert_idx)
@@ -900,7 +784,9 @@
900
784
  const centroids = render_domains
901
785
  .filter((domain) => !domain.is_draw_formula && domain.points_3d.length > 0)
902
786
  .map((domain) => {
903
- let sx = 0, sy = 0, sz = 0
787
+ let sx = 0,
788
+ sy = 0,
789
+ sz = 0
904
790
  for (const pt of domain.points_3d) {
905
791
  const [x_val, y_val, z_val] = to_render_xyz(pt)
906
792
  sx += x_val
@@ -908,7 +794,12 @@
908
794
  sz += z_val
909
795
  }
910
796
  const n_points = domain.points_3d.length
911
- return { formula: domain.formula, cx: sx / n_points, cy: sy / n_points, cz: sz / n_points }
797
+ return {
798
+ formula: domain.formula,
799
+ cx: sx / n_points,
800
+ cy: sy / n_points,
801
+ cz: sz / n_points,
802
+ }
912
803
  })
913
804
 
914
805
  // Assign each face to the nearest domain centroid
@@ -938,9 +829,9 @@
938
829
  const tol = 1e-3
939
830
  const round = (val: number): number => Math.round(val / tol)
940
831
  const vkey = (vert_idx: number): string =>
941
- `${round(pos.getX(vert_idx))},${round(pos.getY(vert_idx))},${
942
- round(pos.getZ(vert_idx))
943
- }`
832
+ `${round(pos.getX(vert_idx))},${round(pos.getY(vert_idx))},${round(
833
+ pos.getZ(vert_idx),
834
+ )}`
944
835
  // Compute face normals
945
836
  const normals: Vec3[] = []
946
837
  for (let face_idx = 0; face_idx < n_faces; face_idx++) {
@@ -962,13 +853,11 @@
962
853
  for (let face_idx = 0; face_idx < n_faces; face_idx++) {
963
854
  const base = face_idx * 3
964
855
  const keys = [vkey(base), vkey(base + 1), vkey(base + 2)]
965
- for (
966
- const ek of [
967
- edge_key(keys[0], keys[1]),
968
- edge_key(keys[1], keys[2]),
969
- edge_key(keys[0], keys[2]),
970
- ]
971
- ) {
856
+ for (const ek of [
857
+ edge_key(keys[0], keys[1]),
858
+ edge_key(keys[1], keys[2]),
859
+ edge_key(keys[0], keys[2]),
860
+ ]) {
972
861
  const list = edge_faces.get(ek)
973
862
  if (list) list.push(face_idx)
974
863
  else edge_faces.set(ek, [face_idx])
@@ -984,13 +873,15 @@
984
873
  return node
985
874
  }
986
875
  const union = (a_idx: number, b_idx: number): void => {
987
- const ra = find(a_idx), rb = find(b_idx)
876
+ const ra = find(a_idx),
877
+ rb = find(b_idx)
988
878
  if (ra !== rb) parent[ra] = rb
989
879
  }
990
880
  for (const pair of edge_faces.values()) {
991
881
  if (pair.length !== 2) continue
992
882
  const [fa, fb] = pair
993
- const na = normals[fa], nb = normals[fb]
883
+ const na = normals[fa],
884
+ nb = normals[fb]
994
885
  if (Math.abs(na[0] * nb[0] + na[1] * nb[1] + na[2] * nb[2]) > 1 - tol) {
995
886
  union(fa, fb)
996
887
  }
@@ -1057,23 +948,21 @@
1057
948
  return geom
1058
949
  })
1059
950
 
951
+ const domain_label = (domain: DomainRenderData) => ({
952
+ formula: domain.formula,
953
+ position: swiz(domain.ann_loc[0], domain.ann_loc[1], domain.ann_loc[2]),
954
+ label_font_size: domain.label_font_size,
955
+ })
956
+
1060
957
  const visible_domain_labels = $derived.by(() => {
1061
958
  if (!hull_base_geometry || face_domain_map.length === 0) {
1062
- return render_domains.map((domain) => ({
1063
- formula: domain.formula,
1064
- position: swiz(domain.ann_loc[0], domain.ann_loc[1], domain.ann_loc[2]),
1065
- label_font_size: domain.label_font_size,
1066
- }))
959
+ return render_domains.map(domain_label)
1067
960
  }
1068
961
 
1069
962
  const pos = hull_base_geometry.getAttribute(`position`)
1070
963
  const pinned_labels = render_domains
1071
964
  .filter((domain) => domain.is_draw_formula)
1072
- .map((domain) => ({
1073
- formula: domain.formula,
1074
- position: swiz(domain.ann_loc[0], domain.ann_loc[1], domain.ann_loc[2]),
1075
- label_font_size: domain.label_font_size,
1076
- }))
965
+ .map(domain_label)
1077
966
  const font_size_by_formula = new SvelteMap(
1078
967
  render_domains.map((domain) => [domain.formula, domain.label_font_size]),
1079
968
  )
@@ -1085,16 +974,24 @@
1085
974
  )
1086
975
  })
1087
976
 
1088
- $effect(() => {
1089
- const geom = hull_base_geometry
1090
- return () => dispose_geometry(geom)
1091
- })
977
+ // Register an effect that disposes the given geometries whenever they are
978
+ // recomputed or the component unmounts.
979
+ function dispose_on_change(
980
+ get_geometries: () => (THREE.BufferGeometry | null | undefined)[],
981
+ ): void {
982
+ $effect(() => {
983
+ const geometries = get_geometries()
984
+ return () => {
985
+ for (const geometry of geometries) geometry?.dispose()
986
+ }
987
+ })
988
+ }
1092
989
 
1093
- $effect(() => {
1094
- const geom = colored_hull_geometry
1095
- // Don't dispose if it's the same object as hull_base_geometry (no clone was made)
1096
- if (geom && geom !== hull_base_geometry) return () => dispose_geometry(geom)
1097
- })
990
+ dispose_on_change(() => [hull_base_geometry])
991
+ // Don't dispose colored hull if it's the same object as hull_base_geometry (no clone made)
992
+ dispose_on_change(() =>
993
+ colored_hull_geometry !== hull_base_geometry ? [colored_hull_geometry] : [],
994
+ )
1098
995
 
1099
996
  // Domains on the outer surface (used by the "Surface" formula overlay quick-select).
1100
997
  const surface_formulas = $derived.by((): SvelteSet<string> => {
@@ -1140,15 +1037,9 @@
1140
1037
 
1141
1038
  const controls_series = $derived<DataSeries3D[]>([
1142
1039
  {
1143
- x: render_domains.flatMap((domain) =>
1144
- domain.points_3d.map((point) => point[1])
1145
- ),
1146
- y: render_domains.flatMap((domain) =>
1147
- domain.points_3d.map((point) => point[2])
1148
- ),
1149
- z: render_domains.flatMap((domain) =>
1150
- domain.points_3d.map((point) => point[0])
1151
- ),
1040
+ x: render_domains.flatMap((domain) => domain.points_3d.map((point) => point[1])),
1041
+ y: render_domains.flatMap((domain) => domain.points_3d.map((point) => point[2])),
1042
+ z: render_domains.flatMap((domain) => domain.points_3d.map((point) => point[0])),
1152
1043
  label: `domains`,
1153
1044
  },
1154
1045
  ])
@@ -1159,8 +1050,7 @@
1159
1050
  const result: { geometry: THREE.BufferGeometry; color: string }[] = []
1160
1051
  for (const domain of render_domains) {
1161
1052
  if (!domain.is_draw_formula) continue
1162
- const color_idx = formulas_to_draw.indexOf(domain.formula) %
1163
- formula_colors.length
1053
+ const color_idx = formulas_to_draw.indexOf(domain.formula) % formula_colors.length
1164
1054
  const swizzled = domain.points_3d.map((point) => to_render_xyz(point))
1165
1055
  const positions: number[] = []
1166
1056
  for (const [pa, pb] of get_domain_edges(swizzled)) {
@@ -1179,8 +1069,7 @@
1179
1069
  if (!draw_formula_meshes) return result
1180
1070
  for (const domain of render_domains) {
1181
1071
  if (!domain.is_draw_formula || domain.points_3d.length < 4) continue
1182
- const color_idx = formulas_to_draw.indexOf(domain.formula) %
1183
- formula_colors.length
1072
+ const color_idx = formulas_to_draw.indexOf(domain.formula) % formula_colors.length
1184
1073
  const unique = dedup_3d(domain.points_3d)
1185
1074
  if (unique.length < 4) continue
1186
1075
  const vectors = unique.map((pt) => to_vec3(pt))
@@ -1194,10 +1083,7 @@
1194
1083
  return result
1195
1084
  })
1196
1085
 
1197
- function get_touches_limits(
1198
- points_3d: number[][],
1199
- lims: Vec2[],
1200
- ): string[] {
1086
+ function get_touches_limits(points_3d: number[][], lims: Vec2[]): string[] {
1201
1087
  const limit_tol = 1e-3
1202
1088
  const touches_limits: string[] = []
1203
1089
  for (
@@ -1207,11 +1093,11 @@
1207
1093
  ) {
1208
1094
  const [axis_min, axis_max] = lims[axis_idx]
1209
1095
  const axis_name = plot_elements[axis_idx] ?? `axis_${axis_idx}`
1210
- const touches_min = points_3d.some((point) =>
1211
- Math.abs(point[axis_idx] - axis_min) < limit_tol
1096
+ const touches_min = points_3d.some(
1097
+ (point) => Math.abs(point[axis_idx] - axis_min) < limit_tol,
1212
1098
  )
1213
- const touches_max = points_3d.some((point) =>
1214
- Math.abs(point[axis_idx] - axis_max) < limit_tol
1099
+ const touches_max = points_3d.some(
1100
+ (point) => Math.abs(point[axis_idx] - axis_max) < limit_tol,
1215
1101
  )
1216
1102
  if (touches_min) touches_limits.push(`${axis_name} lower bound`)
1217
1103
  if (touches_max) touches_limits.push(`${axis_name} upper bound`)
@@ -1269,9 +1155,7 @@
1269
1155
  const vertex_owners = new SvelteMap<string, string[]>()
1270
1156
  for (const domain of render_domains) {
1271
1157
  for (const pt of domain.points_3d) {
1272
- const key = pt.map((val) => (Math.round(val / tol) * tol).toFixed(4)).join(
1273
- `,`,
1274
- )
1158
+ const key = pt.map((val) => (Math.round(val / tol) * tol).toFixed(4)).join(`,`)
1275
1159
  const owners = vertex_owners.get(key)
1276
1160
  if (owners) {
1277
1161
  if (!owners.includes(domain.formula)) owners.push(domain.formula)
@@ -1344,46 +1228,12 @@
1344
1228
  return result
1345
1229
  })
1346
1230
 
1347
- function dispose_geometry(geometry: THREE.BufferGeometry | null | undefined): void {
1348
- if (!geometry) return
1349
- geometry.dispose()
1350
- }
1351
-
1352
- function dispose_geometries(
1353
- geometries: (THREE.BufferGeometry | null | undefined)[],
1354
- ): void {
1355
- for (const geometry of geometries) dispose_geometry(geometry)
1356
- }
1357
-
1358
- $effect(() => {
1359
- const geometry = edge_geometry
1360
- return () => dispose_geometry(geometry)
1361
- })
1362
-
1363
- $effect(() => {
1364
- const geometry = occlusion_hull_geometry
1365
- return () => dispose_geometry(geometry)
1366
- })
1367
-
1368
- $effect(() => {
1369
- const geometry = bounding_box_geometry
1370
- return () => dispose_geometry(geometry)
1371
- })
1372
-
1373
- $effect(() => {
1374
- const geometries = formula_edge_data.map((data) => data.geometry)
1375
- return () => dispose_geometries(geometries)
1376
- })
1377
-
1378
- $effect(() => {
1379
- const geometries = formula_mesh_data.map((data) => data.geometry)
1380
- return () => dispose_geometries(geometries)
1381
- })
1382
-
1383
- $effect(() => {
1384
- const geometries = hover_mesh_data.map((data) => data.geometry)
1385
- return () => dispose_geometries(geometries)
1386
- })
1231
+ dispose_on_change(() => [edge_geometry])
1232
+ dispose_on_change(() => [occlusion_hull_geometry])
1233
+ dispose_on_change(() => [bounding_box_geometry])
1234
+ dispose_on_change(() => formula_edge_data.map((data) => data.geometry))
1235
+ dispose_on_change(() => formula_mesh_data.map((data) => data.geometry))
1236
+ dispose_on_change(() => hover_mesh_data.map((data) => data.geometry))
1387
1237
 
1388
1238
  // === Grid, axes, ticks (matching ScatterPlot3D style) ===
1389
1239
 
@@ -1440,8 +1290,8 @@
1440
1290
  // Niced ranges (from ticks) padded so the grid extends beyond the diagram.
1441
1291
  // For horizontal axes (0,1): pad both sides.
1442
1292
  // For vertical axis (2): use actual data range and round min down to an integer.
1443
- const niced_range = $derived.by(() => {
1444
- return [0, 1, 2].map((axis): Vec2 => {
1293
+ const niced_range = $derived(
1294
+ [0, 1, 2].map((axis): Vec2 => {
1445
1295
  const ticks = data_ticks[axis]
1446
1296
  const lo = ticks[0]
1447
1297
  const hi = ticks.at(-1) ?? lo
@@ -1451,14 +1301,11 @@
1451
1301
  return [Math.floor(min_data), hi]
1452
1302
  }
1453
1303
  return [lo - step, hi + step]
1454
- })
1455
- })
1304
+ }),
1305
+ )
1456
1306
 
1457
1307
  // Helper to create a line geometry from two Vec3 arrays
1458
- function make_line_geom(
1459
- start: Vec3,
1460
- end: Vec3,
1461
- ): THREE.BufferGeometry {
1308
+ function make_line_geom(start: Vec3, end: Vec3): THREE.BufferGeometry {
1462
1309
  const geom = new THREE.BufferGeometry()
1463
1310
  geom.setAttribute(
1464
1311
  `position`,
@@ -1506,11 +1353,12 @@
1506
1353
  return [0, 1, 2].map((axis) => {
1507
1354
  const ticks = data_ticks[axis]
1508
1355
  const color = axis_colors[axis]
1509
- const label = axis === 0
1510
- ? (z_axis.label || chem_axis_label(0))
1511
- : axis === 1
1512
- ? (x_axis.label || chem_axis_label(1))
1513
- : (y_axis.label || chem_axis_label(2))
1356
+ const label =
1357
+ axis === 0
1358
+ ? z_axis.label || chem_axis_label(0)
1359
+ : axis === 1
1360
+ ? x_axis.label || chem_axis_label(1)
1361
+ : y_axis.label || chem_axis_label(2)
1514
1362
 
1515
1363
  const tick_geoms: THREE.BufferGeometry[] = []
1516
1364
  const grid_geoms: THREE.BufferGeometry[] = []
@@ -1530,16 +1378,14 @@
1530
1378
  back[2] + out_y * tick_label_dist,
1531
1379
  )
1532
1380
  for (const val of ticks) {
1533
- tick_geoms.push(make_line_geom(
1534
- swiz(val, back[1], back[2]),
1535
- swiz(val, back[1], back[2] + out_y * tick_size),
1536
- ))
1537
- grid_geoms.push(
1538
- make_line_geom(swiz(val, r1[0], back[2]), swiz(val, r1[1], back[2])),
1539
- )
1540
- grid_geoms.push(
1541
- make_line_geom(swiz(val, back[1], r2[0]), swiz(val, back[1], r2[1])),
1381
+ tick_geoms.push(
1382
+ make_line_geom(
1383
+ swiz(val, back[1], back[2]),
1384
+ swiz(val, back[1], back[2] + out_y * tick_size),
1385
+ ),
1542
1386
  )
1387
+ grid_geoms.push(make_line_geom(swiz(val, r1[0], back[2]), swiz(val, r1[1], back[2])))
1388
+ grid_geoms.push(make_line_geom(swiz(val, back[1], r2[0]), swiz(val, back[1], r2[1])))
1543
1389
  tick_labels.push({
1544
1390
  pos: swiz(
1545
1391
  val,
@@ -1560,16 +1406,14 @@
1560
1406
  back[2] + out_y * tick_label_dist,
1561
1407
  )
1562
1408
  for (const val of ticks) {
1563
- tick_geoms.push(make_line_geom(
1564
- swiz(back[0], val, back[2]),
1565
- swiz(back[0], val, back[2] + out_y * tick_size),
1566
- ))
1567
- grid_geoms.push(
1568
- make_line_geom(swiz(r0[0], val, back[2]), swiz(r0[1], val, back[2])),
1569
- )
1570
- grid_geoms.push(
1571
- make_line_geom(swiz(back[0], val, r2[0]), swiz(back[0], val, r2[1])),
1409
+ tick_geoms.push(
1410
+ make_line_geom(
1411
+ swiz(back[0], val, back[2]),
1412
+ swiz(back[0], val, back[2] + out_y * tick_size),
1413
+ ),
1572
1414
  )
1415
+ grid_geoms.push(make_line_geom(swiz(r0[0], val, back[2]), swiz(r0[1], val, back[2])))
1416
+ grid_geoms.push(make_line_geom(swiz(back[0], val, r2[0]), swiz(back[0], val, r2[1])))
1573
1417
  tick_labels.push({
1574
1418
  pos: swiz(back[0], val, back[2] + out_y * tick_label_dist),
1575
1419
  text: format_num(val, `.3~g`),
@@ -1586,16 +1430,14 @@
1586
1430
  outer_end(r2) + outer_direction(r2) * axis_label_dist,
1587
1431
  )
1588
1432
  for (const val of ticks) {
1589
- tick_geoms.push(make_line_geom(
1590
- swiz(back[0], back[1], val),
1591
- swiz(back[0], back[1] + out_x * tick_size, val),
1592
- ))
1593
- grid_geoms.push(
1594
- make_line_geom(swiz(r0[0], back[1], val), swiz(r0[1], back[1], val)),
1595
- )
1596
- grid_geoms.push(
1597
- make_line_geom(swiz(back[0], r1[0], val), swiz(back[0], r1[1], val)),
1433
+ tick_geoms.push(
1434
+ make_line_geom(
1435
+ swiz(back[0], back[1], val),
1436
+ swiz(back[0], back[1] + out_x * tick_size, val),
1437
+ ),
1598
1438
  )
1439
+ grid_geoms.push(make_line_geom(swiz(r0[0], back[1], val), swiz(r0[1], back[1], val)))
1440
+ grid_geoms.push(make_line_geom(swiz(back[0], r1[0], val), swiz(back[0], r1[1], val)))
1599
1441
  tick_labels.push({
1600
1442
  pos: swiz(back[0], back[1] + out_x * tick_label_dist, val),
1601
1443
  text: format_num(val, `.3~g`),
@@ -1623,21 +1465,17 @@
1623
1465
 
1624
1466
  function update_label_occlusion(): void {
1625
1467
  if (!wrapper) return
1626
- const tick_labels = Array.from(
1627
- wrapper.querySelectorAll<HTMLElement>(`.axis-tick-label`),
1628
- )
1468
+ const tick_labels = Array.from(wrapper.querySelectorAll<HTMLElement>(`.axis-tick-label`))
1629
1469
  tick_labels_occluded = false
1630
1470
  for (const tick_label of tick_labels) {
1631
1471
  tick_label.style.visibility = ``
1632
1472
  }
1633
- const domain_rects = Array.from(
1634
- wrapper.querySelectorAll<HTMLElement>(`.domain-label`),
1635
- )
1636
- .filter((domain_label) => {
1637
- const style = getComputedStyle(domain_label)
1473
+ const domain_rects = Array.from(wrapper.querySelectorAll<HTMLElement>(`.domain-label`))
1474
+ .filter((label_el) => {
1475
+ const style = getComputedStyle(label_el)
1638
1476
  return style.display !== `none` && style.visibility !== `hidden`
1639
1477
  })
1640
- .map((domain_label) => pad_rect(domain_label.getBoundingClientRect(), 1))
1478
+ .map((label_el) => pad_rect(label_el.getBoundingClientRect(), 1))
1641
1479
  if (domain_rects.length === 0) return
1642
1480
 
1643
1481
  for (const tick_label of tick_labels) {
@@ -1700,11 +1538,7 @@
1700
1538
  }
1701
1539
  const controls_target = controls?.target
1702
1540
  if (controls_target) {
1703
- camera_target_override = [
1704
- controls_target.x,
1705
- controls_target.y,
1706
- controls_target.z,
1707
- ]
1541
+ camera_target_override = [controls_target.x, controls_target.y, controls_target.z]
1708
1542
  }
1709
1543
  }
1710
1544
 
@@ -1769,28 +1603,20 @@
1769
1603
  bg_css_var: `--chempot-3d-bg-fullscreen`,
1770
1604
  })
1771
1605
 
1772
- $effect(() => {
1773
- const grid_geometries = grid_config
1774
- return () => {
1775
- for (const grid_item of grid_geometries) {
1776
- dispose_geometry(grid_item.line_geom)
1777
- for (const tick_geometry of grid_item.tick_geoms) {
1778
- dispose_geometry(tick_geometry)
1779
- }
1780
- for (const line_geometry of grid_item.grid_geoms) {
1781
- dispose_geometry(line_geometry)
1782
- }
1783
- }
1784
- }
1785
- })
1606
+ dispose_on_change(() =>
1607
+ grid_config.flatMap((grid_item) => [
1608
+ grid_item.line_geom,
1609
+ ...grid_item.tick_geoms,
1610
+ ...grid_item.grid_geoms,
1611
+ ]),
1612
+ )
1786
1613
 
1787
1614
  const projection_planes = $derived.by(() => {
1788
1615
  const projections = display.projections
1789
1616
  if (!projections) return []
1790
1617
  const [r0, r1, r2] = niced_range
1791
- const s0 = (r0[1] - r0[0]) * (display.projection_scale ?? 0.5)
1792
- const s1 = (r1[1] - r1[0]) * (display.projection_scale ?? 0.5)
1793
- const s2 = (r2[1] - r2[0]) * (display.projection_scale ?? 0.5)
1618
+ const projection_scale = display.projection_scale ?? 0.5
1619
+ const [s0, s1, s2] = niced_range.map(([lo, hi]) => (hi - lo) * projection_scale)
1794
1620
  const planes: {
1795
1621
  key: string
1796
1622
  pos: Vec3
@@ -1880,18 +1706,12 @@
1880
1706
  next_projection[current_owner_idx] = next_projection[axis_idx]
1881
1707
  }
1882
1708
  next_projection[axis_idx] = element
1883
- const normalized = normalize_projection_triplet(
1884
- next_projection,
1885
- all_entry_elements,
1886
- )
1709
+ const normalized = normalize_projection_triplet(next_projection, all_entry_elements)
1887
1710
  if (normalized) projection_elements_override = normalized
1888
1711
  }
1889
1712
 
1890
1713
  function apply_projection_preset(preset_elements: string[]): void {
1891
- const normalized = normalize_projection_triplet(
1892
- preset_elements,
1893
- all_entry_elements,
1894
- )
1714
+ const normalized = normalize_projection_triplet(preset_elements, all_entry_elements)
1895
1715
  if (normalized) projection_elements_override = normalized
1896
1716
  }
1897
1717
 
@@ -1968,19 +1788,21 @@
1968
1788
  },
1969
1789
  {
1970
1790
  label: `View`,
1971
- on_download: () =>
1972
- export_view_json_file(current_view_settings(), export_basename),
1791
+ on_download: () => export_view_json_file(current_view_settings(), export_basename),
1973
1792
  },
1974
1793
  {
1975
1794
  label: `GLB`,
1976
1795
  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),
1796
+ export_glb_file(
1797
+ {
1798
+ hull_geometry: colored_hull_geometry,
1799
+ hull_opacity: color_mode === `none` ? 0.25 : 0.4,
1800
+ edge_geometry,
1801
+ formula_meshes: formula_mesh_data,
1802
+ formula_edges: draw_formula_lines ? formula_edge_data : [],
1803
+ },
1804
+ export_basename,
1805
+ ),
1984
1806
  },
1985
1807
  ],
1986
1808
  },
@@ -1997,10 +1819,12 @@
1997
1819
  const pointer = hover_info?.pointer
1998
1820
  if (!pointer) return { x: 4, y: 4 }
1999
1821
  return constrain_tooltip_position(
2000
- pointer.x, pointer.y,
1822
+ pointer.x,
1823
+ pointer.y,
2001
1824
  tooltip_el?.offsetWidth ?? 200,
2002
1825
  tooltip_el?.offsetHeight ?? 100,
2003
- container_width, container_height,
1826
+ container_width,
1827
+ container_height,
2004
1828
  { offset: 0 },
2005
1829
  )
2006
1830
  })
@@ -2018,12 +1842,22 @@
2018
1842
  hover_info = null
2019
1843
  }
2020
1844
 
1845
+ function stop_phase_pointer_event(raw_event: unknown): void {
1846
+ const event = raw_event as
1847
+ | { nativeEvent?: { stopPropagation?: () => void }; stopPropagation?: () => void }
1848
+ | null
1849
+ | undefined
1850
+ event?.stopPropagation?.()
1851
+ event?.nativeEvent?.stopPropagation?.()
1852
+ }
1853
+
2021
1854
  function handle_phase_hover(domain_data: HoverMeshData, raw_event: unknown): void {
2022
1855
  if (locked_hover_formula && locked_hover_formula !== domain_data.formula) return
2023
1856
  set_hover_info(domain_data, raw_event)
2024
1857
  }
2025
1858
 
2026
1859
  function toggle_phase_lock(domain_data: HoverMeshData, raw_event: unknown): void {
1860
+ stop_phase_pointer_event(raw_event)
2027
1861
  if (locked_hover_formula === domain_data.formula) {
2028
1862
  clear_hover_lock()
2029
1863
  return
@@ -2053,20 +1887,15 @@
2053
1887
  role="application"
2054
1888
  tabindex="0"
2055
1889
  onkeydown={(event) => {
2056
- if (
2057
- event.target instanceof HTMLInputElement ||
2058
- event.target instanceof HTMLSelectElement
2059
- ) return
1890
+ if (event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement)
1891
+ return
2060
1892
  if (event.key === `Escape`) clear_hover_lock()
2061
1893
  else if (event.key === `c`) cycle_color_mode()
2062
1894
  else if (event.key === `f` && fullscreen_toggle) toggle_fullscreen(wrapper)
2063
1895
  }}
2064
1896
  onpointerdown={(event) => {
2065
1897
  const target = event.target
2066
- if (
2067
- locked_hover_formula &&
2068
- (target === wrapper || target instanceof HTMLCanvasElement)
2069
- ) {
1898
+ if (locked_hover_formula && (target === wrapper || target instanceof HTMLCanvasElement)) {
2070
1899
  clear_hover_lock()
2071
1900
  }
2072
1901
  }}
@@ -2102,11 +1931,7 @@
2102
1931
  </div>
2103
1932
  <label class="overlay-search">
2104
1933
  Search:
2105
- <input
2106
- type="text"
2107
- placeholder="Formula filter"
2108
- bind:value={formula_filter_query}
2109
- />
1934
+ <input type="text" placeholder="Formula filter" bind:value={formula_filter_query} />
2110
1935
  </label>
2111
1936
  <div class="formula-list">
2112
1937
  {#if filtered_formulas.length === 0}
@@ -2117,14 +1942,14 @@
2117
1942
  <label>
2118
1943
  <input
2119
1944
  type="checkbox"
2120
- checked={formulas_to_draw.includes(formula)}
1945
+ checked={formula_overlay_idx !== -1}
2121
1946
  onchange={() => toggle_formula_selection(formula)}
2122
1947
  />
2123
1948
  <span
2124
1949
  class="formula-color-dot"
2125
1950
  style:background={formula_colors[
2126
1951
  (formula_overlay_idx >= 0 ? formula_overlay_idx : formula_idx) %
2127
- formula_colors.length
1952
+ formula_colors.length
2128
1953
  ]}
2129
1954
  ></span>
2130
1955
  {get_electro_neg_formula(formula, true, ``, `.3~s`)}
@@ -2231,8 +2056,7 @@
2231
2056
  <input
2232
2057
  type="checkbox"
2233
2058
  checked={draw_formula_meshes}
2234
- onchange={() =>
2235
- overrides.set(`draw_formula_meshes`, !draw_formula_meshes)}
2059
+ onchange={() => overrides.set(`draw_formula_meshes`, !draw_formula_meshes)}
2236
2060
  /> Meshes
2237
2061
  </label>
2238
2062
  <label>
@@ -2252,7 +2076,7 @@
2252
2076
  step="0.1"
2253
2077
  value={element_padding}
2254
2078
  oninput={(event) =>
2255
- overrides.set(`element_padding`, Number(event.currentTarget.value))}
2079
+ overrides.set(`element_padding`, Number(event.currentTarget.value))}
2256
2080
  />
2257
2081
  </label>
2258
2082
  <label>
@@ -2263,7 +2087,7 @@
2263
2087
  step="1"
2264
2088
  value={default_min_limit}
2265
2089
  oninput={(event) =>
2266
- overrides.set(`default_min_limit`, Number(event.currentTarget.value))}
2090
+ overrides.set(`default_min_limit`, Number(event.currentTarget.value))}
2267
2091
  />
2268
2092
  </label>
2269
2093
  </div>
@@ -2273,7 +2097,7 @@
2273
2097
  id="chempot-color-mode"
2274
2098
  value={color_mode}
2275
2099
  onchange={(event) =>
2276
- overrides.set(`color_mode`, event.currentTarget.value as ChemPotColorMode)}
2100
+ overrides.set(`color_mode`, event.currentTarget.value as ChemPotColorMode)}
2277
2101
  >
2278
2102
  {#each CHEMPOT_COLOR_MODE_OPTIONS as [value, label] (value)}
2279
2103
  <option {value}>{label}</option>
@@ -2287,10 +2111,7 @@
2287
2111
  id="chempot-color-scale"
2288
2112
  value={color_scale}
2289
2113
  onchange={(event) =>
2290
- overrides.set(
2291
- `color_scale`,
2292
- event.currentTarget.value as D3InterpolateName,
2293
- )}
2114
+ overrides.set(`color_scale`, event.currentTarget.value as D3InterpolateName)}
2294
2115
  >
2295
2116
  {#each CHEMPOT_COLOR_SCALE_OPTIONS as [value, label] (value)}
2296
2117
  <option {value}>{label}</option>
@@ -2300,8 +2121,7 @@
2300
2121
  <input
2301
2122
  type="checkbox"
2302
2123
  checked={reverse_color_scale}
2303
- onchange={() =>
2304
- overrides.set(`reverse_color_scale`, !reverse_color_scale)}
2124
+ onchange={() => overrides.set(`reverse_color_scale`, !reverse_color_scale)}
2305
2125
  /> Rev
2306
2126
  </label>
2307
2127
  </div>
@@ -2314,312 +2134,297 @@
2314
2134
  {/if}
2315
2135
  </section>
2316
2136
  {#if show_temperature_slider && temperature !== undefined}
2317
- <TemperatureSlider
2318
- class="chempot-temp-slider"
2319
- {available_temperatures}
2320
- bind:temperature
2321
- />
2137
+ <TemperatureSlider class="chempot-temp-slider" {available_temperatures} bind:temperature />
2322
2138
  {/if}
2323
2139
  <div class="canvas-clip">
2324
- {#if diagram_computing}
2325
- <div class="computing-state">
2326
- <Spinner text="Computing chemical potential domains..." style="--spinner-size: 1.2em" />
2327
- </div>
2328
- {:else if !diagram_data}
2329
- <div class="error-state" role="alert" aria-live="polite">
2330
- <p>Cannot compute chemical potential diagram.</p>
2331
- <p>Need at least 2 elements with elemental reference entries.</p>
2332
- </div>
2333
- {:else if mounted && typeof WebGLRenderingContext !== `undefined`}
2334
- <Canvas
2335
- createRenderer={(cvs) =>
2336
- new THREE.WebGLRenderer({
2337
- canvas: cvs,
2338
- alpha: true,
2339
- antialias: true,
2340
- preserveDrawingBuffer: true,
2341
- })}
2342
- >
2343
- <ChemPotScene3D>
2344
- {#if camera_projection === `orthographic`}
2345
- <!-- Orthographic camera matching pymatgen's projection style -->
2346
- <T.OrthographicCamera
2347
- makeDefault
2348
- position={camera_position}
2349
- zoom={orthographic_zoom}
2350
- near={0.1}
2351
- far={data_extent * 10}
2352
- >
2353
- <extras.OrbitControls
2354
- bind:ref={orbit_controls_ref}
2355
- enableRotate
2356
- enableZoom
2357
- enablePan
2358
- autoRotate={auto_rotate > 0}
2359
- autoRotateSpeed={auto_rotate}
2360
- target={camera_target}
2361
- />
2362
- </T.OrthographicCamera>
2363
- {:else}
2364
- <T.PerspectiveCamera
2365
- makeDefault
2366
- position={camera_position}
2367
- fov={50}
2368
- near={0.1}
2369
- far={data_extent * 10}
2370
- >
2371
- <extras.OrbitControls
2372
- bind:ref={orbit_controls_ref}
2373
- enableRotate
2374
- enableZoom
2375
- enablePan
2376
- autoRotate={auto_rotate > 0}
2377
- autoRotateSpeed={auto_rotate}
2378
- target={camera_target}
2379
- />
2380
- </T.PerspectiveCamera>
2381
- {/if}
2140
+ {#if diagram_computing}
2141
+ <div class="computing-state">
2142
+ <Spinner
2143
+ text="Computing chemical potential domains..."
2144
+ style="--spinner-size: 1.2em"
2145
+ />
2146
+ </div>
2147
+ {:else if !diagram_data}
2148
+ <div class="error-state" role="alert" aria-live="polite">
2149
+ <p>Cannot compute chemical potential diagram.</p>
2150
+ <p>Need at least 2 elements with elemental reference entries.</p>
2151
+ </div>
2152
+ {:else if mounted && typeof WebGLRenderingContext !== `undefined`}
2153
+ {#snippet orbit_controls()}
2154
+ <extras.OrbitControls
2155
+ bind:ref={orbit_controls_ref}
2156
+ enableRotate
2157
+ enableZoom
2158
+ enablePan
2159
+ autoRotate={auto_rotate > 0 && page_visibility.visible}
2160
+ autoRotateSpeed={auto_rotate}
2161
+ target={camera_target}
2162
+ />
2163
+ {/snippet}
2164
+ <Canvas
2165
+ createRenderer={(cvs) =>
2166
+ new THREE.WebGLRenderer({
2167
+ canvas: cvs,
2168
+ alpha: true,
2169
+ antialias: true,
2170
+ preserveDrawingBuffer: true,
2171
+ })}
2172
+ >
2173
+ <ChemPotScene3D>
2174
+ {#if camera_projection === `orthographic`}
2175
+ <!-- Orthographic camera matching pymatgen's projection style -->
2176
+ <T.OrthographicCamera
2177
+ makeDefault
2178
+ position={camera_position}
2179
+ zoom={orthographic_zoom}
2180
+ near={0.1}
2181
+ far={data_extent * 10}
2182
+ >
2183
+ {@render orbit_controls()}
2184
+ </T.OrthographicCamera>
2185
+ {:else}
2186
+ <T.PerspectiveCamera
2187
+ makeDefault
2188
+ position={camera_position}
2189
+ fov={50}
2190
+ near={0.1}
2191
+ far={data_extent * 10}
2192
+ >
2193
+ {@render orbit_controls()}
2194
+ </T.PerspectiveCamera>
2195
+ {/if}
2382
2196
 
2383
- <!-- Ambient light for visibility -->
2384
- <T.AmbientLight intensity={0.8} />
2385
- <T.DirectionalLight position={[1, 1, 1]} intensity={0.5} />
2197
+ <!-- Ambient light for visibility -->
2198
+ <T.AmbientLight intensity={0.8} />
2199
+ <T.DirectionalLight position={[1, 1, 1]} intensity={0.5} />
2386
2200
 
2387
- <!-- Vertex-colored hull for both plain and colored modes.
2201
+ <!-- Vertex-colored hull for both plain and colored modes.
2388
2202
  {#key domain_colors} forces Threlte to re-create the mesh whenever
2389
2203
  colors change (covers color_mode, color_scale, and data updates),
2390
2204
  since on-demand rendering won't detect mutated vertex color buffers. -->
2391
- {#if colored_hull_geometry}
2392
- {#key domain_colors}
2393
- <T.Mesh geometry={colored_hull_geometry}>
2205
+ {#if colored_hull_geometry}
2206
+ {#key domain_colors}
2207
+ <T.Mesh geometry={colored_hull_geometry}>
2208
+ <T.MeshBasicMaterial
2209
+ vertexColors
2210
+ transparent
2211
+ opacity={color_mode === `none` ? 0.25 : 0.4}
2212
+ side={THREE.DoubleSide}
2213
+ polygonOffset
2214
+ polygonOffsetFactor={1}
2215
+ polygonOffsetUnits={1}
2216
+ />
2217
+ </T.Mesh>
2218
+ {/key}
2219
+ {/if}
2220
+
2221
+ <!-- Domain boundary edges (wireframe on top of opaque fills) -->
2222
+ <T.LineSegments geometry={edge_geometry}>
2223
+ <T.LineBasicMaterial color={0x333333} linewidth={1} />
2224
+ </T.LineSegments>
2225
+
2226
+ <!-- Invisible pick meshes for per-phase hover tooltip -->
2227
+ {#each hover_mesh_data as domain_hover (domain_hover.formula)}
2228
+ <T.Mesh
2229
+ geometry={domain_hover.geometry}
2230
+ onpointerenter={(event: unknown) => handle_phase_hover(domain_hover, event)}
2231
+ onpointermove={(event: unknown) => handle_phase_hover(domain_hover, event)}
2232
+ onpointerdown={(event: unknown) => toggle_phase_lock(domain_hover, event)}
2233
+ onpointerleave={() => {
2234
+ if (!locked_hover_formula && hover_info?.formula === domain_hover.formula) {
2235
+ hover_info = null
2236
+ }
2237
+ }}
2238
+ >
2394
2239
  <T.MeshBasicMaterial
2395
- vertexColors
2396
2240
  transparent
2397
- opacity={color_mode === `none` ? 0.25 : 0.4}
2241
+ opacity={0}
2398
2242
  side={THREE.DoubleSide}
2399
- polygonOffset
2400
- polygonOffsetFactor={1}
2401
- polygonOffsetUnits={1}
2243
+ depthWrite={false}
2402
2244
  />
2403
2245
  </T.Mesh>
2404
- {/key}
2405
- {/if}
2246
+ {/each}
2406
2247
 
2407
- <!-- Domain boundary edges (wireframe on top of opaque fills) -->
2408
- <T.LineSegments geometry={edge_geometry}>
2409
- <T.LineBasicMaterial color={0x333333} linewidth={1} />
2410
- </T.LineSegments>
2411
-
2412
- <!-- Invisible pick meshes for per-phase hover tooltip -->
2413
- {#each hover_mesh_data as domain_hover (domain_hover.formula)}
2414
- <T.Mesh
2415
- geometry={domain_hover.geometry}
2416
- onpointerenter={(event: unknown) => handle_phase_hover(domain_hover, event)}
2417
- onpointermove={(event: unknown) => handle_phase_hover(domain_hover, event)}
2418
- onpointerdown={(event: unknown) => toggle_phase_lock(domain_hover, event)}
2419
- onpointerleave={() => {
2420
- if (
2421
- !locked_hover_formula &&
2422
- hover_info?.formula === domain_hover.formula
2423
- ) {
2424
- hover_info = null
2425
- }
2426
- }}
2427
- >
2428
- <T.MeshBasicMaterial
2429
- transparent
2430
- opacity={0}
2431
- side={THREE.DoubleSide}
2432
- depthWrite={false}
2433
- />
2434
- </T.Mesh>
2435
- {/each}
2436
-
2437
- <!-- Formula overlay meshes (semi-transparent colored fill) -->
2438
- {#each formula_mesh_data as { geometry, color }, mesh_idx (mesh_idx)}
2439
- <T.Mesh {geometry}>
2440
- <T.MeshBasicMaterial
2441
- color={new THREE.Color(color)}
2442
- transparent
2443
- opacity={0.13}
2444
- side={THREE.DoubleSide}
2445
- depthWrite={false}
2446
- />
2447
- </T.Mesh>
2448
- {/each}
2449
-
2450
- <!-- Formula overlay edges (colored, thicker) -->
2451
- {#if draw_formula_lines}
2452
- {#each formula_edge_data as { geometry, color }, edge_idx (edge_idx)}
2453
- <T.LineSegments {geometry}>
2454
- <T.LineBasicMaterial color={new THREE.Color(color)} linewidth={2} />
2455
- </T.LineSegments>
2248
+ <!-- Formula overlay meshes (semi-transparent colored fill) -->
2249
+ {#each formula_mesh_data as { geometry, color }, mesh_idx (mesh_idx)}
2250
+ <T.Mesh {geometry}>
2251
+ <T.MeshBasicMaterial
2252
+ color={new THREE.Color(color)}
2253
+ transparent
2254
+ opacity={0.13}
2255
+ side={THREE.DoubleSide}
2256
+ depthWrite={false}
2257
+ />
2258
+ </T.Mesh>
2456
2259
  {/each}
2457
- {/if}
2458
2260
 
2459
- {#each projection_planes as plane (`${plane.key}-${projection_opacity}`)}
2460
- <T.Mesh position={plane.pos} rotation={plane.rot}>
2461
- <T.PlaneGeometry args={plane.size} />
2462
- <T.MeshBasicMaterial
2463
- color={plane.color}
2464
- opacity={projection_opacity}
2465
- transparent
2466
- side={THREE.DoubleSide}
2467
- depthWrite={false}
2468
- />
2469
- </T.Mesh>
2470
- {/each}
2261
+ <!-- Formula overlay edges (colored, thicker) -->
2262
+ {#if draw_formula_lines}
2263
+ {#each formula_edge_data as { geometry, color }, edge_idx (edge_idx)}
2264
+ <T.LineSegments {geometry}>
2265
+ <T.LineBasicMaterial color={new THREE.Color(color)} linewidth={2} />
2266
+ </T.LineSegments>
2267
+ {/each}
2268
+ {/if}
2471
2269
 
2472
- {#if display.show_bounding_box}
2473
- <T.LineSegments geometry={bounding_box_geometry}>
2474
- <T.LineBasicMaterial color="#666" opacity={0.6} transparent />
2475
- </T.LineSegments>
2476
- {/if}
2270
+ {#each projection_planes as plane (`${plane.key}-${projection_opacity}`)}
2271
+ <T.Mesh position={plane.pos} rotation={plane.rot}>
2272
+ <T.PlaneGeometry args={plane.size} />
2273
+ <T.MeshBasicMaterial
2274
+ color={plane.color}
2275
+ opacity={projection_opacity}
2276
+ transparent
2277
+ side={THREE.DoubleSide}
2278
+ depthWrite={false}
2279
+ />
2280
+ </T.Mesh>
2281
+ {/each}
2477
2282
 
2478
- <!-- Axes, ticks, grid lines, and labels -->
2479
- {#each grid_config as gc (gc.axis)}
2480
- {#if display.show_axes}
2481
- <!-- Main axis line -->
2482
- <T.Line geometry={gc.line_geom}>
2483
- <T.LineBasicMaterial color={gc.color} linewidth={2} />
2484
- </T.Line>
2485
- <!-- Tick marks -->
2486
- {#each gc.tick_geoms as tick_geom, tdx (tdx)}
2487
- <T.Line geometry={tick_geom}>
2488
- <T.LineBasicMaterial color={gc.color} />
2489
- </T.Line>
2490
- {/each}
2283
+ {#if display.show_bounding_box}
2284
+ <T.LineSegments geometry={bounding_box_geometry}>
2285
+ <T.LineBasicMaterial color="#666" opacity={0.6} transparent />
2286
+ </T.LineSegments>
2491
2287
  {/if}
2492
- {#if display.show_grid}
2493
- <!-- Grid lines -->
2494
- {#each gc.grid_geoms as grid_geom, gdx (gdx)}
2495
- <T.Line geometry={grid_geom}>
2496
- <T.LineBasicMaterial color="#888" opacity={0.3} transparent />
2288
+
2289
+ <!-- Axes, ticks, grid lines, and labels -->
2290
+ {#each grid_config as gc (gc.axis)}
2291
+ {#if display.show_axes}
2292
+ <!-- Main axis line -->
2293
+ <T.Line geometry={gc.line_geom}>
2294
+ <T.LineBasicMaterial color={gc.color} linewidth={2} />
2497
2295
  </T.Line>
2498
- {/each}
2499
- {/if}
2500
- {#if display.show_axis_labels}
2501
- <!-- Tick labels (billboarded, always face camera) -->
2502
- {#each gc.tick_labels as tick, tick_idx (tick_idx)}
2296
+ <!-- Tick marks -->
2297
+ {#each gc.tick_geoms as tick_geom, tdx (tdx)}
2298
+ <T.Line geometry={tick_geom}>
2299
+ <T.LineBasicMaterial color={gc.color} />
2300
+ </T.Line>
2301
+ {/each}
2302
+ {/if}
2303
+ {#if display.show_grid}
2304
+ <!-- Grid lines -->
2305
+ {#each gc.grid_geoms as grid_geom, gdx (gdx)}
2306
+ <T.Line geometry={grid_geom}>
2307
+ <T.LineBasicMaterial color="#888" opacity={0.3} transparent />
2308
+ </T.Line>
2309
+ {/each}
2310
+ {/if}
2311
+ {#if display.show_axis_labels}
2312
+ <!-- Tick labels (billboarded, always face camera) -->
2313
+ {#each gc.tick_labels as tick, tick_idx (tick_idx)}
2314
+ <extras.HTML position={tick.pos} center portal={wrapper} zIndexRange={[1, 0]}>
2315
+ <span class="tick-label axis-tick-label">{tick.text}</span>
2316
+ </extras.HTML>
2317
+ {/each}
2318
+ <!-- Axis label -->
2503
2319
  <extras.HTML
2504
- position={tick.pos}
2320
+ position={gc.label_pos}
2505
2321
  center
2506
2322
  portal={wrapper}
2507
2323
  zIndexRange={[1, 0]}
2508
2324
  >
2509
- <span class="tick-label axis-tick-label">{tick.text}</span>
2325
+ <span class="axis-label" style:color={gc.color}
2326
+ >{@html sanitize_html(gc.label)}</span
2327
+ >
2328
+ </extras.HTML>
2329
+ {/if}
2330
+ {/each}
2331
+
2332
+ <!-- Domain labels -->
2333
+ {#if label_stable}
2334
+ {#each visible_domain_labels as domain (domain.formula)}
2335
+ <extras.HTML
2336
+ position={domain.position}
2337
+ center
2338
+ portal={wrapper}
2339
+ zIndexRange={[5, 5]}
2340
+ >
2341
+ <span
2342
+ class="domain-label"
2343
+ style:font-size="{(domain.label_font_size * zoom_scale).toFixed(1)}px"
2344
+ >
2345
+ {#each formula_label_segments(domain.formula) as segment}
2346
+ <span class:formula-subscript={segment.subscript}>{segment.text}</span>
2347
+ {/each}
2348
+ </span>
2510
2349
  </extras.HTML>
2511
2350
  {/each}
2512
- <!-- Axis label -->
2513
- <extras.HTML
2514
- position={gc.label_pos}
2515
- center
2516
- portal={wrapper}
2517
- zIndexRange={[1, 0]}
2518
- >
2519
- <span class="axis-label" style:color={gc.color}>{@html gc.label}</span>
2520
- </extras.HTML>
2521
2351
  {/if}
2522
- {/each}
2523
-
2524
- <!-- Domain labels -->
2525
- {#if label_stable}
2526
- {#each visible_domain_labels as domain (domain.formula)}
2527
- <extras.HTML
2528
- position={domain.position}
2529
- center
2530
- portal={wrapper}
2531
- zIndexRange={[5, 5]}
2532
- >
2533
- <span
2534
- class="domain-label"
2535
- style:font-size="{(domain.label_font_size * zoom_scale).toFixed(1)}px"
2536
- >
2537
- {#each formula_label_segments(domain.formula) as segment}
2538
- <span class:formula-subscript={segment.subscript}>{segment.text}</span>
2539
- {/each}
2540
- </span>
2541
- </extras.HTML>
2352
+ </ChemPotScene3D>
2353
+ </Canvas>
2354
+ <!-- Color bar for continuous modes -->
2355
+ {#if color_range}
2356
+ {@const color_bar_config = get_chempot_color_bar_config(
2357
+ color_scale,
2358
+ reverse_color_scale,
2359
+ )}
2360
+ <ColorBar
2361
+ title={color_range.label}
2362
+ range={[color_range.min, color_range.max]}
2363
+ color_scale_fn={color_bar_config.color_scale_fn}
2364
+ color_scale_domain={color_bar_config.color_scale_domain}
2365
+ wrapper_style="position: absolute; bottom: 16px; left: 1em; width: 200px; z-index: 10;"
2366
+ bar_style="height: 12px;"
2367
+ title_style="margin-bottom: 4px;"
2368
+ />
2369
+ {/if}
2370
+ <!-- Categorical legend for arity mode -->
2371
+ {#if color_mode === `arity`}
2372
+ <div class="arity-legend">
2373
+ {#each arity_legend_labels as label, idx (label)}
2374
+ <span>
2375
+ <span style:background={ARITY_COLORS[idx]}></span>
2376
+ {label}
2377
+ </span>
2542
2378
  {/each}
2543
- {/if}
2544
- </ChemPotScene3D>
2545
- </Canvas>
2546
- <!-- Color bar for continuous modes -->
2547
- {#if color_mode !== `none` && color_mode !== `arity` && color_range}
2548
- {@const color_bar_config = get_chempot_color_bar_config(
2549
- color_scale,
2550
- reverse_color_scale,
2551
- )}
2552
- <ColorBar
2553
- title={color_range.label}
2554
- range={[color_range.min, color_range.max]}
2555
- color_scale_fn={color_bar_config.color_scale_fn}
2556
- color_scale_domain={color_bar_config.color_scale_domain}
2557
- wrapper_style="position: absolute; bottom: 16px; left: 1em; width: 200px; z-index: 10;"
2558
- bar_style="height: 12px;"
2559
- title_style="margin-bottom: 4px;"
2560
- />
2561
- {/if}
2562
- <!-- Categorical legend for arity mode -->
2563
- {#if color_mode === `arity`}
2564
- <div class="arity-legend">
2565
- {#each arity_legend_labels as label, idx (label)}
2566
- <span>
2567
- <span style:background={arity_colors[idx]}></span>
2568
- {label}
2569
- </span>
2570
- {/each}
2571
- </div>
2572
- {/if}
2573
- {/if}
2574
- {#if render_local_tooltip && show_tooltip && hover_info?.view === `3d`}
2575
- <aside
2576
- bind:this={tooltip_el}
2577
- class="phase-tooltip"
2578
- style:left="{tooltip_pos.x}px"
2579
- style:top="{tooltip_pos.y}px"
2580
- >
2581
- <h4>
2582
- {#each formula_label_segments(hover_info.formula) as segment}
2583
- <span class:formula-subscript={segment.subscript}>{segment.text}</span>
2584
- {/each}
2585
- </h4>
2586
- {#if locked_hover_formula === hover_info.formula}
2587
- <p>Pinned · Press Esc to unlock</p>
2379
+ </div>
2588
2380
  {/if}
2589
- <p>
2590
- Vertices: {hover_info.n_vertices} · Edges: {hover_info.n_edges} · Points:
2591
- {hover_info.n_points}
2592
- </p>
2593
- <p>
2594
- Entries: {hover_info.matching_entry_count}
2595
- {#if hover_info.min_energy_per_atom !== null &&
2596
- hover_info.max_energy_per_atom !== null}
2597
- · E/atom: {format_num(hover_info.min_energy_per_atom, `.4~g`)}
2598
- to {format_num(hover_info.max_energy_per_atom, `.4~g`)} eV
2381
+ {/if}
2382
+ {#if render_local_tooltip && show_tooltip && hover_info?.view === `3d`}
2383
+ <aside
2384
+ bind:this={tooltip_el}
2385
+ class="phase-tooltip"
2386
+ style:left="{tooltip_pos.x}px"
2387
+ style:top="{tooltip_pos.y}px"
2388
+ >
2389
+ <h4>
2390
+ {#each formula_label_segments(hover_info.formula) as segment}
2391
+ <span class:formula-subscript={segment.subscript}>{segment.text}</span>
2392
+ {/each}
2393
+ </h4>
2394
+ {#if locked_hover_formula === hover_info.formula}
2395
+ <p>Pinned · Press Esc to unlock</p>
2599
2396
  {/if}
2600
- </p>
2601
- {#if tooltip_detail_level === `detailed`}
2602
- <h5 id="axis-ranges">Axis ranges</h5>
2603
- {#each hover_info.axis_ranges as axis_range (axis_range.element)}
2604
- <p>
2605
- {axis_range.element}: {format_num(axis_range.min_val, `.4~g`)} to
2606
- {format_num(axis_range.max_val, `.4~g`)} eV
2607
- </p>
2608
- {/each}
2609
2397
  <p>
2610
- Centroid: ({
2611
- hover_info.ann_loc.map((value) => format_num(value, `.3~g`)).join(
2612
- `, `,
2613
- )
2614
- })
2398
+ Vertices: {hover_info.n_vertices} · Edges: {hover_info.n_edges} · Points:
2399
+ {hover_info.n_points}
2400
+ </p>
2401
+ <p>
2402
+ Entries: {hover_info.matching_entry_count}
2403
+ {#if hover_info.min_energy_per_atom !== null && hover_info.max_energy_per_atom !== null}
2404
+ · E/atom: {format_num(hover_info.min_energy_per_atom, `.4~g`)}
2405
+ to {format_num(hover_info.max_energy_per_atom, `.4~g`)} eV
2406
+ {/if}
2615
2407
  </p>
2616
- {#if hover_info.touches_limits.length > 0}
2617
- <h5 id="touches-bounds">Touches bounds</h5>
2618
- <p>{hover_info.touches_limits.join(`, `)}</p>
2408
+ {#if tooltip_detail_level === `detailed`}
2409
+ <h5 id="axis-ranges">Axis ranges</h5>
2410
+ {#each hover_info.axis_ranges as axis_range (axis_range.element)}
2411
+ <p>
2412
+ {axis_range.element}: {format_num(axis_range.min_val, `.4~g`)} to
2413
+ {format_num(axis_range.max_val, `.4~g`)} eV
2414
+ </p>
2415
+ {/each}
2416
+ <p>
2417
+ Centroid: ({hover_info.ann_loc
2418
+ .map((value) => format_num(value, `.3~g`))
2419
+ .join(`, `)})
2420
+ </p>
2421
+ {#if hover_info.touches_limits.length > 0}
2422
+ <h5 id="touches-bounds">Touches bounds</h5>
2423
+ <p>{hover_info.touches_limits.join(`, `)}</p>
2424
+ {/if}
2619
2425
  {/if}
2620
- {/if}
2621
- </aside>
2622
- {/if}
2426
+ </aside>
2427
+ {/if}
2623
2428
  </div>
2624
2429
  </div>
2625
2430
 
@@ -2708,8 +2513,10 @@
2708
2513
  }
2709
2514
  .chempot-diagram-3d :global(.projection-controls .pane-row) {
2710
2515
  display: grid;
2711
- grid-template-columns:
2712
- auto minmax(4.5em, 1fr) auto minmax(4.5em, 1fr) auto minmax(4.5em, 1fr);
2516
+ grid-template-columns: auto minmax(4.5em, 1fr) auto minmax(4.5em, 1fr) auto minmax(
2517
+ 4.5em,
2518
+ 1fr
2519
+ );
2713
2520
  align-items: center;
2714
2521
  gap: 3pt;
2715
2522
  }
@@ -2849,10 +2656,7 @@
2849
2656
  .phase-tooltip {
2850
2657
  position: absolute;
2851
2658
  max-width: min(32rem, 92vw);
2852
- background: var(
2853
- --tooltip-bg,
2854
- light-dark(rgba(255, 255, 255, 0.95), rgba(0, 0, 0, 0.9))
2855
- );
2659
+ background: var(--tooltip-bg, light-dark(rgba(255, 255, 255, 0.95), rgba(0, 0, 0, 0.9)));
2856
2660
  color: var(--tooltip-text, var(--text-color, #222));
2857
2661
  border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
2858
2662
  border-radius: 6px;