matterviz 0.3.6 → 0.3.7

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 (863) hide show
  1. package/dist/EmptyState.svelte.d.ts +9 -0
  2. package/dist/FilePicker.svelte +360 -0
  3. package/dist/FilePicker.svelte.d.ts +17 -0
  4. package/dist/Icon.svelte.d.ts +13 -0
  5. package/dist/MillerIndexInput.svelte +66 -0
  6. package/dist/MillerIndexInput.svelte.d.ts +7 -0
  7. package/dist/api/mp.d.ts +6 -0
  8. package/dist/api/mp.js +22 -0
  9. package/dist/api/optimade.d.ts +45 -0
  10. package/dist/api/optimade.js +135 -0
  11. package/dist/brillouin/BrillouinZone.svelte +549 -0
  12. package/dist/brillouin/BrillouinZone.svelte.d.ts +83 -0
  13. package/dist/brillouin/BrillouinZoneControls.svelte +144 -0
  14. package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +17 -0
  15. package/dist/brillouin/BrillouinZoneExportPane.svelte +146 -0
  16. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +15 -0
  17. package/dist/brillouin/BrillouinZoneInfoPane.svelte +146 -0
  18. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +13 -0
  19. package/dist/brillouin/BrillouinZoneScene.svelte +476 -0
  20. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +48 -0
  21. package/dist/brillouin/BrillouinZoneTooltip.svelte +92 -0
  22. package/dist/brillouin/BrillouinZoneTooltip.svelte.d.ts +8 -0
  23. package/dist/brillouin/compute.d.ts +17 -0
  24. package/dist/brillouin/compute.js +426 -0
  25. package/dist/brillouin/index.d.ts +8 -0
  26. package/dist/brillouin/index.js +7 -0
  27. package/dist/brillouin/types.d.ts +43 -0
  28. package/dist/brillouin/types.js +1 -0
  29. package/dist/chempot-diagram/ChemPotDiagram.svelte +327 -0
  30. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  31. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +846 -0
  32. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  33. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +3193 -0
  34. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  35. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  36. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  37. package/dist/chempot-diagram/async-compute.svelte.js +78 -0
  38. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  39. package/dist/chempot-diagram/chempot-worker.js +11 -0
  40. package/dist/chempot-diagram/color.d.ts +10 -0
  41. package/dist/chempot-diagram/color.js +32 -0
  42. package/dist/chempot-diagram/compute.d.ts +48 -0
  43. package/dist/chempot-diagram/compute.js +806 -0
  44. package/dist/chempot-diagram/index.d.ts +6 -0
  45. package/dist/chempot-diagram/index.js +6 -0
  46. package/dist/chempot-diagram/pointer.d.ts +16 -0
  47. package/dist/chempot-diagram/pointer.js +40 -0
  48. package/dist/chempot-diagram/temperature.d.ts +15 -0
  49. package/dist/chempot-diagram/temperature.js +34 -0
  50. package/dist/chempot-diagram/types.d.ts +81 -0
  51. package/dist/chempot-diagram/types.js +28 -0
  52. package/dist/colors/index.d.ts +47 -0
  53. package/dist/colors/index.js +203 -0
  54. package/dist/composition/BarChart.svelte +297 -0
  55. package/dist/composition/BarChart.svelte.d.ts +39 -0
  56. package/dist/composition/BubbleChart.svelte +218 -0
  57. package/dist/composition/BubbleChart.svelte.d.ts +28 -0
  58. package/dist/composition/Composition.svelte +165 -0
  59. package/dist/composition/Composition.svelte.d.ts +15 -0
  60. package/dist/composition/Formula.svelte +268 -0
  61. package/dist/composition/Formula.svelte.d.ts +19 -0
  62. package/dist/composition/FormulaFilter.svelte +1257 -0
  63. package/dist/composition/FormulaFilter.svelte.d.ts +51 -0
  64. package/dist/composition/PieChart.svelte +323 -0
  65. package/dist/composition/PieChart.svelte.d.ts +37 -0
  66. package/dist/composition/format.d.ts +15 -0
  67. package/dist/composition/format.js +109 -0
  68. package/dist/composition/index.d.ts +20 -0
  69. package/dist/composition/index.js +14 -0
  70. package/dist/composition/parse.d.ts +56 -0
  71. package/dist/composition/parse.js +474 -0
  72. package/dist/constants.d.ts +29 -0
  73. package/dist/constants.js +99 -0
  74. package/dist/controls.d.ts +14 -0
  75. package/dist/controls.js +30 -0
  76. package/dist/convex-hull/ConvexHull.svelte +157 -0
  77. package/dist/convex-hull/ConvexHull.svelte.d.ts +13 -0
  78. package/dist/convex-hull/ConvexHull2D.svelte +825 -0
  79. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +11 -0
  80. package/dist/convex-hull/ConvexHull3D.svelte +1801 -0
  81. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +8 -0
  82. package/dist/convex-hull/ConvexHull4D.svelte +1398 -0
  83. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +8 -0
  84. package/dist/convex-hull/ConvexHullControls.svelte +535 -0
  85. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +48 -0
  86. package/dist/convex-hull/ConvexHullInfoPane.svelte +125 -0
  87. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +20 -0
  88. package/dist/convex-hull/ConvexHullStats.svelte +929 -0
  89. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +17 -0
  90. package/dist/convex-hull/ConvexHullTooltip.svelte +131 -0
  91. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +33 -0
  92. package/dist/convex-hull/GasPressureControls.svelte +247 -0
  93. package/dist/convex-hull/GasPressureControls.svelte.d.ts +11 -0
  94. package/dist/convex-hull/StructurePopup.svelte +151 -0
  95. package/dist/convex-hull/StructurePopup.svelte.d.ts +18 -0
  96. package/dist/convex-hull/TemperatureSlider.svelte.d.ts +8 -0
  97. package/dist/convex-hull/barycentric-coords.d.ts +18 -0
  98. package/dist/convex-hull/barycentric-coords.js +182 -0
  99. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  100. package/dist/convex-hull/demo-temperature.js +40 -0
  101. package/dist/convex-hull/gas-thermodynamics.d.ts +16 -0
  102. package/dist/convex-hull/gas-thermodynamics.js +314 -0
  103. package/dist/convex-hull/helpers.d.ts +114 -0
  104. package/dist/convex-hull/helpers.js +710 -0
  105. package/dist/convex-hull/index.d.ts +119 -0
  106. package/dist/convex-hull/index.js +58 -0
  107. package/dist/convex-hull/thermodynamics.d.ts +67 -0
  108. package/dist/convex-hull/thermodynamics.js +1752 -0
  109. package/dist/convex-hull/types.d.ts +162 -0
  110. package/dist/convex-hull/types.js +36 -0
  111. package/dist/coordination/CoordinationBarPlot.svelte +311 -0
  112. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +30 -0
  113. package/dist/coordination/calc-coordination.d.ts +15 -0
  114. package/dist/coordination/calc-coordination.js +63 -0
  115. package/dist/coordination/index.d.ts +8 -0
  116. package/dist/coordination/index.js +7 -0
  117. package/dist/effects.svelte.d.ts +12 -0
  118. package/dist/effects.svelte.js +37 -0
  119. package/dist/element/BohrAtom.svelte.d.ts +20 -0
  120. package/dist/element/ElementHeading.svelte +26 -0
  121. package/dist/element/ElementHeading.svelte.d.ts +8 -0
  122. package/dist/element/ElementPhoto.svelte +57 -0
  123. package/dist/element/ElementPhoto.svelte.d.ts +9 -0
  124. package/dist/element/ElementStats.svelte +80 -0
  125. package/dist/element/ElementStats.svelte.d.ts +8 -0
  126. package/dist/element/ElementTile.svelte +484 -0
  127. package/dist/element/ElementTile.svelte.d.ts +29 -0
  128. package/dist/element/Nucleus.svelte.d.ts +17 -0
  129. package/dist/element/data.d.ts +2 -0
  130. package/dist/element/data.js +2 -0
  131. package/dist/element/index.d.ts +8 -0
  132. package/dist/element/index.js +7 -0
  133. package/dist/element/types.d.ts +57 -0
  134. package/dist/element/types.js +1 -0
  135. package/dist/feedback/ClickFeedback.svelte +58 -0
  136. package/dist/feedback/ClickFeedback.svelte.d.ts +12 -0
  137. package/dist/feedback/DragOverlay.svelte +42 -0
  138. package/dist/feedback/DragOverlay.svelte.d.ts +7 -0
  139. package/dist/feedback/Spinner.svelte.d.ts +7 -0
  140. package/dist/feedback/StatusMessage.svelte.d.ts +9 -0
  141. package/dist/feedback/index.d.ts +4 -0
  142. package/dist/feedback/index.js +4 -0
  143. package/dist/fermi-surface/FermiSlice.svelte +189 -0
  144. package/dist/fermi-surface/FermiSlice.svelte.d.ts +24 -0
  145. package/dist/fermi-surface/FermiSurface.svelte +600 -0
  146. package/dist/fermi-surface/FermiSurface.svelte.d.ts +83 -0
  147. package/dist/fermi-surface/FermiSurfaceControls.svelte +448 -0
  148. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +35 -0
  149. package/dist/fermi-surface/FermiSurfaceScene.svelte +794 -0
  150. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +50 -0
  151. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +111 -0
  152. package/dist/fermi-surface/FermiSurfaceTooltip.svelte.d.ts +8 -0
  153. package/dist/fermi-surface/compute.d.ts +5 -0
  154. package/dist/fermi-surface/compute.js +538 -0
  155. package/dist/fermi-surface/constants.d.ts +9 -0
  156. package/dist/fermi-surface/constants.js +27 -0
  157. package/dist/fermi-surface/export.d.ts +5 -0
  158. package/dist/fermi-surface/export.js +50 -0
  159. package/dist/fermi-surface/index.d.ts +12 -0
  160. package/dist/fermi-surface/index.js +13 -0
  161. package/dist/fermi-surface/marching-cubes.d.ts +2 -0
  162. package/dist/fermi-surface/marching-cubes.js +2 -0
  163. package/dist/fermi-surface/parse.d.ts +2 -0
  164. package/dist/fermi-surface/parse.js +491 -0
  165. package/dist/fermi-surface/symmetry.d.ts +3 -0
  166. package/dist/fermi-surface/symmetry.js +46 -0
  167. package/dist/fermi-surface/types.d.ts +110 -0
  168. package/dist/fermi-surface/types.js +4 -0
  169. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1545 -0
  170. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  171. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
  172. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +30 -0
  173. package/dist/heatmap-matrix/index.d.ts +53 -0
  174. package/dist/heatmap-matrix/index.js +100 -0
  175. package/dist/heatmap-matrix/shared.d.ts +2 -0
  176. package/dist/heatmap-matrix/shared.js +4 -0
  177. package/dist/icons.d.ts +569 -0
  178. package/dist/icons.js +648 -0
  179. package/dist/index.d.ts +39 -0
  180. package/dist/index.js +39 -0
  181. package/dist/io/decompress.d.ts +11 -0
  182. package/dist/io/decompress.js +74 -0
  183. package/dist/io/export.d.ts +16 -0
  184. package/dist/io/export.js +316 -0
  185. package/dist/io/fetch.d.ts +5 -0
  186. package/dist/io/fetch.js +39 -0
  187. package/dist/io/file-drop.d.ts +7 -0
  188. package/dist/io/file-drop.js +43 -0
  189. package/dist/io/index.d.ts +7 -0
  190. package/dist/io/index.js +6 -0
  191. package/dist/io/is-binary.d.ts +1 -0
  192. package/dist/io/is-binary.js +20 -0
  193. package/dist/io/types.d.ts +8 -0
  194. package/dist/io/types.js +1 -0
  195. package/dist/io/url-drop.d.ts +2 -0
  196. package/dist/io/url-drop.js +123 -0
  197. package/dist/isosurface/Isosurface.svelte +285 -0
  198. package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
  199. package/dist/isosurface/IsosurfaceControls.svelte +277 -0
  200. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
  201. package/dist/isosurface/index.d.ts +5 -0
  202. package/dist/isosurface/index.js +6 -0
  203. package/dist/isosurface/parse.d.ts +6 -0
  204. package/dist/isosurface/parse.js +553 -0
  205. package/dist/isosurface/slice.d.ts +11 -0
  206. package/dist/isosurface/slice.js +140 -0
  207. package/dist/isosurface/types.d.ts +56 -0
  208. package/dist/isosurface/types.js +227 -0
  209. package/dist/labels.d.ts +53 -0
  210. package/dist/labels.js +277 -0
  211. package/dist/layout/FullscreenToggle.svelte +50 -0
  212. package/dist/layout/FullscreenToggle.svelte.d.ts +7 -0
  213. package/dist/layout/InfoCard.svelte +120 -0
  214. package/dist/layout/InfoCard.svelte.d.ts +21 -0
  215. package/dist/layout/InfoTag.svelte +185 -0
  216. package/dist/layout/InfoTag.svelte.d.ts +19 -0
  217. package/dist/layout/PropertyFilter.svelte +246 -0
  218. package/dist/layout/PropertyFilter.svelte.d.ts +24 -0
  219. package/dist/layout/SettingsSection.svelte +148 -0
  220. package/dist/layout/SettingsSection.svelte.d.ts +17 -0
  221. package/dist/layout/SubpageGrid.svelte +82 -0
  222. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  223. package/dist/layout/fullscreen.d.ts +9 -0
  224. package/dist/layout/fullscreen.js +53 -0
  225. package/dist/layout/index.d.ts +10 -0
  226. package/dist/layout/index.js +8 -0
  227. package/dist/layout/json-tree/JsonNode.svelte +548 -0
  228. package/dist/layout/json-tree/JsonNode.svelte.d.ts +11 -0
  229. package/dist/layout/json-tree/JsonTree.svelte +1230 -0
  230. package/dist/layout/json-tree/JsonTree.svelte.d.ts +6 -0
  231. package/dist/layout/json-tree/JsonValue.svelte.d.ts +9 -0
  232. package/dist/layout/json-tree/index.d.ts +3 -0
  233. package/dist/layout/json-tree/index.js +3 -0
  234. package/dist/layout/json-tree/types.d.ts +74 -0
  235. package/dist/layout/json-tree/types.js +2 -0
  236. package/dist/layout/json-tree/utils.d.ts +29 -0
  237. package/dist/layout/json-tree/utils.js +641 -0
  238. package/dist/marching-cubes.d.ts +14 -0
  239. package/dist/marching-cubes.js +540 -0
  240. package/dist/math.d.ts +101 -0
  241. package/dist/math.js +905 -0
  242. package/dist/overlays/ContextMenu.svelte +162 -0
  243. package/dist/overlays/ContextMenu.svelte.d.ts +25 -0
  244. package/dist/overlays/CopyButton.svelte +45 -0
  245. package/dist/overlays/CopyButton.svelte.d.ts +8 -0
  246. package/dist/overlays/DragControlTab.svelte +98 -0
  247. package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
  248. package/dist/overlays/DraggablePane.svelte +487 -0
  249. package/dist/overlays/DraggablePane.svelte.d.ts +36 -0
  250. package/dist/overlays/InfoPaneCards.svelte +149 -0
  251. package/dist/overlays/InfoPaneCards.svelte.d.ts +22 -0
  252. package/dist/overlays/index.d.ts +3 -0
  253. package/dist/overlays/index.js +3 -0
  254. package/dist/periodic-table/PeriodicTable.svelte +469 -0
  255. package/dist/periodic-table/PeriodicTable.svelte.d.ts +55 -0
  256. package/dist/periodic-table/PeriodicTableControls.svelte +557 -0
  257. package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +24 -0
  258. package/dist/periodic-table/PropertySelect.svelte +37 -0
  259. package/dist/periodic-table/PropertySelect.svelte.d.ts +13 -0
  260. package/dist/periodic-table/TableInset.svelte.d.ts +9 -0
  261. package/dist/periodic-table/index.d.ts +10 -0
  262. package/dist/periodic-table/index.js +4 -0
  263. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +1086 -0
  264. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +44 -0
  265. package/dist/phase-diagram/PhaseDiagramControls.svelte +444 -0
  266. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +30 -0
  267. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
  268. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  269. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +184 -0
  270. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +19 -0
  271. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +391 -0
  272. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +16 -0
  273. package/dist/phase-diagram/TdbInfoPanel.svelte +203 -0
  274. package/dist/phase-diagram/TdbInfoPanel.svelte.d.ts +12 -0
  275. package/dist/phase-diagram/build-diagram.d.ts +11 -0
  276. package/dist/phase-diagram/build-diagram.js +160 -0
  277. package/dist/phase-diagram/colors.d.ts +35 -0
  278. package/dist/phase-diagram/colors.js +51 -0
  279. package/dist/phase-diagram/diagram-input.d.ts +29 -0
  280. package/dist/phase-diagram/diagram-input.js +3 -0
  281. package/dist/phase-diagram/index.d.ts +13 -0
  282. package/dist/phase-diagram/index.js +11 -0
  283. package/dist/phase-diagram/parse.d.ts +55 -0
  284. package/dist/phase-diagram/parse.js +272 -0
  285. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  286. package/dist/phase-diagram/svg-to-diagram.js +867 -0
  287. package/dist/phase-diagram/types.d.ts +93 -0
  288. package/dist/phase-diagram/types.js +1 -0
  289. package/dist/phase-diagram/utils.d.ts +118 -0
  290. package/dist/phase-diagram/utils.js +604 -0
  291. package/dist/plot/AxisLabel.svelte +51 -0
  292. package/dist/plot/AxisLabel.svelte.d.ts +16 -0
  293. package/dist/plot/BarPlot.svelte +2113 -0
  294. package/dist/plot/BarPlot.svelte.d.ts +84 -0
  295. package/dist/plot/BarPlotControls.svelte +66 -0
  296. package/dist/plot/BarPlotControls.svelte.d.ts +18 -0
  297. package/dist/plot/BinnedScatterPlot.svelte +1114 -0
  298. package/dist/plot/BinnedScatterPlot.svelte.d.ts +66 -0
  299. package/dist/plot/ColorBar.svelte +721 -0
  300. package/dist/plot/ColorBar.svelte.d.ts +31 -0
  301. package/dist/plot/ColorScaleSelect.svelte +54 -0
  302. package/dist/plot/ColorScaleSelect.svelte.d.ts +15 -0
  303. package/dist/plot/ElementScatter.svelte +63 -0
  304. package/dist/plot/ElementScatter.svelte.d.ts +14 -0
  305. package/dist/plot/FillArea.svelte.d.ts +21 -0
  306. package/dist/plot/Histogram.svelte +1558 -0
  307. package/dist/plot/Histogram.svelte.d.ts +50 -0
  308. package/dist/plot/HistogramControls.svelte +212 -0
  309. package/dist/plot/HistogramControls.svelte.d.ts +22 -0
  310. package/dist/plot/InteractiveAxisLabel.svelte +96 -0
  311. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +14 -0
  312. package/dist/plot/Line.svelte +84 -0
  313. package/dist/plot/Line.svelte.d.ts +15 -0
  314. package/dist/plot/PlotAxis.svelte +169 -0
  315. package/dist/plot/PlotAxis.svelte.d.ts +24 -0
  316. package/dist/plot/PlotControls.svelte +537 -0
  317. package/dist/plot/PlotControls.svelte.d.ts +4 -0
  318. package/dist/plot/PlotLegend.svelte +569 -0
  319. package/dist/plot/PlotLegend.svelte.d.ts +29 -0
  320. package/dist/plot/PlotTooltip.svelte +67 -0
  321. package/dist/plot/PlotTooltip.svelte.d.ts +17 -0
  322. package/dist/plot/PortalSelect.svelte +253 -0
  323. package/dist/plot/PortalSelect.svelte.d.ts +16 -0
  324. package/dist/plot/ReferenceLine.svelte.d.ts +20 -0
  325. package/dist/plot/ReferenceLine3D.svelte +156 -0
  326. package/dist/plot/ReferenceLine3D.svelte.d.ts +14 -0
  327. package/dist/plot/ReferencePlane.svelte +175 -0
  328. package/dist/plot/ReferencePlane.svelte.d.ts +14 -0
  329. package/dist/plot/ScatterPlot.svelte +2778 -0
  330. package/dist/plot/ScatterPlot.svelte.d.ts +96 -0
  331. package/dist/plot/ScatterPlot3D.svelte +529 -0
  332. package/dist/plot/ScatterPlot3D.svelte.d.ts +95 -0
  333. package/dist/plot/ScatterPlot3DControls.svelte +437 -0
  334. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +20 -0
  335. package/dist/plot/ScatterPlot3DScene.svelte +912 -0
  336. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +74 -0
  337. package/dist/plot/ScatterPlotControls.svelte +306 -0
  338. package/dist/plot/ScatterPlotControls.svelte.d.ts +17 -0
  339. package/dist/plot/ScatterPoint.svelte +182 -0
  340. package/dist/plot/ScatterPoint.svelte.d.ts +22 -0
  341. package/dist/plot/SpacegroupBarPlot.svelte +293 -0
  342. package/dist/plot/SpacegroupBarPlot.svelte.d.ts +9 -0
  343. package/dist/plot/Surface3D.svelte +197 -0
  344. package/dist/plot/Surface3D.svelte.d.ts +13 -0
  345. package/dist/plot/ZeroLines.svelte +97 -0
  346. package/dist/plot/ZeroLines.svelte.d.ts +33 -0
  347. package/dist/plot/ZoomRect.svelte +23 -0
  348. package/dist/plot/ZoomRect.svelte.d.ts +8 -0
  349. package/dist/plot/adaptive-density.d.ts +69 -0
  350. package/dist/plot/adaptive-density.js +191 -0
  351. package/dist/plot/auto-place.d.ts +43 -0
  352. package/dist/plot/auto-place.js +122 -0
  353. package/dist/plot/axis-utils.d.ts +19 -0
  354. package/dist/plot/axis-utils.js +78 -0
  355. package/dist/plot/binned-scatter-types.d.ts +59 -0
  356. package/dist/plot/binned-scatter-types.js +1 -0
  357. package/dist/plot/data-cleaning.d.ts +37 -0
  358. package/dist/plot/data-cleaning.js +855 -0
  359. package/dist/plot/data-transform.d.ts +16 -0
  360. package/dist/plot/data-transform.js +45 -0
  361. package/dist/plot/defaults.d.ts +19 -0
  362. package/dist/plot/defaults.js +9 -0
  363. package/dist/plot/fill-utils.d.ts +46 -0
  364. package/dist/plot/fill-utils.js +322 -0
  365. package/dist/plot/hover-lock.svelte.d.ts +14 -0
  366. package/dist/plot/hover-lock.svelte.js +46 -0
  367. package/dist/plot/index.d.ts +41 -0
  368. package/dist/plot/index.js +39 -0
  369. package/dist/plot/interactions.d.ts +12 -0
  370. package/dist/plot/interactions.js +101 -0
  371. package/dist/plot/layout.d.ts +78 -0
  372. package/dist/plot/layout.js +273 -0
  373. package/dist/plot/reference-line.d.ts +60 -0
  374. package/dist/plot/reference-line.js +314 -0
  375. package/dist/plot/scales.d.ts +48 -0
  376. package/dist/plot/scales.js +481 -0
  377. package/dist/plot/svg.d.ts +1 -0
  378. package/dist/plot/svg.js +11 -0
  379. package/dist/plot/types.d.ts +831 -0
  380. package/dist/plot/types.js +99 -0
  381. package/dist/plot/utils/label-placement.d.ts +68 -0
  382. package/dist/plot/utils/label-placement.js +326 -0
  383. package/dist/plot/utils/series-visibility.d.ts +15 -0
  384. package/dist/plot/utils/series-visibility.js +85 -0
  385. package/dist/plot/utils.d.ts +1 -0
  386. package/dist/plot/utils.js +14 -0
  387. package/dist/rdf/RdfPlot.svelte +247 -0
  388. package/dist/rdf/RdfPlot.svelte.d.ts +27 -0
  389. package/dist/rdf/calc-rdf.d.ts +4 -0
  390. package/dist/rdf/calc-rdf.js +111 -0
  391. package/dist/rdf/index.d.ts +23 -0
  392. package/dist/rdf/index.js +2 -0
  393. package/dist/sanitize.d.ts +6 -0
  394. package/dist/sanitize.js +116 -0
  395. package/dist/settings.d.ts +255 -0
  396. package/dist/settings.js +1132 -0
  397. package/dist/spectral/Bands.svelte +1040 -0
  398. package/dist/spectral/Bands.svelte.d.ts +40 -0
  399. package/dist/spectral/BandsAndDos.svelte +134 -0
  400. package/dist/spectral/BandsAndDos.svelte.d.ts +18 -0
  401. package/dist/spectral/BrillouinBandsDos.svelte +252 -0
  402. package/dist/spectral/BrillouinBandsDos.svelte.d.ts +20 -0
  403. package/dist/spectral/Dos.svelte +697 -0
  404. package/dist/spectral/Dos.svelte.d.ts +29 -0
  405. package/dist/spectral/helpers.d.ts +119 -0
  406. package/dist/spectral/helpers.js +1032 -0
  407. package/dist/spectral/index.d.ts +6 -0
  408. package/dist/spectral/index.js +6 -0
  409. package/dist/spectral/types.d.ts +84 -0
  410. package/dist/spectral/types.js +2 -0
  411. package/dist/state.svelte.d.ts +25 -0
  412. package/dist/state.svelte.js +45 -0
  413. package/dist/structure/Arrow.svelte +72 -0
  414. package/dist/structure/Arrow.svelte.d.ts +15 -0
  415. package/dist/structure/AtomLegend.svelte +815 -0
  416. package/dist/structure/AtomLegend.svelte.d.ts +35 -0
  417. package/dist/structure/Bond.svelte +140 -0
  418. package/dist/structure/Bond.svelte.d.ts +9 -0
  419. package/dist/structure/CanvasTooltip.svelte +33 -0
  420. package/dist/structure/CanvasTooltip.svelte.d.ts +12 -0
  421. package/dist/structure/CellSelect.svelte +349 -0
  422. package/dist/structure/CellSelect.svelte.d.ts +13 -0
  423. package/dist/structure/Cylinder.svelte +45 -0
  424. package/dist/structure/Cylinder.svelte.d.ts +10 -0
  425. package/dist/structure/Lattice.svelte +196 -0
  426. package/dist/structure/Lattice.svelte.d.ts +17 -0
  427. package/dist/structure/Structure.svelte +2248 -0
  428. package/dist/structure/Structure.svelte.d.ts +89 -0
  429. package/dist/structure/StructureControls.svelte +1273 -0
  430. package/dist/structure/StructureControls.svelte.d.ts +31 -0
  431. package/dist/structure/StructureExportPane.svelte +252 -0
  432. package/dist/structure/StructureExportPane.svelte.d.ts +17 -0
  433. package/dist/structure/StructureInfoPane.svelte +737 -0
  434. package/dist/structure/StructureInfoPane.svelte.d.ts +19 -0
  435. package/dist/structure/StructureScene.svelte +2255 -0
  436. package/dist/structure/StructureScene.svelte.d.ts +111 -0
  437. package/dist/structure/atom-properties.d.ts +37 -0
  438. package/dist/structure/atom-properties.js +200 -0
  439. package/dist/structure/bond-order-perception.d.ts +13 -0
  440. package/dist/structure/bond-order-perception.js +384 -0
  441. package/dist/structure/bonding.d.ts +68 -0
  442. package/dist/structure/bonding.js +696 -0
  443. package/dist/structure/export.d.ts +20 -0
  444. package/dist/structure/export.js +727 -0
  445. package/dist/structure/index.d.ts +126 -0
  446. package/dist/structure/index.js +169 -0
  447. package/dist/structure/label-placement.d.ts +14 -0
  448. package/dist/structure/label-placement.js +72 -0
  449. package/dist/structure/measure.d.ts +6 -0
  450. package/dist/structure/measure.js +29 -0
  451. package/dist/structure/parse.d.ts +66 -0
  452. package/dist/structure/parse.js +1392 -0
  453. package/dist/structure/partial-occupancy.d.ts +25 -0
  454. package/dist/structure/partial-occupancy.js +99 -0
  455. package/dist/structure/pbc.d.ts +9 -0
  456. package/dist/structure/pbc.js +123 -0
  457. package/dist/structure/supercell.d.ts +8 -0
  458. package/dist/structure/supercell.js +170 -0
  459. package/dist/structure/validation.d.ts +2 -0
  460. package/dist/structure/validation.js +10 -0
  461. package/dist/symmetry/SymmetryStats.svelte +226 -0
  462. package/dist/symmetry/SymmetryStats.svelte.d.ts +21 -0
  463. package/dist/symmetry/WyckoffTable.svelte +120 -0
  464. package/dist/symmetry/WyckoffTable.svelte.d.ts +11 -0
  465. package/dist/symmetry/cell-transform.d.ts +12 -0
  466. package/dist/symmetry/cell-transform.js +91 -0
  467. package/dist/symmetry/index.d.ts +43 -0
  468. package/dist/symmetry/index.js +228 -0
  469. package/dist/symmetry/spacegroups.d.ts +9 -0
  470. package/dist/symmetry/spacegroups.js +394 -0
  471. package/dist/table/HeatmapTable.svelte +1833 -0
  472. package/dist/table/HeatmapTable.svelte.d.ts +49 -0
  473. package/dist/table/ToggleMenu.svelte +385 -0
  474. package/dist/table/ToggleMenu.svelte.d.ts +11 -0
  475. package/dist/table/index.d.ts +74 -0
  476. package/dist/table/index.js +38 -0
  477. package/dist/theme/ThemeControl.svelte +53 -0
  478. package/dist/theme/ThemeControl.svelte.d.ts +9 -0
  479. package/dist/theme/index.d.ts +29 -0
  480. package/dist/theme/index.js +79 -0
  481. package/dist/time.d.ts +4 -0
  482. package/dist/time.js +70 -0
  483. package/dist/tooltip/TooltipContent.svelte +58 -0
  484. package/dist/tooltip/TooltipContent.svelte.d.ts +31 -0
  485. package/dist/tooltip/index.d.ts +2 -0
  486. package/dist/tooltip/index.js +1 -0
  487. package/dist/tooltip/types.d.ts +8 -0
  488. package/dist/tooltip/types.js +1 -0
  489. package/dist/trajectory/Trajectory.svelte +1545 -0
  490. package/dist/trajectory/Trajectory.svelte.d.ts +77 -0
  491. package/dist/trajectory/TrajectoryError.svelte +128 -0
  492. package/dist/trajectory/TrajectoryError.svelte.d.ts +13 -0
  493. package/dist/trajectory/TrajectoryExportPane.svelte +357 -0
  494. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +17 -0
  495. package/dist/trajectory/TrajectoryInfoPane.svelte +313 -0
  496. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +17 -0
  497. package/dist/trajectory/constants.d.ts +6 -0
  498. package/dist/trajectory/constants.js +7 -0
  499. package/dist/trajectory/extract.d.ts +5 -0
  500. package/dist/trajectory/extract.js +162 -0
  501. package/dist/trajectory/format-detect.d.ts +9 -0
  502. package/dist/trajectory/format-detect.js +76 -0
  503. package/dist/trajectory/frame-reader.d.ts +17 -0
  504. package/dist/trajectory/frame-reader.js +332 -0
  505. package/dist/trajectory/helpers.d.ts +15 -0
  506. package/dist/trajectory/helpers.js +164 -0
  507. package/dist/trajectory/index.d.ts +63 -0
  508. package/dist/trajectory/index.js +126 -0
  509. package/dist/trajectory/parse/ase.d.ts +2 -0
  510. package/dist/trajectory/parse/ase.js +73 -0
  511. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  512. package/dist/trajectory/parse/hdf5.js +127 -0
  513. package/dist/trajectory/parse/index.d.ts +12 -0
  514. package/dist/trajectory/parse/index.js +298 -0
  515. package/dist/trajectory/parse/lammps.d.ts +5 -0
  516. package/dist/trajectory/parse/lammps.js +179 -0
  517. package/dist/trajectory/parse/vasp.d.ts +2 -0
  518. package/dist/trajectory/parse/vasp.js +68 -0
  519. package/dist/trajectory/parse/xyz.d.ts +2 -0
  520. package/dist/trajectory/parse/xyz.js +110 -0
  521. package/dist/trajectory/plotting.d.ts +28 -0
  522. package/dist/trajectory/plotting.js +423 -0
  523. package/dist/trajectory/types.d.ts +11 -0
  524. package/dist/trajectory/types.js +1 -0
  525. package/dist/utils.d.ts +6 -0
  526. package/dist/utils.js +45 -0
  527. package/dist/xrd/XrdPlot.svelte +615 -0
  528. package/dist/xrd/XrdPlot.svelte.d.ts +28 -0
  529. package/dist/xrd/broadening.d.ts +20 -0
  530. package/dist/xrd/broadening.js +97 -0
  531. package/dist/xrd/calc-xrd.d.ts +37 -0
  532. package/dist/xrd/calc-xrd.js +336 -0
  533. package/dist/xrd/index.d.ts +37 -0
  534. package/dist/xrd/index.js +4 -0
  535. package/dist/xrd/parse.d.ts +13 -0
  536. package/dist/xrd/parse.js +749 -0
  537. package/license +1 -1
  538. package/package.json +232 -1457
  539. package/readme.md +98 -171
  540. package/.vscode/launch.json +0 -13
  541. package/.vscodeignore +0 -7
  542. package/dist/assets/STLExporter-BpTH3YHE.js +0 -8
  543. package/dist/assets/browser-DdDecX_W.js +0 -1
  544. package/dist/assets/export-qgn-H9y6.js +0 -2
  545. package/dist/assets/main-DiKYzti2.css +0 -1
  546. package/dist/assets/moyo_wasm_bg-0ocwg7xY.wasm +0 -0
  547. package/dist/extension.js +0 -31293
  548. package/dist/src/lib/FilePicker.svelte +0 -360
  549. package/dist/src/lib/MillerIndexInput.svelte +0 -66
  550. package/dist/src/lib/api/mp.ts +0 -26
  551. package/dist/src/lib/api/optimade.ts +0 -204
  552. package/dist/src/lib/brillouin/BrillouinZone.svelte +0 -549
  553. package/dist/src/lib/brillouin/BrillouinZoneControls.svelte +0 -144
  554. package/dist/src/lib/brillouin/BrillouinZoneExportPane.svelte +0 -146
  555. package/dist/src/lib/brillouin/BrillouinZoneInfoPane.svelte +0 -146
  556. package/dist/src/lib/brillouin/BrillouinZoneScene.svelte +0 -476
  557. package/dist/src/lib/brillouin/BrillouinZoneTooltip.svelte +0 -92
  558. package/dist/src/lib/brillouin/compute.ts +0 -529
  559. package/dist/src/lib/brillouin/index.ts +0 -8
  560. package/dist/src/lib/brillouin/types.ts +0 -51
  561. package/dist/src/lib/chempot-diagram/ChemPotDiagram.svelte +0 -327
  562. package/dist/src/lib/chempot-diagram/ChemPotDiagram2D.svelte +0 -846
  563. package/dist/src/lib/chempot-diagram/ChemPotDiagram3D.svelte +0 -3193
  564. package/dist/src/lib/chempot-diagram/async-compute.svelte.ts +0 -94
  565. package/dist/src/lib/chempot-diagram/chempot-worker.ts +0 -11
  566. package/dist/src/lib/chempot-diagram/color.ts +0 -42
  567. package/dist/src/lib/chempot-diagram/compute.ts +0 -1014
  568. package/dist/src/lib/chempot-diagram/index.ts +0 -6
  569. package/dist/src/lib/chempot-diagram/pointer.ts +0 -56
  570. package/dist/src/lib/chempot-diagram/temperature.ts +0 -77
  571. package/dist/src/lib/chempot-diagram/types.ts +0 -130
  572. package/dist/src/lib/colors/index.ts +0 -249
  573. package/dist/src/lib/composition/BarChart.svelte +0 -297
  574. package/dist/src/lib/composition/BubbleChart.svelte +0 -218
  575. package/dist/src/lib/composition/Composition.svelte +0 -165
  576. package/dist/src/lib/composition/Formula.svelte +0 -268
  577. package/dist/src/lib/composition/FormulaFilter.svelte +0 -1257
  578. package/dist/src/lib/composition/PieChart.svelte +0 -323
  579. package/dist/src/lib/composition/format.ts +0 -155
  580. package/dist/src/lib/composition/index.ts +0 -37
  581. package/dist/src/lib/composition/parse.ts +0 -605
  582. package/dist/src/lib/constants.ts +0 -134
  583. package/dist/src/lib/controls.ts +0 -42
  584. package/dist/src/lib/convex-hull/ConvexHull.svelte +0 -157
  585. package/dist/src/lib/convex-hull/ConvexHull2D.svelte +0 -825
  586. package/dist/src/lib/convex-hull/ConvexHull3D.svelte +0 -1801
  587. package/dist/src/lib/convex-hull/ConvexHull4D.svelte +0 -1398
  588. package/dist/src/lib/convex-hull/ConvexHullControls.svelte +0 -535
  589. package/dist/src/lib/convex-hull/ConvexHullInfoPane.svelte +0 -125
  590. package/dist/src/lib/convex-hull/ConvexHullStats.svelte +0 -929
  591. package/dist/src/lib/convex-hull/ConvexHullTooltip.svelte +0 -131
  592. package/dist/src/lib/convex-hull/GasPressureControls.svelte +0 -247
  593. package/dist/src/lib/convex-hull/StructurePopup.svelte +0 -151
  594. package/dist/src/lib/convex-hull/barycentric-coords.ts +0 -246
  595. package/dist/src/lib/convex-hull/demo-temperature.ts +0 -63
  596. package/dist/src/lib/convex-hull/gas-thermodynamics.ts +0 -405
  597. package/dist/src/lib/convex-hull/helpers.ts +0 -932
  598. package/dist/src/lib/convex-hull/index.ts +0 -202
  599. package/dist/src/lib/convex-hull/thermodynamics.ts +0 -2192
  600. package/dist/src/lib/convex-hull/types.ts +0 -267
  601. package/dist/src/lib/coordination/CoordinationBarPlot.svelte +0 -311
  602. package/dist/src/lib/coordination/calc-coordination.ts +0 -93
  603. package/dist/src/lib/coordination/index.ts +0 -9
  604. package/dist/src/lib/effects.svelte.ts +0 -48
  605. package/dist/src/lib/element/ElementHeading.svelte +0 -26
  606. package/dist/src/lib/element/ElementPhoto.svelte +0 -57
  607. package/dist/src/lib/element/ElementStats.svelte +0 -80
  608. package/dist/src/lib/element/ElementTile.svelte +0 -484
  609. package/dist/src/lib/element/data.ts +0 -14
  610. package/dist/src/lib/element/index.ts +0 -8
  611. package/dist/src/lib/element/types.ts +0 -62
  612. package/dist/src/lib/feedback/ClickFeedback.svelte +0 -58
  613. package/dist/src/lib/feedback/DragOverlay.svelte +0 -42
  614. package/dist/src/lib/feedback/index.ts +0 -4
  615. package/dist/src/lib/fermi-surface/FermiSlice.svelte +0 -189
  616. package/dist/src/lib/fermi-surface/FermiSurface.svelte +0 -600
  617. package/dist/src/lib/fermi-surface/FermiSurfaceControls.svelte +0 -448
  618. package/dist/src/lib/fermi-surface/FermiSurfaceScene.svelte +0 -794
  619. package/dist/src/lib/fermi-surface/FermiSurfaceTooltip.svelte +0 -111
  620. package/dist/src/lib/fermi-surface/compute.ts +0 -728
  621. package/dist/src/lib/fermi-surface/constants.ts +0 -32
  622. package/dist/src/lib/fermi-surface/export.ts +0 -64
  623. package/dist/src/lib/fermi-surface/index.ts +0 -14
  624. package/dist/src/lib/fermi-surface/marching-cubes.ts +0 -3
  625. package/dist/src/lib/fermi-surface/parse.ts +0 -574
  626. package/dist/src/lib/fermi-surface/symmetry.ts +0 -56
  627. package/dist/src/lib/fermi-surface/types.ts +0 -159
  628. package/dist/src/lib/heatmap-matrix/HeatmapMatrix.svelte +0 -1545
  629. package/dist/src/lib/heatmap-matrix/HeatmapMatrixControls.svelte +0 -225
  630. package/dist/src/lib/heatmap-matrix/index.ts +0 -167
  631. package/dist/src/lib/heatmap-matrix/shared.ts +0 -7
  632. package/dist/src/lib/icons.ts +0 -650
  633. package/dist/src/lib/index.ts +0 -61
  634. package/dist/src/lib/io/decompress.ts +0 -92
  635. package/dist/src/lib/io/export.ts +0 -385
  636. package/dist/src/lib/io/fetch.ts +0 -46
  637. package/dist/src/lib/io/file-drop.ts +0 -51
  638. package/dist/src/lib/io/index.ts +0 -7
  639. package/dist/src/lib/io/is-binary.ts +0 -24
  640. package/dist/src/lib/io/types.ts +0 -8
  641. package/dist/src/lib/io/url-drop.ts +0 -141
  642. package/dist/src/lib/isosurface/Isosurface.svelte +0 -285
  643. package/dist/src/lib/isosurface/IsosurfaceControls.svelte +0 -277
  644. package/dist/src/lib/isosurface/index.ts +0 -7
  645. package/dist/src/lib/isosurface/parse.ts +0 -656
  646. package/dist/src/lib/isosurface/slice.ts +0 -175
  647. package/dist/src/lib/isosurface/types.ts +0 -309
  648. package/dist/src/lib/labels.ts +0 -320
  649. package/dist/src/lib/layout/FullscreenToggle.svelte +0 -50
  650. package/dist/src/lib/layout/InfoCard.svelte +0 -120
  651. package/dist/src/lib/layout/InfoTag.svelte +0 -185
  652. package/dist/src/lib/layout/PropertyFilter.svelte +0 -246
  653. package/dist/src/lib/layout/SettingsSection.svelte +0 -148
  654. package/dist/src/lib/layout/SubpageGrid.svelte +0 -82
  655. package/dist/src/lib/layout/fullscreen.ts +0 -65
  656. package/dist/src/lib/layout/index.ts +0 -11
  657. package/dist/src/lib/layout/json-tree/JsonNode.svelte +0 -548
  658. package/dist/src/lib/layout/json-tree/JsonTree.svelte +0 -1230
  659. package/dist/src/lib/layout/json-tree/index.ts +0 -3
  660. package/dist/src/lib/layout/json-tree/types.ts +0 -126
  661. package/dist/src/lib/layout/json-tree/utils.ts +0 -682
  662. package/dist/src/lib/marching-cubes.ts +0 -614
  663. package/dist/src/lib/math.ts +0 -1081
  664. package/dist/src/lib/overlays/ContextMenu.svelte +0 -162
  665. package/dist/src/lib/overlays/CopyButton.svelte +0 -45
  666. package/dist/src/lib/overlays/DragControlTab.svelte +0 -98
  667. package/dist/src/lib/overlays/DraggablePane.svelte +0 -487
  668. package/dist/src/lib/overlays/InfoPaneCards.svelte +0 -149
  669. package/dist/src/lib/overlays/index.ts +0 -3
  670. package/dist/src/lib/periodic-table/PeriodicTable.svelte +0 -469
  671. package/dist/src/lib/periodic-table/PeriodicTableControls.svelte +0 -557
  672. package/dist/src/lib/periodic-table/PropertySelect.svelte +0 -37
  673. package/dist/src/lib/periodic-table/index.ts +0 -12
  674. package/dist/src/lib/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +0 -1086
  675. package/dist/src/lib/phase-diagram/PhaseDiagramControls.svelte +0 -444
  676. package/dist/src/lib/phase-diagram/PhaseDiagramEditorPane.svelte +0 -126
  677. package/dist/src/lib/phase-diagram/PhaseDiagramExportPane.svelte +0 -184
  678. package/dist/src/lib/phase-diagram/PhaseDiagramTooltip.svelte +0 -391
  679. package/dist/src/lib/phase-diagram/TdbInfoPanel.svelte +0 -203
  680. package/dist/src/lib/phase-diagram/build-diagram.ts +0 -186
  681. package/dist/src/lib/phase-diagram/colors.ts +0 -58
  682. package/dist/src/lib/phase-diagram/diagram-input.ts +0 -40
  683. package/dist/src/lib/phase-diagram/index.ts +0 -13
  684. package/dist/src/lib/phase-diagram/parse.ts +0 -348
  685. package/dist/src/lib/phase-diagram/svg-to-diagram.ts +0 -1023
  686. package/dist/src/lib/phase-diagram/types.ts +0 -144
  687. package/dist/src/lib/phase-diagram/utils.ts +0 -775
  688. package/dist/src/lib/plot/AxisLabel.svelte +0 -51
  689. package/dist/src/lib/plot/BarPlot.svelte +0 -2113
  690. package/dist/src/lib/plot/BarPlotControls.svelte +0 -66
  691. package/dist/src/lib/plot/BinnedScatterPlot.svelte +0 -1114
  692. package/dist/src/lib/plot/ColorBar.svelte +0 -721
  693. package/dist/src/lib/plot/ColorScaleSelect.svelte +0 -54
  694. package/dist/src/lib/plot/ElementScatter.svelte +0 -63
  695. package/dist/src/lib/plot/Histogram.svelte +0 -1558
  696. package/dist/src/lib/plot/HistogramControls.svelte +0 -212
  697. package/dist/src/lib/plot/InteractiveAxisLabel.svelte +0 -96
  698. package/dist/src/lib/plot/Line.svelte +0 -84
  699. package/dist/src/lib/plot/PlotAxis.svelte +0 -169
  700. package/dist/src/lib/plot/PlotControls.svelte +0 -537
  701. package/dist/src/lib/plot/PlotLegend.svelte +0 -569
  702. package/dist/src/lib/plot/PlotTooltip.svelte +0 -67
  703. package/dist/src/lib/plot/PortalSelect.svelte +0 -253
  704. package/dist/src/lib/plot/ReferenceLine3D.svelte +0 -156
  705. package/dist/src/lib/plot/ReferencePlane.svelte +0 -175
  706. package/dist/src/lib/plot/ScatterPlot.svelte +0 -2778
  707. package/dist/src/lib/plot/ScatterPlot3D.svelte +0 -529
  708. package/dist/src/lib/plot/ScatterPlot3DControls.svelte +0 -437
  709. package/dist/src/lib/plot/ScatterPlot3DScene.svelte +0 -912
  710. package/dist/src/lib/plot/ScatterPlotControls.svelte +0 -306
  711. package/dist/src/lib/plot/ScatterPoint.svelte +0 -182
  712. package/dist/src/lib/plot/SpacegroupBarPlot.svelte +0 -293
  713. package/dist/src/lib/plot/Surface3D.svelte +0 -197
  714. package/dist/src/lib/plot/ZeroLines.svelte +0 -97
  715. package/dist/src/lib/plot/ZoomRect.svelte +0 -23
  716. package/dist/src/lib/plot/adaptive-density.ts +0 -316
  717. package/dist/src/lib/plot/auto-place.ts +0 -184
  718. package/dist/src/lib/plot/axis-utils.ts +0 -122
  719. package/dist/src/lib/plot/binned-scatter-types.ts +0 -83
  720. package/dist/src/lib/plot/data-cleaning.ts +0 -1069
  721. package/dist/src/lib/plot/data-transform.ts +0 -69
  722. package/dist/src/lib/plot/defaults.ts +0 -9
  723. package/dist/src/lib/plot/fill-utils.ts +0 -494
  724. package/dist/src/lib/plot/hover-lock.svelte.ts +0 -60
  725. package/dist/src/lib/plot/index.ts +0 -53
  726. package/dist/src/lib/plot/interactions.ts +0 -119
  727. package/dist/src/lib/plot/layout.ts +0 -425
  728. package/dist/src/lib/plot/reference-line.ts +0 -426
  729. package/dist/src/lib/plot/scales.ts +0 -654
  730. package/dist/src/lib/plot/svg.ts +0 -23
  731. package/dist/src/lib/plot/types.ts +0 -1144
  732. package/dist/src/lib/plot/utils/label-placement.ts +0 -541
  733. package/dist/src/lib/plot/utils/series-visibility.ts +0 -140
  734. package/dist/src/lib/plot/utils.ts +0 -11
  735. package/dist/src/lib/rdf/RdfPlot.svelte +0 -247
  736. package/dist/src/lib/rdf/calc-rdf.ts +0 -167
  737. package/dist/src/lib/rdf/index.ts +0 -27
  738. package/dist/src/lib/sanitize.ts +0 -126
  739. package/dist/src/lib/settings.ts +0 -1479
  740. package/dist/src/lib/spectral/Bands.svelte +0 -1040
  741. package/dist/src/lib/spectral/BandsAndDos.svelte +0 -134
  742. package/dist/src/lib/spectral/BrillouinBandsDos.svelte +0 -252
  743. package/dist/src/lib/spectral/Dos.svelte +0 -697
  744. package/dist/src/lib/spectral/helpers.ts +0 -1381
  745. package/dist/src/lib/spectral/index.ts +0 -8
  746. package/dist/src/lib/spectral/types.ts +0 -112
  747. package/dist/src/lib/state.svelte.ts +0 -64
  748. package/dist/src/lib/structure/Arrow.svelte +0 -72
  749. package/dist/src/lib/structure/AtomLegend.svelte +0 -815
  750. package/dist/src/lib/structure/Bond.svelte +0 -140
  751. package/dist/src/lib/structure/CanvasTooltip.svelte +0 -33
  752. package/dist/src/lib/structure/CellSelect.svelte +0 -349
  753. package/dist/src/lib/structure/Cylinder.svelte +0 -45
  754. package/dist/src/lib/structure/Lattice.svelte +0 -196
  755. package/dist/src/lib/structure/Structure.svelte +0 -2248
  756. package/dist/src/lib/structure/StructureControls.svelte +0 -1273
  757. package/dist/src/lib/structure/StructureExportPane.svelte +0 -252
  758. package/dist/src/lib/structure/StructureInfoPane.svelte +0 -737
  759. package/dist/src/lib/structure/StructureScene.svelte +0 -2255
  760. package/dist/src/lib/structure/atom-properties.ts +0 -316
  761. package/dist/src/lib/structure/bond-order-perception.ts +0 -447
  762. package/dist/src/lib/structure/bonding.ts +0 -944
  763. package/dist/src/lib/structure/export.ts +0 -861
  764. package/dist/src/lib/structure/index.ts +0 -291
  765. package/dist/src/lib/structure/label-placement.ts +0 -130
  766. package/dist/src/lib/structure/measure.ts +0 -45
  767. package/dist/src/lib/structure/parse.ts +0 -1705
  768. package/dist/src/lib/structure/partial-occupancy.ts +0 -183
  769. package/dist/src/lib/structure/pbc.ts +0 -164
  770. package/dist/src/lib/structure/supercell.ts +0 -226
  771. package/dist/src/lib/structure/validation.ts +0 -11
  772. package/dist/src/lib/symmetry/SymmetryStats.svelte +0 -226
  773. package/dist/src/lib/symmetry/WyckoffTable.svelte +0 -120
  774. package/dist/src/lib/symmetry/cell-transform.ts +0 -118
  775. package/dist/src/lib/symmetry/index.ts +0 -348
  776. package/dist/src/lib/symmetry/spacegroups.ts +0 -404
  777. package/dist/src/lib/table/HeatmapTable.svelte +0 -1833
  778. package/dist/src/lib/table/ToggleMenu.svelte +0 -385
  779. package/dist/src/lib/table/index.ts +0 -139
  780. package/dist/src/lib/theme/ThemeControl.svelte +0 -53
  781. package/dist/src/lib/theme/index.ts +0 -107
  782. package/dist/src/lib/time.ts +0 -71
  783. package/dist/src/lib/tooltip/TooltipContent.svelte +0 -58
  784. package/dist/src/lib/tooltip/index.ts +0 -2
  785. package/dist/src/lib/tooltip/types.ts +0 -13
  786. package/dist/src/lib/trajectory/Trajectory.svelte +0 -1545
  787. package/dist/src/lib/trajectory/TrajectoryError.svelte +0 -128
  788. package/dist/src/lib/trajectory/TrajectoryExportPane.svelte +0 -357
  789. package/dist/src/lib/trajectory/TrajectoryInfoPane.svelte +0 -313
  790. package/dist/src/lib/trajectory/constants.ts +0 -7
  791. package/dist/src/lib/trajectory/extract.ts +0 -196
  792. package/dist/src/lib/trajectory/format-detect.ts +0 -96
  793. package/dist/src/lib/trajectory/frame-reader.ts +0 -456
  794. package/dist/src/lib/trajectory/helpers.ts +0 -217
  795. package/dist/src/lib/trajectory/index.ts +0 -218
  796. package/dist/src/lib/trajectory/parse/ase.ts +0 -109
  797. package/dist/src/lib/trajectory/parse/hdf5.ts +0 -173
  798. package/dist/src/lib/trajectory/parse/index.ts +0 -411
  799. package/dist/src/lib/trajectory/parse/lammps.ts +0 -215
  800. package/dist/src/lib/trajectory/parse/vasp.ts +0 -102
  801. package/dist/src/lib/trajectory/parse/xyz.ts +0 -143
  802. package/dist/src/lib/trajectory/plotting.ts +0 -599
  803. package/dist/src/lib/trajectory/types.ts +0 -13
  804. package/dist/src/lib/utils.ts +0 -56
  805. package/dist/src/lib/xrd/XrdPlot.svelte +0 -615
  806. package/dist/src/lib/xrd/broadening.ts +0 -130
  807. package/dist/src/lib/xrd/calc-xrd.ts +0 -397
  808. package/dist/src/lib/xrd/index.ts +0 -38
  809. package/dist/src/lib/xrd/parse.ts +0 -858
  810. package/dist/webview.js +0 -29421
  811. package/icon.png +0 -0
  812. package/matterviz-0.3.2.vsix +0 -0
  813. package/matterviz-0.3.4.vsix +0 -0
  814. package/matterviz-0.3.5.vsix +0 -0
  815. package/scripts/sync-config.ts +0 -101
  816. package/src/declarations.d.ts +0 -2
  817. package/src/extension.ts +0 -972
  818. package/src/node-io.ts +0 -65
  819. package/src/types.ts +0 -17
  820. package/src/webview/JsonBrowser.svelte +0 -1079
  821. package/src/webview/PlotPanel.svelte +0 -346
  822. package/src/webview/detect.ts +0 -444
  823. package/src/webview/main.ts +0 -764
  824. package/src/webview/plot-utils.ts +0 -250
  825. package/test-fixtures/all-viz-types.json.gz +0 -0
  826. package/test-fixtures/plot-demo-data.json.gz +0 -0
  827. package/tests/detect.test.ts +0 -604
  828. package/tests/extension.test.ts +0 -2041
  829. package/tests/node-io.test.ts +0 -39
  830. package/tests/plot-utils.test.ts +0 -302
  831. package/tests/vite-plugin-json-gz.test.ts +0 -114
  832. package/tests/vscode-mock.ts +0 -18
  833. package/tests/webview.test.ts +0 -231
  834. package/tsconfig.json +0 -20
  835. package/vite-plugin-json-gz.ts +0 -29
  836. package/vite.config.ts +0 -34
  837. package/vite.extension.config.ts +0 -34
  838. /package/dist/{src/lib/EmptyState.svelte → EmptyState.svelte} +0 -0
  839. /package/dist/{src/lib/Icon.svelte → Icon.svelte} +0 -0
  840. /package/dist/{src/lib/app.css → app.css} +0 -0
  841. /package/dist/{src/lib/chempot-diagram → chempot-diagram}/ChemPotScene3D.svelte +0 -0
  842. /package/dist/{src/lib/colors → colors}/alloy-colors.json +0 -0
  843. /package/dist/{src/lib/colors → colors}/dark-mode-colors.json +0 -0
  844. /package/dist/{src/lib/colors → colors}/jmol-colors.json +0 -0
  845. /package/dist/{src/lib/colors → colors}/muted-colors.json +0 -0
  846. /package/dist/{src/lib/colors → colors}/pastel-colors.json +0 -0
  847. /package/dist/{src/lib/colors → colors}/vesta-colors.json +0 -0
  848. /package/dist/{src/lib/convex-hull → convex-hull}/TemperatureSlider.svelte +0 -0
  849. /package/dist/{src/lib/element → element}/BohrAtom.svelte +0 -0
  850. /package/dist/{src/lib/element → element}/Nucleus.svelte +0 -0
  851. /package/dist/{src/lib/element → element}/data.json +0 -0
  852. /package/dist/{src/lib/element → element}/data.json.gz +0 -0
  853. /package/dist/{src/lib/element → element}/data.json.gz.d.ts +0 -0
  854. /package/dist/{src/lib/element → element}/data.schema.json +0 -0
  855. /package/dist/{src/lib/element-image-urls.json → element-image-urls.json} +0 -0
  856. /package/dist/{src/lib/feedback → feedback}/Spinner.svelte +0 -0
  857. /package/dist/{src/lib/feedback → feedback}/StatusMessage.svelte +0 -0
  858. /package/dist/{src/lib/layout → layout}/json-tree/JsonValue.svelte +0 -0
  859. /package/dist/{src/lib/periodic-table → periodic-table}/TableInset.svelte +0 -0
  860. /package/dist/{src/lib/plot → plot}/FillArea.svelte +0 -0
  861. /package/dist/{src/lib/plot → plot}/ReferenceLine.svelte +0 -0
  862. /package/dist/{src/lib/theme → theme}/themes.mjs +0 -0
  863. /package/dist/{src/lib/xrd → xrd}/atomic_scattering_params.json +0 -0
@@ -1,3193 +0,0 @@
1
- <script lang="ts">
2
- import type { D3InterpolateName } from '$lib/colors'
3
- import { get_electro_neg_formula, get_formula_label_segments } from '$lib/composition/format'
4
- import type { FormulaLabelSegment } from '$lib/composition/format'
5
- import { extract_formula_elements } from '$lib/composition/parse'
6
- import TemperatureSlider from '$lib/convex-hull/TemperatureSlider.svelte'
7
- import type { PhaseData } from '$lib/convex-hull/types'
8
- import Spinner from '$lib/feedback/Spinner.svelte'
9
- import Icon from '$lib/Icon.svelte'
10
- import { format_num } from '$lib/labels'
11
- import { set_fullscreen_bg, SettingsSection, toggle_fullscreen } from '$lib/layout'
12
- import type { Vec2, Vec3 } from '$lib/math'
13
- import {
14
- convex_hull_2d,
15
- cross_3d,
16
- merge_coplanar_triangles,
17
- normalize_vec3,
18
- } from '$lib/math'
19
- import DraggablePane from '$lib/overlays/DraggablePane.svelte'
20
- import { ColorBar, ScatterPlot3DControls } from '$lib/plot'
21
- import {
22
- constrain_tooltip_position,
23
- pad_rect,
24
- rects_overlap,
25
- } from '$lib/plot/layout'
26
- import type {
27
- AxisConfig3D,
28
- CameraProjection3D,
29
- DataSeries3D,
30
- DisplayConfig3D,
31
- } from '$lib/plot/types'
32
- import { Canvas, T } from '@threlte/core'
33
- import * as extras from '@threlte/extras'
34
- import { scaleLinear } from 'd3-scale'
35
- import { onDestroy, onMount, untrack } from 'svelte'
36
- import { SvelteMap, SvelteSet } from 'svelte/reactivity'
37
- import * as THREE from 'three'
38
- import type { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
39
- import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'
40
- import { ConvexGeometry } from 'three/examples/jsm/geometries/ConvexGeometry.js'
41
- import { compute_chempot_async } from './async-compute.svelte'
42
- import ChemPotScene3D from './ChemPotScene3D.svelte'
43
- import { get_chempot_color_bar_config, make_chempot_color_scale } from './color'
44
- import {
45
- apply_element_padding,
46
- bbox_diagonal,
47
- best_form_energy_for_formula,
48
- build_axis_ranges,
49
- dedup_points,
50
- formula_key_from_composition,
51
- get_3d_domain_simplexes_and_ann_loc,
52
- get_energy_per_atom,
53
- get_min_entries_and_el_refs,
54
- get_ternary_combinations,
55
- get_visible_domain_labels,
56
- pad_domain_points,
57
- scale_to_font_range,
58
- } from './compute'
59
- import { with_hover_pointer } from './pointer'
60
- import {
61
- get_projection_source_entries,
62
- get_temp_filter_payload,
63
- get_valid_temperature,
64
- } from './temperature'
65
- import type {
66
- ChemPotColorMode,
67
- ChemPotDiagramConfig,
68
- ChemPotDiagramData,
69
- ChemPotHoverInfo,
70
- ChemPotHoverInfo3D,
71
- } from './types'
72
- import { CHEMPOT_DEFAULTS } from './types'
73
-
74
- const edge_key = (key_a: string, key_b: string): string =>
75
- key_a < key_b ? `${key_a}|${key_b}` : `${key_b}|${key_a}`
76
-
77
- let {
78
- entries = [],
79
- config = {},
80
- width = $bindable(800),
81
- height = $bindable(600),
82
- // Auto-corrected to a valid available temperature when needed.
83
- temperature = $bindable<number | undefined>(undefined),
84
- interpolate_temperature = CHEMPOT_DEFAULTS.interpolate_temperature,
85
- max_interpolation_gap = CHEMPOT_DEFAULTS.max_interpolation_gap,
86
- hover_info = $bindable<ChemPotHoverInfo | null>(null),
87
- render_local_tooltip = true,
88
- }: {
89
- entries: PhaseData[]
90
- config?: ChemPotDiagramConfig
91
- width?: number
92
- height?: number
93
- temperature?: number
94
- interpolate_temperature?: boolean
95
- max_interpolation_gap?: number
96
- hover_info?: ChemPotHoverInfo | null
97
- render_local_tooltip?: boolean
98
- } = $props()
99
-
100
- let formal_chempots_override = $state<boolean | null>(null)
101
- let label_stable_override = $state<boolean | null>(null)
102
- let element_padding_override = $state<number | null>(null)
103
- let default_min_limit_override = $state<number | null>(null)
104
- let draw_formula_meshes_override = $state<boolean | null>(null)
105
- let draw_formula_lines_override = $state<boolean | null>(null)
106
- const formal_chempots = $derived(
107
- formal_chempots_override ??
108
- (config.formal_chempots ?? CHEMPOT_DEFAULTS.formal_chempots),
109
- )
110
- const label_stable = $derived(
111
- label_stable_override ?? (config.label_stable ?? CHEMPOT_DEFAULTS.label_stable),
112
- )
113
- const element_padding = $derived(
114
- element_padding_override ??
115
- (config.element_padding ?? CHEMPOT_DEFAULTS.element_padding),
116
- )
117
- const default_min_limit = $derived(
118
- default_min_limit_override ??
119
- (config.default_min_limit ?? CHEMPOT_DEFAULTS.default_min_limit),
120
- )
121
- let formulas_to_draw_override = $state<string[] | null>(null)
122
- const formulas_to_draw = $derived(
123
- formulas_to_draw_override ?? (config.formulas_to_draw ?? []),
124
- )
125
- const draw_formula_meshes = $derived(
126
- draw_formula_meshes_override ??
127
- (config.draw_formula_meshes ?? CHEMPOT_DEFAULTS.draw_formula_meshes),
128
- )
129
- const draw_formula_lines = $derived(
130
- draw_formula_lines_override ??
131
- (config.draw_formula_lines ?? CHEMPOT_DEFAULTS.draw_formula_lines),
132
- )
133
- let color_mode_override = $state<ChemPotColorMode | null>(null)
134
- let color_scale_override = $state<D3InterpolateName | null>(null)
135
- let reverse_color_scale_override = $state<boolean | null>(null)
136
- const color_mode = $derived(
137
- color_mode_override ?? (config.color_mode ?? `arity`),
138
- )
139
- const color_scale = $derived(
140
- color_scale_override ?? (config.color_scale ?? CHEMPOT_DEFAULTS.color_scale),
141
- )
142
- const reverse_color_scale = $derived(
143
- reverse_color_scale_override ??
144
- (config.reverse_color_scale ?? CHEMPOT_DEFAULTS.reverse_color_scale),
145
- )
146
- const show_tooltip = $derived(config.show_tooltip ?? CHEMPOT_DEFAULTS.show_tooltip)
147
- const tooltip_detail_level = $derived(
148
- config.tooltip_detail_level ?? CHEMPOT_DEFAULTS.tooltip_detail_level,
149
- )
150
- const formula_colors = $derived(
151
- config.formula_colors?.length
152
- ? config.formula_colors
153
- : CHEMPOT_DEFAULTS.formula_colors,
154
- )
155
-
156
- const formula_label_segments = (formula: string): FormulaLabelSegment[] =>
157
- get_formula_label_segments(get_electro_neg_formula(formula, true, ``, `.3~s`))
158
-
159
- function normalize_projection_triplet(
160
- maybe_triplet: string[] | undefined,
161
- available_elements: string[],
162
- ): string[] | null {
163
- if (!maybe_triplet || maybe_triplet.length !== 3) return null
164
- const deduped = Array.from(new Set(maybe_triplet))
165
- if (deduped.length !== 3) return null
166
- if (deduped.some((element) => !available_elements.includes(element))) return null
167
- return deduped
168
- }
169
-
170
- let wrapper = $state<HTMLDivElement>()
171
- let fullscreen = $state(false)
172
- let export_pane_open = $state(false)
173
- let formula_picker_open = $state(false)
174
- let controls_open = $state(false)
175
-
176
- // Mutual exclusion: only one pane open at a time.
177
- // Separate effects so each reacts to its own pane opening independently —
178
- // a single $derived ternary would create priority ordering where opening
179
- // a "lower" pane while a "higher" one is open fails silently.
180
- $effect(() => { if (export_pane_open) { formula_picker_open = false; controls_open = false } })
181
- $effect(() => { if (formula_picker_open) { export_pane_open = false; controls_open = false } })
182
- $effect(() => { if (controls_open) { export_pane_open = false; formula_picker_open = false } })
183
- let copy_status = $state(false)
184
- let copy_timeout_id: ReturnType<typeof setTimeout> | null = null
185
- let container_width = $state(0)
186
- let container_height = $state(0)
187
- const base_aspect_ratio = $derived(height > 0 && width > 0 ? height / width : 1)
188
- const render_width = $derived(container_width > 0 ? container_width : width)
189
- const render_height = $derived(
190
- fullscreen
191
- ? (container_height > 0 ? container_height : height)
192
- : Math.round(render_width * base_aspect_ratio),
193
- )
194
-
195
- let mounted = $state(false)
196
- onMount(() => mounted = true)
197
- let orbit_controls_ref = $state<OrbitControls | undefined>(undefined)
198
- // Backside tracking: axes/ticks/labels render on the far side from the camera
199
- // back[i] = backside data coordinate value for data axis i
200
- // Matches ScatterPlot3DScene pattern where pos tracks the opposite side from camera
201
- let back = $state([0, 0, 0])
202
- // Outward offset signs for tick/label placement (away from bounding box)
203
- let out_x = $state(-1) // sign for Three.js X (data axis 1) direction
204
- let out_y = $state(-1) // sign for Three.js Y (data axis 2) direction
205
- let camera_projection = $state<CameraProjection3D>(`orthographic`)
206
- let auto_rotate = $state(0)
207
- let display = $state<DisplayConfig3D>({
208
- show_axes: true,
209
- show_grid: true,
210
- show_axis_labels: true,
211
- show_bounding_box: false,
212
- projections: { xy: false, xz: false, yz: false },
213
- projection_opacity: 0.15,
214
- projection_scale: 0.5,
215
- })
216
- let x_axis = $state<AxisConfig3D>({ label: ``, range: [null, null] })
217
- let y_axis = $state<AxisConfig3D>({ label: ``, range: [null, null] })
218
- let z_axis = $state<AxisConfig3D>({ label: ``, range: [null, null] })
219
- const projection_opacity = $derived(display.projection_opacity ?? 0.15)
220
-
221
- // Plotly/pymatgen uses Z-up with x-axis projecting left in isometric view.
222
- // Three.js uses Y-up with X projecting right. To match pymatgen's visual layout:
223
- // data[0] (plotly x, projects left) → Three.js Z (projects left)
224
- // data[1] (plotly y, projects right) → Three.js X (projects right)
225
- // data[2] (plotly z, projects up) → Three.js Y (projects up)
226
- function to_vec3(pt: number[]): THREE.Vector3 {
227
- const [x_val, y_val, z_val] = to_render_xyz(pt)
228
- return new THREE.Vector3(x_val, y_val, z_val)
229
- }
230
-
231
- // Compute diagram data (requires >= 3 elements for 3D rendering)
232
- const { has_temp_data, available_temperatures, temp_filtered_entries } = $derived(
233
- get_temp_filter_payload(entries, temperature, config, {
234
- interpolate_temperature,
235
- max_interpolation_gap,
236
- }),
237
- )
238
-
239
- // Keep bound temperature aligned with available data points.
240
- $effect(() => {
241
- const next_temperature = get_valid_temperature(
242
- temperature,
243
- has_temp_data,
244
- available_temperatures,
245
- )
246
- if (next_temperature !== temperature) temperature = next_temperature
247
- })
248
-
249
- const show_temperature_slider = $derived(
250
- has_temp_data && available_temperatures.length > 0,
251
- )
252
-
253
- const projection_source_entries = $derived(
254
- get_projection_source_entries(entries, temp_filtered_entries),
255
- )
256
-
257
- const all_entry_elements = $derived.by(() =>
258
- Array.from(
259
- new SvelteSet(
260
- projection_source_entries.flatMap((entry) =>
261
- Object.entries(entry.composition)
262
- .filter(([, amount]) => amount > 0)
263
- .map(([element]) => element)
264
- ),
265
- ),
266
- ).sort()
267
- )
268
- const has_multinary_system = $derived(all_entry_elements.length > 3)
269
- let projection_elements_override = $state<string[] | null>(null)
270
- const config_projection_elements = $derived(
271
- normalize_projection_triplet(config.elements, all_entry_elements),
272
- )
273
- const projection_elements = $derived.by(() => {
274
- if (all_entry_elements.length < 3) return []
275
- if (!has_multinary_system) {
276
- return config_projection_elements ?? all_entry_elements.slice(0, 3)
277
- }
278
- const override_projection = normalize_projection_triplet(
279
- projection_elements_override ?? undefined,
280
- all_entry_elements,
281
- )
282
- if (override_projection) return override_projection
283
- if (config_projection_elements) return config_projection_elements
284
- return all_entry_elements.slice(0, 3)
285
- })
286
- const effective_config = $derived({
287
- ...config,
288
- elements: projection_elements.length === 3
289
- ? projection_elements
290
- : config.elements,
291
- formal_chempots,
292
- label_stable,
293
- element_padding,
294
- default_min_limit,
295
- draw_formula_meshes,
296
- draw_formula_lines,
297
- })
298
- let diagram_data = $state<ChemPotDiagramData | null>(null)
299
- let diagram_computing = $state(false)
300
- $effect(() => {
301
- if (temp_filtered_entries.length < 3) {
302
- diagram_data = null
303
- diagram_computing = false
304
- return
305
- }
306
- let cancelled = false
307
- diagram_computing = true
308
- compute_chempot_async(temp_filtered_entries, effective_config)
309
- .then((data) => {
310
- if (cancelled) return
311
- diagram_data = data.elements.length >= 3 ? data : null
312
- diagram_computing = false
313
- })
314
- .catch((err) => {
315
- if (cancelled) return
316
- console.error(`ChemPotDiagram3D:`, err)
317
- diagram_data = null
318
- diagram_computing = false
319
- })
320
- return () => { cancelled = true }
321
- })
322
-
323
- const plot_elements = $derived(diagram_data?.elements ?? projection_elements)
324
- const is_projection_mode = $derived(
325
- plot_elements.length > 0 &&
326
- plot_elements.length < all_entry_elements.length &&
327
- plot_elements.every((element) => all_entry_elements.includes(element)),
328
- )
329
- const projection_presets = $derived.by(() => {
330
- const presets: string[][] = []
331
- const seen = new Set<string>()
332
- const add_triplet = (candidate: string[] | null): void => {
333
- if (!candidate) return
334
- const key = candidate.join(`|`)
335
- if (seen.has(key)) return
336
- seen.add(key)
337
- presets.push(candidate)
338
- }
339
- add_triplet(config_projection_elements)
340
- add_triplet(plot_elements.length === 3 ? plot_elements : null)
341
- for (const combo of get_ternary_combinations(all_entry_elements)) {
342
- add_triplet(combo)
343
- if (presets.length >= 12) break
344
- }
345
- return presets
346
- })
347
- const current_projection_key = $derived(plot_elements.join(`|`))
348
- let formula_filter_query = $state(``)
349
- const available_formulas = $derived.by(() =>
350
- Object.keys(diagram_data?.domains ?? {}).sort()
351
- )
352
- const filtered_formulas = $derived.by(() => {
353
- const query = formula_filter_query.trim().toLowerCase()
354
- if (!query) return available_formulas
355
- return available_formulas.filter((formula) =>
356
- formula.toLowerCase().includes(query)
357
- )
358
- })
359
-
360
- // Process domains for rendering
361
- interface DomainRenderData {
362
- formula: string
363
- points_3d: number[][]
364
- ann_loc: number[]
365
- is_draw_formula: boolean
366
- label_font_size: number
367
- }
368
-
369
- interface HoverMeshData {
370
- formula: string
371
- geometry: THREE.BufferGeometry
372
- info: ChemPotHoverInfo3D
373
- }
374
-
375
- interface FormulaEnergyStats {
376
- matching_entry_count: number
377
- min_energy_per_atom: number | null
378
- max_energy_per_atom: number | null
379
- }
380
- type NumericColorMode = Exclude<ChemPotColorMode, `none` | `arity`>
381
-
382
- const render_domains = $derived.by((): DomainRenderData[] => {
383
- if (!diagram_data || plot_elements.length < 2) return []
384
-
385
- const dim = diagram_data.elements.length
386
- const indices = Array.from({ length: dim }, (_, idx) => idx)
387
- const new_lims = element_padding > 0
388
- ? apply_element_padding(
389
- diagram_data.domains,
390
- indices,
391
- element_padding,
392
- default_min_limit,
393
- )
394
- : null
395
-
396
- const result: DomainRenderData[] = []
397
- for (const [formula, pts] of Object.entries(diagram_data.domains)) {
398
- const padded = new_lims
399
- ? pad_domain_points(
400
- pts,
401
- indices,
402
- new_lims,
403
- default_min_limit,
404
- element_padding,
405
- )
406
- : pts
407
- if (padded.length < 2) continue
408
- const is_draw = formulas_to_draw.includes(formula)
409
- const centroid = padded[0].map((_, col_idx) =>
410
- padded.reduce((sum, point) => sum + point[col_idx], 0) / padded.length
411
- )
412
- const ann_loc = padded.length >= 3
413
- ? get_3d_domain_simplexes_and_ann_loc(padded).ann_loc
414
- : centroid
415
- result.push({
416
- formula,
417
- points_3d: padded,
418
- ann_loc,
419
- is_draw_formula: is_draw,
420
- label_font_size: bbox_diagonal(padded),
421
- })
422
- }
423
- const fonts = scale_to_font_range(
424
- result.map((render_domain) => render_domain.label_font_size),
425
- 9,
426
- 15,
427
- )
428
- for (let idx = 0; idx < result.length; idx++) result[idx].label_font_size = fonts[idx]
429
- return result
430
- })
431
-
432
- const entry_energy_stats_by_formula = $derived.by(
433
- (): SvelteMap<string, FormulaEnergyStats> => {
434
- const stats_by_formula = new SvelteMap<string, FormulaEnergyStats>()
435
- for (const entry of temp_filtered_entries) {
436
- const formula_key = formula_key_from_composition(entry.composition)
437
- const energy_per_atom = get_energy_per_atom(entry)
438
- const existing = stats_by_formula.get(formula_key)
439
- if (!existing) {
440
- stats_by_formula.set(formula_key, {
441
- matching_entry_count: 1,
442
- min_energy_per_atom: energy_per_atom,
443
- max_energy_per_atom: energy_per_atom,
444
- })
445
- continue
446
- }
447
- stats_by_formula.set(formula_key, {
448
- matching_entry_count: existing.matching_entry_count + 1,
449
- min_energy_per_atom: Math.min(
450
- existing.min_energy_per_atom ?? energy_per_atom,
451
- energy_per_atom,
452
- ),
453
- max_energy_per_atom: Math.max(
454
- existing.max_energy_per_atom ?? energy_per_atom,
455
- energy_per_atom,
456
- ),
457
- })
458
- }
459
- return stats_by_formula
460
- },
461
- )
462
-
463
- // === Region coloring ===
464
- // Categorical palette for arity mode (element count)
465
- const arity_colors = [`#3498db`, `#2ecc71`, `#e67e22`, `#9b59b6`] as const
466
-
467
- // Original (non-renormalized) elemental references for formation energy computation.
468
- // diagram_data.el_refs may be renormalized to zero when formal_chempots is true,
469
- // so we compute our own from the raw entries to get true DFT reference energies.
470
- const raw_el_refs = $derived(
471
- get_min_entries_and_el_refs(temp_filtered_entries).el_refs,
472
- )
473
-
474
- const color_mode_labels: Record<NumericColorMode, string> = {
475
- energy: `Energy per atom (eV)`,
476
- formation_energy: `Formation energy (eV/atom)`,
477
- entries: `Entry count`,
478
- }
479
- function get_numeric_color_value(
480
- formula: string,
481
- active_color_mode: NumericColorMode,
482
- ): number | null {
483
- if (active_color_mode === `energy`) {
484
- return entry_energy_stats_by_formula.get(formula)?.min_energy_per_atom ?? null
485
- }
486
- if (active_color_mode === `formation_energy`) {
487
- return best_form_energy_for_formula(
488
- temp_filtered_entries,
489
- formula,
490
- raw_el_refs,
491
- ) ?? null
492
- }
493
- return entry_energy_stats_by_formula.get(formula)?.matching_entry_count ?? 0
494
- }
495
- const domain_color_values = $derived.by(
496
- (): { value_by_formula: SvelteMap<string, number>; values: number[] } | null => {
497
- if (color_mode === `none` || color_mode === `arity`) return null
498
- const active_color_mode = color_mode as NumericColorMode
499
- const value_by_formula = new SvelteMap<string, number>()
500
- const values: number[] = []
501
- for (const domain of render_domains) {
502
- const value = get_numeric_color_value(domain.formula, active_color_mode)
503
- if (value == null || !Number.isFinite(value)) continue
504
- values.push(value)
505
- value_by_formula.set(domain.formula, value)
506
- }
507
- return { value_by_formula, values }
508
- },
509
- )
510
-
511
- // Per-domain color map keyed by formula
512
- const domain_colors = $derived.by((): SvelteMap<string, string> => {
513
- const colors = new SvelteMap<string, string>()
514
- if (color_mode === `none`) return colors
515
-
516
- if (color_mode === `arity`) {
517
- for (const domain of render_domains) {
518
- const n_elements = extract_formula_elements(domain.formula).length
519
- const idx = Math.min(n_elements, arity_colors.length) - 1
520
- colors.set(domain.formula, arity_colors[Math.max(0, idx)])
521
- }
522
- return colors
523
- }
524
- const values_payload = domain_color_values
525
- const scale = make_chempot_color_scale(
526
- values_payload?.values ?? [],
527
- color_scale,
528
- reverse_color_scale,
529
- )
530
- for (const domain of render_domains) {
531
- const value = values_payload?.value_by_formula.get(domain.formula)
532
- colors.set(domain.formula, value != null && scale ? scale(value) : `#999`)
533
- }
534
- return colors
535
- })
536
-
537
- // Range and label for the color bar (null for none/arity which are categorical)
538
- const color_range = $derived.by(
539
- (): { min: number; max: number; label: string } | null => {
540
- const values = domain_color_values?.values ?? []
541
- if (values.length === 0) return null
542
- let lo = values[0], hi = values[0]
543
- for (let idx = 1; idx < values.length; idx++) {
544
- if (values[idx] < lo) lo = values[idx]
545
- if (values[idx] > hi) hi = values[idx]
546
- }
547
- return {
548
- min: lo,
549
- max: Math.max(hi, lo + 1e-6),
550
- label: color_mode === `none` || color_mode === `arity`
551
- ? ``
552
- : color_mode_labels[color_mode],
553
- }
554
- },
555
- )
556
-
557
- const arity_legend_labels = $derived.by((): string[] => {
558
- let has_four_plus_regions = false
559
- for (const domain of render_domains) {
560
- if (extract_formula_elements(domain.formula).length >= 4) {
561
- has_four_plus_regions = true
562
- break
563
- }
564
- }
565
- return has_four_plus_regions
566
- ? [`Unary`, `Binary`, `Ternary`, `4+`]
567
- : [`Unary`, `Binary`, `Ternary`]
568
- })
569
-
570
- // Stretch short axes to improve screen-space utilization for highly anisotropic systems.
571
- // Mapping is in rendered axis order: X=data[1], Y=data[2], Z=data[0].
572
- const render_axis_scale = $derived.by((): Vec3 => {
573
- const points = render_domains.flatMap((domain) => domain.points_3d)
574
- if (points.length === 0) return [1, 1, 1]
575
- let min0 = Infinity, max0 = -Infinity
576
- let min1 = Infinity, max1 = -Infinity
577
- let min2 = Infinity, max2 = -Infinity
578
- for (const point of points) {
579
- if (point[0] < min0) min0 = point[0]
580
- if (point[0] > max0) max0 = point[0]
581
- if (point[1] < min1) min1 = point[1]
582
- if (point[1] > max1) max1 = point[1]
583
- if (point[2] < min2) min2 = point[2]
584
- if (point[2] > max2) max2 = point[2]
585
- }
586
- const span_x = Math.max(max1 - min1, 1e-6) // render X from data axis 1
587
- const span_y = Math.max(max2 - min2, 1e-6) // render Y from data axis 2
588
- const span_z = Math.max(max0 - min0, 1e-6) // render Z from data axis 0
589
- const max_span = Math.max(span_x, span_y, span_z)
590
- return [
591
- Math.min(Math.max(max_span / span_x, 1), 4),
592
- Math.min(Math.max(max_span / span_y, 1), 4),
593
- Math.min(Math.max(max_span / span_z, 1), 4),
594
- ]
595
- })
596
-
597
- function to_render_xyz(point: number[]): Vec3 {
598
- const [scale_x, scale_y, scale_z] = render_axis_scale
599
- return [point[1] * scale_x, point[2] * scale_y, point[0] * scale_z]
600
- }
601
-
602
- // Compute data center and extent for camera positioning (in swizzled coords)
603
- const { data_center, data_extent } = $derived.by(() => {
604
- const points = render_domains.flatMap((domain) => domain.points_3d)
605
- if (points.length === 0) {
606
- return { data_center: new THREE.Vector3(0, 0, 0), data_extent: 10 }
607
- }
608
- // Compute center in rendered coordinates (swizzled + axis scaling).
609
- let [sum_x, sum_y, sum_z] = [0, 0, 0]
610
- for (const point_3d of points) {
611
- const [x_val, y_val, z_val] = to_render_xyz(point_3d)
612
- sum_x += x_val
613
- sum_y += y_val
614
- sum_z += z_val
615
- }
616
- const n_points = points.length
617
- const center = new THREE.Vector3(
618
- sum_x / n_points,
619
- sum_y / n_points,
620
- sum_z / n_points,
621
- )
622
- // Compute max distance from center
623
- let max_dist = 0
624
- for (const point of points) {
625
- const [x_val, y_val, z_val] = to_render_xyz(point)
626
- const dist = Math.hypot(x_val - center.x, y_val - center.y, z_val - center.z)
627
- if (dist > max_dist) max_dist = dist
628
- }
629
- return { data_center: center, data_extent: Math.max(max_dist * 1.3, 1) }
630
- })
631
- const default_camera_position = $derived<Vec3>([
632
- data_center.x + data_extent,
633
- data_center.y + data_extent,
634
- data_center.z + data_extent,
635
- ])
636
- const default_camera_target = $derived<Vec3>([
637
- data_center.x,
638
- data_center.y,
639
- data_center.z,
640
- ])
641
- const default_orthographic_zoom = $derived(
642
- Math.min(render_width, render_height) / (data_extent * 1.6),
643
- )
644
- let camera_position_override = $state<Vec3 | null>(null)
645
- let camera_target_override = $state<Vec3 | null>(null)
646
- let orthographic_zoom_override = $state<number | null>(null)
647
- const camera_position = $derived(
648
- camera_position_override ?? default_camera_position,
649
- )
650
- const camera_target = $derived(
651
- camera_target_override ?? default_camera_target,
652
- )
653
- const orthographic_zoom = $derived(
654
- orthographic_zoom_override ?? default_orthographic_zoom,
655
- )
656
- // Label scale factor: zoom relative to default, so labels grow/shrink with zoom
657
- // Labels scale sub-linearly with zoom so they grow but don't dominate when zoomed in
658
- const zoom_scale = $derived(
659
- default_orthographic_zoom > 0 ? Math.sqrt(orthographic_zoom / default_orthographic_zoom) : 1,
660
- )
661
- let last_data_center: Vec3 | null = null
662
- let last_data_extent: number | null = null
663
-
664
- // Compute domain boundary edges via axis-aligned 2D convex hull projection.
665
- // Each domain in a chem pot diagram is a convex polygon/polyhedron. We project
666
- // to 2D (trying all 3 axis-aligned planes) and use the best projection's
667
- // convex hull boundary. This reliably handles both flat and 3D domains.
668
- function get_domain_edges(
669
- pts: number[][],
670
- ): [number[], number[]][] {
671
- const unique = dedup_3d(pts)
672
- if (unique.length < 2) return []
673
- if (unique.length === 2) return [[unique[0], unique[1]]]
674
- if (unique.length === 3) {
675
- return [[unique[0], unique[1]], [unique[1], unique[2]], [unique[0], unique[2]]]
676
- }
677
- return get_2d_hull_edges(unique)
678
- }
679
-
680
- function polygon_area_2d(points_2d: Vec2[]): number {
681
- if (points_2d.length < 3) return 0
682
- let area_twice = 0
683
- for (let idx = 0; idx < points_2d.length; idx++) {
684
- const current = points_2d[idx]
685
- const next = points_2d[(idx + 1) % points_2d.length]
686
- area_twice += current[0] * next[1] - next[0] * current[1]
687
- }
688
- return Math.abs(area_twice) / 2
689
- }
690
-
691
- // Compute domain edges from the single best axis-aligned projection
692
- // (largest non-degenerate hull area). Unioning multiple projections can add
693
- // non-physical diagonals for nearly coplanar domains.
694
- // Called only from get_domain_edges with 4+ unique points
695
- function get_2d_hull_edges(
696
- pts: number[][],
697
- ): [number[], number[]][] {
698
- let selected_hull: Vec2[] = []
699
- let selected_coord_to_idx: SvelteMap<string, number> | null = null
700
- let selected_hull_area = -1
701
-
702
- for (const drop of [0, 1, 2]) {
703
- const axes = [0, 1, 2].filter((ax) => ax !== drop)
704
-
705
- // Skip this projection if points collapse to a line (near-zero range in
706
- // either projected axis). This avoids spurious edges from edge-on views.
707
- let min0 = Infinity, max0 = -Infinity, min1 = Infinity, max1 = -Infinity
708
- for (const pt of pts) {
709
- const v0 = pt[axes[0]], v1 = pt[axes[1]]
710
- if (v0 < min0) min0 = v0
711
- if (v0 > max0) max0 = v0
712
- if (v1 < min1) min1 = v1
713
- if (v1 > max1) max1 = v1
714
- }
715
- const range0 = max0 - min0, range1 = max1 - min1
716
- const max_2d_range = Math.max(range0, range1)
717
- if (max_2d_range < 1e-6 || Math.min(range0, range1) < max_2d_range * 0.01) {
718
- continue
719
- }
720
-
721
- // Build coordinate lookup for this projection
722
- const coord_to_idx = new SvelteMap<string, number>()
723
- const pts_2d: Vec2[] = []
724
- for (let idx = 0; idx < pts.length; idx++) {
725
- const p2 = [pts[idx][axes[0]], pts[idx][axes[1]]] as Vec2
726
- pts_2d.push(p2)
727
- const key = `${p2[0].toFixed(6)},${p2[1].toFixed(6)}`
728
- if (!coord_to_idx.has(key)) coord_to_idx.set(key, idx)
729
- }
730
-
731
- const hull = convex_hull_2d(pts_2d)
732
- if (hull.length < 3) continue
733
- const hull_area = polygon_area_2d(hull)
734
- if (hull_area <= selected_hull_area) continue
735
- selected_hull = hull
736
- selected_coord_to_idx = coord_to_idx
737
- selected_hull_area = hull_area
738
- }
739
-
740
- if (!selected_coord_to_idx || selected_hull.length < 3) return []
741
-
742
- const edges: [number[], number[]][] = []
743
- for (let idx = 0; idx < selected_hull.length; idx++) {
744
- const point_a = selected_hull[idx]
745
- const point_b = selected_hull[(idx + 1) % selected_hull.length]
746
- const point_a_idx = selected_coord_to_idx.get(
747
- `${point_a[0].toFixed(6)},${point_a[1].toFixed(6)}`,
748
- )
749
- const point_b_idx = selected_coord_to_idx.get(
750
- `${point_b[0].toFixed(6)},${point_b[1].toFixed(6)}`,
751
- )
752
- if (
753
- point_a_idx == null || point_b_idx == null || point_a_idx >= pts.length ||
754
- point_b_idx >= pts.length
755
- ) {
756
- console.warn(`get_2d_hull_edges: invalid edge`, {
757
- point_a,
758
- point_b,
759
- point_a_idx,
760
- point_b_idx,
761
- })
762
- continue
763
- }
764
- edges.push([pts[point_a_idx], pts[point_b_idx]])
765
- }
766
-
767
- return edges
768
- }
769
-
770
- // Build globally deduplicated edge geometry for domain boundaries using
771
- // 3D convex hull crease edges (not 2D projected hull).
772
- const edge_geometry = $derived.by(() => {
773
- if (is_projection_mode) {
774
- const all_points = render_domains
775
- .filter((domain) => !domain.is_draw_formula)
776
- .flatMap((domain) => domain.points_3d)
777
- const unique_points = dedup_3d(all_points)
778
- if (unique_points.length >= 4) {
779
- try {
780
- const hull_vectors = unique_points.map((point) => to_vec3(point))
781
- const hull_geometry = new ConvexGeometry(hull_vectors)
782
- const hull_edges = new THREE.EdgesGeometry(hull_geometry)
783
- hull_geometry.dispose()
784
- return hull_edges
785
- } catch {
786
- // Fall back to per-domain edges below.
787
- }
788
- }
789
- }
790
-
791
- const seen = new SvelteSet<string>()
792
- const positions: number[] = []
793
- for (const domain of render_domains) {
794
- if (domain.is_draw_formula) continue
795
- // Compute edges in swizzled (Three.js) coords since ConvexGeometry works there
796
- const swizzled = domain.points_3d.map((point) => to_render_xyz(point))
797
- for (const [pa, pb] of get_domain_edges(swizzled)) {
798
- const ka = pa.map((coord) => coord.toFixed(4)).join(`,`)
799
- const kb = pb.map((coord) => coord.toFixed(4)).join(`,`)
800
- const key = ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`
801
- if (seen.has(key)) continue
802
- seen.add(key)
803
- positions.push(pa[0], pa[1], pa[2], pb[0], pb[1], pb[2])
804
- }
805
- }
806
- const geom = new THREE.BufferGeometry()
807
- geom.setAttribute(`position`, new THREE.Float32BufferAttribute(positions, 3))
808
- return geom
809
- })
810
-
811
- // Build a single opaque convex hull mesh from ALL domain vertices for depth
812
- // occlusion. This seamless surface writes to the depth buffer, hiding wireframe
813
- // edges on the back side. Using all vertices together avoids gaps between domains.
814
- const occlusion_hull_geometry = $derived.by((): THREE.BufferGeometry | null => {
815
- try {
816
- const all_points: number[][] = []
817
- for (const domain of render_domains) {
818
- if (domain.is_draw_formula) continue
819
- all_points.push(...domain.points_3d)
820
- }
821
- const unique_points = dedup_3d(all_points)
822
- if (unique_points.length < 4) return null
823
- const vectors = unique_points.map((point) => to_vec3(point))
824
- return merge_coplanar_geometry(new ConvexGeometry(vectors))
825
- } catch {
826
- return null
827
- }
828
- })
829
-
830
- // Non-indexed hull geometry with artificial closing faces removed.
831
- // The convex hull includes faces that close the diagram at the lower axis
832
- // limits — flat walls and diagonal closing triangles. These are artificial
833
- // (they depend on how far we extend the axes) and clutter the view.
834
- // We detect them via their outward-pointing face normal: closing faces have
835
- // normals pointing entirely toward the negative octant (all components ≤ 0),
836
- // while meaningful domain boundaries always have at least one positive
837
- // normal component (pointing toward 0 eV / the elemental reference).
838
- const hull_base_geometry = $derived.by((): THREE.BufferGeometry | null => {
839
- if (!occlusion_hull_geometry) return null
840
- const src = occlusion_hull_geometry.index
841
- ? occlusion_hull_geometry.toNonIndexed()
842
- : occlusion_hull_geometry.clone()
843
- const pos = src.getAttribute(`position`)
844
- const n_verts = pos.count
845
- const n_faces = n_verts / 3
846
- // Hull centroid for orienting face normals outward
847
- let hx = 0, hy = 0, hz = 0
848
- for (let vert_idx = 0; vert_idx < n_verts; vert_idx++) {
849
- hx += pos.getX(vert_idx)
850
- hy += pos.getY(vert_idx)
851
- hz += pos.getZ(vert_idx)
852
- }
853
- hx /= n_verts
854
- hy /= n_verts
855
- hz /= n_verts
856
- const kept: number[] = []
857
- for (let face_idx = 0; face_idx < n_faces; face_idx++) {
858
- const base = face_idx * 3
859
- const va: Vec3 = [pos.getX(base), pos.getY(base), pos.getZ(base)]
860
- const vb: Vec3 = [pos.getX(base + 1), pos.getY(base + 1), pos.getZ(base + 1)]
861
- const vc: Vec3 = [pos.getX(base + 2), pos.getY(base + 2), pos.getZ(base + 2)]
862
- // Face normal via cross product of two edges
863
- let normal = cross_3d(
864
- [vb[0] - va[0], vb[1] - va[1], vb[2] - va[2]],
865
- [vc[0] - va[0], vc[1] - va[1], vc[2] - va[2]],
866
- )
867
- // Orient outward (away from hull centroid)
868
- const dx = (va[0] + vb[0] + vc[0]) / 3 - hx
869
- const dy = (va[1] + vb[1] + vc[1]) / 3 - hy
870
- const dz = (va[2] + vb[2] + vc[2]) / 3 - hz
871
- if (normal[0] * dx + normal[1] * dy + normal[2] * dz < 0) {
872
- normal = [-normal[0], -normal[1], -normal[2]]
873
- }
874
- // Closing faces point entirely toward negative octant (all ≤ 0).
875
- // Meaningful domain faces always have at least one positive component.
876
- if (normal[0] <= 0 && normal[1] <= 0 && normal[2] <= 0) continue
877
- kept.push(...va, ...vb, ...vc)
878
- }
879
- // Re-merge coplanar faces after the filter — the closing-face removal
880
- // can expose new coplanar adjacencies or leave fragments that should be
881
- // merged into cleaner fan triangulations.
882
- const merged = merge_coplanar_triangles(new Float32Array(kept))
883
- const geom = new THREE.BufferGeometry()
884
- geom.setAttribute(`position`, new THREE.Float32BufferAttribute(merged, 3))
885
- const colors = new Float32Array(merged.length).fill(0.965)
886
- geom.setAttribute(`color`, new THREE.Float32BufferAttribute(colors, 3))
887
- return geom
888
- })
889
-
890
- // Per-face domain assignment (stable — only changes when geometry or domains change).
891
- // Uses actual vertex centroid (mean of points_3d) for robust nearest-face matching.
892
- const face_domain_map = $derived.by((): string[] => {
893
- if (!hull_base_geometry) return []
894
- const pos = hull_base_geometry.getAttribute(`position`)
895
- const n_faces = pos.count / 3
896
-
897
- // Domain vertex centroids in render coords (swizzled + axis stretch), matching hull_base_geometry.
898
- const centroids = render_domains
899
- .filter((domain) => !domain.is_draw_formula && domain.points_3d.length > 0)
900
- .map((domain) => {
901
- let sx = 0, sy = 0, sz = 0
902
- for (const pt of domain.points_3d) {
903
- const [x_val, y_val, z_val] = to_render_xyz(pt)
904
- sx += x_val
905
- sy += y_val
906
- sz += z_val
907
- }
908
- const n_points = domain.points_3d.length
909
- return { formula: domain.formula, cx: sx / n_points, cy: sy / n_points, cz: sz / n_points }
910
- })
911
-
912
- // Assign each face to the nearest domain centroid
913
- const result: string[] = []
914
- for (let face_idx = 0; face_idx < n_faces; face_idx++) {
915
- const base = face_idx * 3
916
- const fcx = (pos.getX(base) + pos.getX(base + 1) + pos.getX(base + 2)) / 3
917
- const fcy = (pos.getY(base) + pos.getY(base + 1) + pos.getY(base + 2)) / 3
918
- const fcz = (pos.getZ(base) + pos.getZ(base + 1) + pos.getZ(base + 2)) / 3
919
- let best_formula = ``
920
- let best_dist = Infinity
921
- for (const dc of centroids) {
922
- const dist = (fcx - dc.cx) ** 2 + (fcy - dc.cy) ** 2 + (fcz - dc.cz) ** 2
923
- if (dist < best_dist) {
924
- best_dist = dist
925
- best_formula = dc.formula
926
- }
927
- }
928
- result.push(best_formula)
929
- }
930
-
931
- // Unify coplanar adjacent faces to the majority domain so that fan
932
- // triangulation edges within a single hull face don't create visible
933
- // color boundaries. Build adjacency via shared edge keys, group
934
- // coplanar neighbors, then assign each group to its most-common domain.
935
- if (n_faces > 1) {
936
- const tol = 1e-3
937
- const round = (v: number): number => Math.round(v / tol)
938
- const vkey = (vert_idx: number): string =>
939
- `${round(pos.getX(vert_idx))},${round(pos.getY(vert_idx))},${
940
- round(pos.getZ(vert_idx))
941
- }`
942
- // Compute face normals
943
- const normals: Vec3[] = []
944
- for (let face_idx = 0; face_idx < n_faces; face_idx++) {
945
- const base = face_idx * 3
946
- const e1: Vec3 = [
947
- pos.getX(base + 1) - pos.getX(base),
948
- pos.getY(base + 1) - pos.getY(base),
949
- pos.getZ(base + 1) - pos.getZ(base),
950
- ]
951
- const e2: Vec3 = [
952
- pos.getX(base + 2) - pos.getX(base),
953
- pos.getY(base + 2) - pos.getY(base),
954
- pos.getZ(base + 2) - pos.getZ(base),
955
- ]
956
- normals.push(normalize_vec3(cross_3d(e1, e2)))
957
- }
958
- // Build edge → face adjacency
959
- const edge_faces = new SvelteMap<string, number[]>()
960
- for (let face_idx = 0; face_idx < n_faces; face_idx++) {
961
- const base = face_idx * 3
962
- const keys = [vkey(base), vkey(base + 1), vkey(base + 2)]
963
- for (
964
- const ek of [
965
- edge_key(keys[0], keys[1]),
966
- edge_key(keys[1], keys[2]),
967
- edge_key(keys[0], keys[2]),
968
- ]
969
- ) {
970
- const list = edge_faces.get(ek)
971
- if (list) list.push(face_idx)
972
- else edge_faces.set(ek, [face_idx])
973
- }
974
- }
975
- // Union-find for coplanar adjacent faces
976
- const parent = Array.from({ length: n_faces }, (_, idx) => idx)
977
- const find = (x: number): number => {
978
- while (parent[x] !== x) {
979
- parent[x] = parent[parent[x]]
980
- x = parent[x]
981
- }
982
- return x
983
- }
984
- const union = (a_idx: number, b_idx: number): void => {
985
- const ra = find(a_idx), rb = find(b_idx)
986
- if (ra !== rb) parent[ra] = rb
987
- }
988
- for (const pair of edge_faces.values()) {
989
- if (pair.length !== 2) continue
990
- const [fa, fb] = pair
991
- const na = normals[fa], nb = normals[fb]
992
- if (Math.abs(na[0] * nb[0] + na[1] * nb[1] + na[2] * nb[2]) > 1 - tol) {
993
- union(fa, fb)
994
- }
995
- }
996
- // Assign majority domain to each coplanar group
997
- const groups = new SvelteMap<number, number[]>()
998
- for (let face_idx = 0; face_idx < n_faces; face_idx++) {
999
- const root = find(face_idx)
1000
- const grp = groups.get(root)
1001
- if (grp) grp.push(face_idx)
1002
- else groups.set(root, [face_idx])
1003
- }
1004
- for (const members of groups.values()) {
1005
- if (members.length < 2) continue
1006
- // Find most common domain in this group
1007
- const counts = new SvelteMap<string, number>()
1008
- for (const member_idx of members) {
1009
- counts.set(result[member_idx], (counts.get(result[member_idx]) ?? 0) + 1)
1010
- }
1011
- let majority = result[members[0]]
1012
- let max_count = 0
1013
- for (const [formula, count] of counts) {
1014
- if (count > max_count) {
1015
- max_count = count
1016
- majority = formula
1017
- }
1018
- }
1019
- for (const member_idx of members) result[member_idx] = majority
1020
- }
1021
- }
1022
-
1023
- return result
1024
- })
1025
-
1026
- // Reactive color fill: creates a cloned geometry with vertex colors applied.
1027
- // Only runs when color_mode or domain_colors change — no mutation of hull_base_geometry.
1028
- const colored_hull_geometry = $derived.by((): THREE.BufferGeometry | null => {
1029
- const mapping = face_domain_map
1030
- if (!hull_base_geometry || mapping.length === 0) return hull_base_geometry
1031
-
1032
- const geom = hull_base_geometry.clone()
1033
- const color_attr = geom.getAttribute(`color`) as THREE.BufferAttribute
1034
- const use_colors = color_mode !== `none` && domain_colors.size > 0
1035
- const fb = use_colors
1036
- ? [0.91, 0.91, 0.91] // #e8e8e8
1037
- : [0.965, 0.965, 0.965] // #f6f6f6
1038
-
1039
- // Cache parsed RGB per formula to avoid redundant THREE.Color allocations
1040
- const rgb_cache = new SvelteMap<string, Vec3>()
1041
- for (const [formula, hex] of domain_colors) {
1042
- const clr = new THREE.Color(hex)
1043
- rgb_cache.set(formula, [clr.r, clr.g, clr.b])
1044
- }
1045
-
1046
- for (let face_idx = 0; face_idx < mapping.length; face_idx++) {
1047
- const rgb = use_colors ? rgb_cache.get(mapping[face_idx]) : null
1048
- const [red, green, blue] = rgb ?? fb
1049
- const base = face_idx * 3
1050
- for (let vert_idx = 0; vert_idx < 3; vert_idx++) {
1051
- color_attr.setXYZ(base + vert_idx, red, green, blue)
1052
- }
1053
- }
1054
- color_attr.needsUpdate = true
1055
- return geom
1056
- })
1057
-
1058
- const visible_domain_labels = $derived.by(() => {
1059
- if (!hull_base_geometry || face_domain_map.length === 0) {
1060
- return render_domains.map((domain) => ({
1061
- formula: domain.formula,
1062
- position: swiz(domain.ann_loc[0], domain.ann_loc[1], domain.ann_loc[2]),
1063
- label_font_size: domain.label_font_size,
1064
- }))
1065
- }
1066
-
1067
- const pos = hull_base_geometry.getAttribute(`position`)
1068
- const pinned_labels = render_domains
1069
- .filter((domain) => domain.is_draw_formula)
1070
- .map((domain) => ({
1071
- formula: domain.formula,
1072
- position: swiz(domain.ann_loc[0], domain.ann_loc[1], domain.ann_loc[2]),
1073
- label_font_size: domain.label_font_size,
1074
- }))
1075
- const font_size_by_formula = new SvelteMap(
1076
- render_domains.map((domain) => [domain.formula, domain.label_font_size]),
1077
- )
1078
- return get_visible_domain_labels(
1079
- pos.array,
1080
- face_domain_map,
1081
- font_size_by_formula,
1082
- pinned_labels,
1083
- )
1084
- })
1085
-
1086
- $effect(() => {
1087
- const geom = hull_base_geometry
1088
- return () => dispose_geometry(geom)
1089
- })
1090
-
1091
- $effect(() => {
1092
- const geom = colored_hull_geometry
1093
- // Don't dispose if it's the same object as hull_base_geometry (no clone was made)
1094
- if (geom && geom !== hull_base_geometry) return () => dispose_geometry(geom)
1095
- })
1096
-
1097
- // Domains on the outer surface (used by the "Surface" formula overlay quick-select).
1098
- const surface_formulas = $derived.by((): SvelteSet<string> => {
1099
- const on_surface = new SvelteSet<string>()
1100
- if (!occlusion_hull_geometry) {
1101
- for (const domain of render_domains) on_surface.add(domain.formula)
1102
- return on_surface
1103
- }
1104
- // Raycast from each domain's centroid outward -- if it hits the hull,
1105
- // the centroid is inside (interior domain). Use multiple ray directions
1106
- // and count: if most hit, the point is interior.
1107
- const raycaster = new THREE.Raycaster()
1108
- const hull_mesh = new THREE.Mesh(occlusion_hull_geometry)
1109
- const directions = [
1110
- new THREE.Vector3(1, 0, 0),
1111
- new THREE.Vector3(0, 1, 0),
1112
- new THREE.Vector3(0, 0, 1),
1113
- new THREE.Vector3(-1, 0, 0),
1114
- new THREE.Vector3(0, -1, 0),
1115
- new THREE.Vector3(0, 0, -1),
1116
- ]
1117
- for (const domain of render_domains) {
1118
- if (domain.is_draw_formula) {
1119
- on_surface.add(domain.formula)
1120
- continue
1121
- }
1122
- const origin = to_vec3(domain.ann_loc)
1123
- // Count how many rays hit the hull from the centroid
1124
- let hits = 0
1125
- for (const dir of directions) {
1126
- raycaster.set(origin, dir)
1127
- if (raycaster.intersectObject(hull_mesh).length > 0) hits++
1128
- }
1129
- // If fewer than 4 of 6 rays hit, centroid is on or near the surface
1130
- if (hits < 4) on_surface.add(domain.formula)
1131
- }
1132
- return on_surface
1133
- })
1134
-
1135
- // Deduplicate 3D points within tolerance (reuses compute.ts dedup_points)
1136
- const dedup_3d = (pts: number[][], tol: number = 1e-4): number[][] =>
1137
- dedup_points(pts, tol).unique
1138
-
1139
- const controls_series = $derived<DataSeries3D[]>([
1140
- {
1141
- x: render_domains.flatMap((domain) =>
1142
- domain.points_3d.map((point) => point[1])
1143
- ),
1144
- y: render_domains.flatMap((domain) =>
1145
- domain.points_3d.map((point) => point[2])
1146
- ),
1147
- z: render_domains.flatMap((domain) =>
1148
- domain.points_3d.map((point) => point[0])
1149
- ),
1150
- label: `domains`,
1151
- },
1152
- ])
1153
-
1154
- // Build formula overlay edge geometries (per formula, colored) using crease edges
1155
- const formula_edge_data = $derived.by(() => {
1156
- if (!draw_formula_lines || formulas_to_draw.length === 0) return []
1157
- const result: { geometry: THREE.BufferGeometry; color: string }[] = []
1158
- for (const domain of render_domains) {
1159
- if (!domain.is_draw_formula) continue
1160
- const color_idx = formulas_to_draw.indexOf(domain.formula) %
1161
- formula_colors.length
1162
- const swizzled = domain.points_3d.map((point) => to_render_xyz(point))
1163
- const positions: number[] = []
1164
- for (const [pa, pb] of get_domain_edges(swizzled)) {
1165
- positions.push(pa[0], pa[1], pa[2], pb[0], pb[1], pb[2])
1166
- }
1167
- const geom = new THREE.BufferGeometry()
1168
- geom.setAttribute(`position`, new THREE.Float32BufferAttribute(positions, 3))
1169
- result.push({ geometry: geom, color: formula_colors[color_idx] })
1170
- }
1171
- return result
1172
- })
1173
-
1174
- // Build formula overlay mesh geometries (convex hull surface)
1175
- const formula_mesh_data = $derived.by(() => {
1176
- const result: { geometry: THREE.BufferGeometry; color: string }[] = []
1177
- if (!draw_formula_meshes) return result
1178
- for (const domain of render_domains) {
1179
- if (!domain.is_draw_formula || domain.points_3d.length < 4) continue
1180
- const color_idx = formulas_to_draw.indexOf(domain.formula) %
1181
- formula_colors.length
1182
- const unique = dedup_3d(domain.points_3d)
1183
- if (unique.length < 4) continue
1184
- const vectors = unique.map((pt) => to_vec3(pt))
1185
- try {
1186
- const geom = merge_coplanar_geometry(new ConvexGeometry(vectors))
1187
- result.push({ geometry: geom, color: formula_colors[color_idx] })
1188
- } catch {
1189
- // Degenerate hull, skip
1190
- }
1191
- }
1192
- return result
1193
- })
1194
-
1195
- function get_touches_limits(
1196
- points_3d: number[][],
1197
- lims: [number, number][],
1198
- ): string[] {
1199
- const limit_tol = 1e-3
1200
- const touches_limits: string[] = []
1201
- for (
1202
- let axis_idx = 0;
1203
- axis_idx < Math.min(plot_elements.length, lims.length);
1204
- axis_idx++
1205
- ) {
1206
- const [axis_min, axis_max] = lims[axis_idx]
1207
- const axis_name = plot_elements[axis_idx] ?? `axis_${axis_idx}`
1208
- const touches_min = points_3d.some((point) =>
1209
- Math.abs(point[axis_idx] - axis_min) < limit_tol
1210
- )
1211
- const touches_max = points_3d.some((point) =>
1212
- Math.abs(point[axis_idx] - axis_max) < limit_tol
1213
- )
1214
- if (touches_min) touches_limits.push(`${axis_name} lower bound`)
1215
- if (touches_max) touches_limits.push(`${axis_name} upper bound`)
1216
- }
1217
- return touches_limits
1218
- }
1219
-
1220
- // Post-process ConvexGeometry to merge coplanar triangles, eliminating
1221
- // internal diagonal edges across flat faces of the convex hull.
1222
- function merge_coplanar_geometry(geom: THREE.BufferGeometry): THREE.BufferGeometry {
1223
- const non_indexed = geom.index ? geom.toNonIndexed() : geom
1224
- const pos = non_indexed.getAttribute(`position`)
1225
- const merged = merge_coplanar_triangles(pos.array as Float32Array)
1226
- const result = new THREE.BufferGeometry()
1227
- result.setAttribute(`position`, new THREE.Float32BufferAttribute(merged, 3))
1228
- result.computeVertexNormals()
1229
- // Dispose intermediate geometry from toNonIndexed() (avoid double-dispose if same object)
1230
- if (non_indexed !== geom) non_indexed.dispose()
1231
- // Callers always pass a freshly created ConvexGeometry, so we own it
1232
- geom.dispose()
1233
- return result
1234
- }
1235
-
1236
- function create_hover_geometry(
1237
- points_3d: number[][],
1238
- ): { geometry: THREE.BufferGeometry; n_vertices: number } | null {
1239
- const unique_points = dedup_3d(points_3d)
1240
- if (unique_points.length < 3) return null
1241
- // For exactly 3 unique points (planar/degenerate domain), create a triangle
1242
- // geometry directly since ConvexGeometry requires 4+ points for a 3D hull
1243
- if (unique_points.length === 3) {
1244
- const geom = new THREE.BufferGeometry()
1245
- const vectors = unique_points.map((pt) => to_vec3(pt))
1246
- const verts = new Float32Array(vectors.flatMap((vec) => [vec.x, vec.y, vec.z]))
1247
- geom.setAttribute(`position`, new THREE.Float32BufferAttribute(verts, 3))
1248
- geom.setIndex([0, 1, 2, 2, 1, 0]) // both winding orders for double-sided pick
1249
- geom.computeVertexNormals()
1250
- return { geometry: geom, n_vertices: 3 }
1251
- }
1252
- try {
1253
- return {
1254
- geometry: merge_coplanar_geometry(
1255
- new ConvexGeometry(unique_points.map((point) => to_vec3(point))),
1256
- ),
1257
- n_vertices: unique_points.length,
1258
- }
1259
- } catch {
1260
- return null
1261
- }
1262
- }
1263
-
1264
- // Domain adjacency: two domains are neighbors if they share any vertex (within tolerance)
1265
- const domain_neighbors = $derived.by((): SvelteMap<string, string[]> => {
1266
- const tol = 1e-4
1267
- const vertex_owners = new SvelteMap<string, string[]>()
1268
- for (const domain of render_domains) {
1269
- for (const pt of domain.points_3d) {
1270
- const key = pt.map((val) => (Math.round(val / tol) * tol).toFixed(4)).join(
1271
- `,`,
1272
- )
1273
- const owners = vertex_owners.get(key)
1274
- if (owners) {
1275
- if (!owners.includes(domain.formula)) owners.push(domain.formula)
1276
- } else vertex_owners.set(key, [domain.formula])
1277
- }
1278
- }
1279
- const neighbors = new SvelteMap<string, SvelteSet<string>>()
1280
- for (const domain of render_domains) {
1281
- neighbors.set(domain.formula, new SvelteSet())
1282
- }
1283
- for (const owners of vertex_owners.values()) {
1284
- if (owners.length < 2) continue
1285
- for (let idx = 0; idx < owners.length; idx++) {
1286
- for (let jdx = idx + 1; jdx < owners.length; jdx++) {
1287
- neighbors.get(owners[idx])?.add(owners[jdx])
1288
- neighbors.get(owners[jdx])?.add(owners[idx])
1289
- }
1290
- }
1291
- }
1292
- const result = new SvelteMap<string, string[]>()
1293
- for (const [formula, set] of neighbors) result.set(formula, [...set].sort())
1294
- return result
1295
- })
1296
-
1297
- const hover_mesh_data = $derived.by((): HoverMeshData[] => {
1298
- if (!diagram_data) return []
1299
- const result: HoverMeshData[] = []
1300
- const lims = diagram_data.lims
1301
- const energy_stats_by_formula = entry_energy_stats_by_formula
1302
-
1303
- for (const domain of render_domains) {
1304
- if (domain.points_3d.length < 3) continue
1305
- const hover_geometry = create_hover_geometry(domain.points_3d)
1306
- if (!hover_geometry) continue
1307
- const { geometry, n_vertices } = hover_geometry
1308
-
1309
- const swizzled_points = domain.points_3d.map((point) => to_render_xyz(point))
1310
- const edge_count = get_domain_edges(swizzled_points).length
1311
- const axis_ranges = build_axis_ranges(domain.points_3d, plot_elements)
1312
- const touches_limits = get_touches_limits(domain.points_3d, lims)
1313
- const energy_stats = energy_stats_by_formula.get(domain.formula) ?? {
1314
- matching_entry_count: 0,
1315
- min_energy_per_atom: null,
1316
- max_energy_per_atom: null,
1317
- }
1318
-
1319
- const info: ChemPotHoverInfo3D = {
1320
- formula: domain.formula,
1321
- view: `3d`,
1322
- n_vertices,
1323
- n_edges: edge_count,
1324
- n_points: domain.points_3d.length,
1325
- ann_loc: domain.ann_loc,
1326
- axis_ranges,
1327
- touches_limits,
1328
- is_elemental: all_entry_elements.includes(domain.formula),
1329
- is_draw_formula: domain.is_draw_formula,
1330
- matching_entry_count: energy_stats.matching_entry_count,
1331
- min_energy_per_atom: energy_stats.min_energy_per_atom,
1332
- max_energy_per_atom: energy_stats.max_energy_per_atom,
1333
- neighbors: domain_neighbors.get(domain.formula) ?? [],
1334
- }
1335
-
1336
- result.push({
1337
- formula: domain.formula,
1338
- geometry,
1339
- info,
1340
- })
1341
- }
1342
- return result
1343
- })
1344
-
1345
- function dispose_geometry(geometry: THREE.BufferGeometry | null | undefined): void {
1346
- if (!geometry) return
1347
- geometry.dispose()
1348
- }
1349
-
1350
- function dispose_geometries(
1351
- geometries: (THREE.BufferGeometry | null | undefined)[],
1352
- ): void {
1353
- for (const geometry of geometries) dispose_geometry(geometry)
1354
- }
1355
-
1356
- $effect(() => {
1357
- const geometry = edge_geometry
1358
- return () => dispose_geometry(geometry)
1359
- })
1360
-
1361
- $effect(() => {
1362
- const geometry = occlusion_hull_geometry
1363
- return () => dispose_geometry(geometry)
1364
- })
1365
-
1366
- $effect(() => {
1367
- const geometry = bounding_box_geometry
1368
- return () => dispose_geometry(geometry)
1369
- })
1370
-
1371
- $effect(() => {
1372
- const geometries = formula_edge_data.map((data) => data.geometry)
1373
- return () => dispose_geometries(geometries)
1374
- })
1375
-
1376
- $effect(() => {
1377
- const geometries = formula_mesh_data.map((data) => data.geometry)
1378
- return () => dispose_geometries(geometries)
1379
- })
1380
-
1381
- $effect(() => {
1382
- const geometries = hover_mesh_data.map((data) => data.geometry)
1383
- return () => dispose_geometries(geometries)
1384
- })
1385
-
1386
- // === Grid, axes, ticks (matching ScatterPlot3D style) ===
1387
-
1388
- // Bounding box of all data points in DATA coordinates (before swizzle)
1389
- const raw_data_bbox = $derived.by(() => {
1390
- const pts = render_domains.flatMap((domain) => domain.points_3d)
1391
- if (pts.length === 0) return { mins: [0, 0, 0], maxs: [1, 1, 1] }
1392
- const mins = [Infinity, Infinity, Infinity]
1393
- const maxs = [-Infinity, -Infinity, -Infinity]
1394
- for (const pt of pts) {
1395
- for (let dim = 0; dim < 3; dim++) {
1396
- if (pt[dim] < mins[dim]) mins[dim] = pt[dim]
1397
- if (pt[dim] > maxs[dim]) maxs[dim] = pt[dim]
1398
- }
1399
- }
1400
- return { mins, maxs }
1401
- })
1402
-
1403
- // Axis range controls are in swizzled axis order:
1404
- // x-axis control -> data axis 1, y-axis control -> data axis 2, z-axis control -> data axis 0
1405
- const data_bbox = $derived.by(() => {
1406
- const mins = [...raw_data_bbox.mins]
1407
- const maxs = [...raw_data_bbox.maxs]
1408
- const range_by_data_axis: ([number | null, number | null] | undefined)[] = [
1409
- z_axis.range,
1410
- x_axis.range,
1411
- y_axis.range,
1412
- ]
1413
- for (let axis_idx = 0; axis_idx < 3; axis_idx++) {
1414
- const range = range_by_data_axis[axis_idx]
1415
- if (!range) continue
1416
- const [range_min, range_max] = range
1417
- if (range_min !== null) mins[axis_idx] = range_min
1418
- if (range_max !== null) maxs[axis_idx] = range_max
1419
- }
1420
- return { mins, maxs }
1421
- })
1422
-
1423
- // Generate nice tick values for each data axis using D3
1424
- function gen_ticks(min_val: number, max_val: number, count: number = 5): number[] {
1425
- if (!isFinite(min_val) || !isFinite(max_val) || min_val === max_val) {
1426
- return [min_val]
1427
- }
1428
- return scaleLinear().domain([min_val, max_val]).nice().ticks(count)
1429
- }
1430
-
1431
- // Ticks in DATA coordinates for each of the 3 data axes
1432
- const data_ticks = $derived([
1433
- gen_ticks(data_bbox.mins[0], data_bbox.maxs[0]),
1434
- gen_ticks(data_bbox.mins[1], data_bbox.maxs[1]),
1435
- gen_ticks(data_bbox.mins[2], data_bbox.maxs[2]),
1436
- ])
1437
-
1438
- // Niced ranges (from ticks) padded so the grid extends beyond the diagram.
1439
- // For horizontal axes (0,1): pad both sides.
1440
- // For vertical axis (2): use actual data range and round min down to an integer.
1441
- const niced_range = $derived.by(() => {
1442
- return [0, 1, 2].map((axis): Vec2 => {
1443
- const ticks = data_ticks[axis]
1444
- const lo = ticks[0]
1445
- const hi = ticks.at(-1) ?? lo
1446
- const step = ticks.length > 1 ? ticks[1] - ticks[0] : 1
1447
- if (axis === 2) {
1448
- const min_data = data_bbox.mins[2]
1449
- return [Math.floor(min_data), hi]
1450
- }
1451
- return [lo - step, hi + step]
1452
- })
1453
- })
1454
-
1455
- // Helper to create a line geometry from two Vec3 arrays
1456
- function make_line_geom(
1457
- start: Vec3,
1458
- end: Vec3,
1459
- ): THREE.BufferGeometry {
1460
- const geom = new THREE.BufferGeometry()
1461
- geom.setAttribute(
1462
- `position`,
1463
- new THREE.BufferAttribute(new Float32Array([...start, ...end]), 3),
1464
- )
1465
- return geom
1466
- }
1467
-
1468
- // Swizzle a data-coord triple to Three.js coords
1469
- function swiz(d0: number, d1: number, d2: number): Vec3 {
1470
- const [scale_x, scale_y, scale_z] = render_axis_scale
1471
- return [d1 * scale_x, d2 * scale_y, d0 * scale_z] // data[0]→Z, data[1]→X, data[2]→Y
1472
- }
1473
-
1474
- const axis_colors = [`#e74c3c`, `#2ecc71`, `#3498db`] as const
1475
- function chem_axis_label(data_axis: number): string {
1476
- const el = plot_elements[data_axis]
1477
- const prefix = formal_chempots ? `\u0394` : ``
1478
- return `${prefix}\u03BC<sub>${el}</sub> <span class="axis-unit">(eV)</span>`
1479
- }
1480
-
1481
- // Proportional offsets for tick marks and labels, scaled to data extent
1482
- const tick_size = $derived(data_extent * 0.015)
1483
- const tick_label_dist = $derived(data_extent * 0.04)
1484
- const axis_label_dist = $derived(data_extent * 0.02)
1485
-
1486
- // Place axis label just past the outer end of the axis (the end closer to 0).
1487
- // In isometric 3D, the end near 0 projects outward at the front edge of the
1488
- // bounding box, while the negative end projects inward toward the center.
1489
- const outer_end = (range: [number, number]): number =>
1490
- Math.abs(range[0]) <= Math.abs(range[1]) ? range[0] : range[1]
1491
- // Direction from range center toward outer end (to extend the label beyond the grid)
1492
- const outer_direction = (range: [number, number]): number => {
1493
- const end = outer_end(range)
1494
- const mid = (range[0] + range[1]) / 2
1495
- return end >= mid ? 1 : -1
1496
- }
1497
-
1498
- // Grid/axis configuration for each data axis.
1499
- // Axes, ticks, and labels are placed on the backside (far from camera)
1500
- // matching ScatterPlot3DScene's dynamic backside tracking pattern.
1501
- const grid_config = $derived.by(() => {
1502
- const [r0, r1, r2] = niced_range
1503
-
1504
- return [0, 1, 2].map((axis) => {
1505
- const ticks = data_ticks[axis]
1506
- const color = axis_colors[axis]
1507
- const label = axis === 0
1508
- ? (z_axis.label || chem_axis_label(0))
1509
- : axis === 1
1510
- ? (x_axis.label || chem_axis_label(1))
1511
- : (y_axis.label || chem_axis_label(2))
1512
-
1513
- const tick_geoms: THREE.BufferGeometry[] = []
1514
- const grid_geoms: THREE.BufferGeometry[] = []
1515
- const tick_labels: { pos: Vec3; text: string }[] = []
1516
- let line_geom: THREE.BufferGeometry
1517
- let label_pos: Vec3
1518
-
1519
- if (axis === 0) {
1520
- // Data axis 0 (Three.js Z, depth): axis at backside d1 and d2
1521
- const ls = swiz(r0[0], back[1], back[2])
1522
- const le = swiz(r0[1], back[1], back[2])
1523
- line_geom = make_line_geom(ls, le)
1524
- // Axis label past the outer end of the axis (near 0, projects outward)
1525
- label_pos = swiz(
1526
- outer_end(r0) + outer_direction(r0) * axis_label_dist,
1527
- back[1] + out_x * tick_label_dist * 0.5,
1528
- back[2] + out_y * tick_label_dist,
1529
- )
1530
- for (const val of ticks) {
1531
- tick_geoms.push(make_line_geom(
1532
- swiz(val, back[1], back[2]),
1533
- swiz(val, back[1], back[2] + out_y * tick_size),
1534
- ))
1535
- grid_geoms.push(
1536
- make_line_geom(swiz(val, r1[0], back[2]), swiz(val, r1[1], back[2])),
1537
- )
1538
- grid_geoms.push(
1539
- make_line_geom(swiz(val, back[1], r2[0]), swiz(val, back[1], r2[1])),
1540
- )
1541
- tick_labels.push({
1542
- pos: swiz(
1543
- val,
1544
- back[1] + out_x * tick_label_dist * 0.5,
1545
- back[2] + out_y * tick_label_dist,
1546
- ),
1547
- text: format_num(val, `.3~g`),
1548
- })
1549
- }
1550
- } else if (axis === 1) {
1551
- // Data axis 1 (Three.js X, horizontal): axis at backside d0 and d2
1552
- const ls = swiz(back[0], r1[0], back[2])
1553
- const le = swiz(back[0], r1[1], back[2])
1554
- line_geom = make_line_geom(ls, le)
1555
- label_pos = swiz(
1556
- back[0],
1557
- outer_end(r1) + outer_direction(r1) * axis_label_dist,
1558
- back[2] + out_y * tick_label_dist,
1559
- )
1560
- for (const val of ticks) {
1561
- tick_geoms.push(make_line_geom(
1562
- swiz(back[0], val, back[2]),
1563
- swiz(back[0], val, back[2] + out_y * tick_size),
1564
- ))
1565
- grid_geoms.push(
1566
- make_line_geom(swiz(r0[0], val, back[2]), swiz(r0[1], val, back[2])),
1567
- )
1568
- grid_geoms.push(
1569
- make_line_geom(swiz(back[0], val, r2[0]), swiz(back[0], val, r2[1])),
1570
- )
1571
- tick_labels.push({
1572
- pos: swiz(back[0], val, back[2] + out_y * tick_label_dist),
1573
- text: format_num(val, `.3~g`),
1574
- })
1575
- }
1576
- } else {
1577
- // Data axis 2 (Three.js Y, vertical): axis at backside d0 and d1
1578
- const ls = swiz(back[0], back[1], r2[0])
1579
- const le = swiz(back[0], back[1], r2[1])
1580
- line_geom = make_line_geom(ls, le)
1581
- label_pos = swiz(
1582
- back[0],
1583
- back[1] + out_x * tick_label_dist,
1584
- outer_end(r2) + outer_direction(r2) * axis_label_dist,
1585
- )
1586
- for (const val of ticks) {
1587
- tick_geoms.push(make_line_geom(
1588
- swiz(back[0], back[1], val),
1589
- swiz(back[0], back[1] + out_x * tick_size, val),
1590
- ))
1591
- grid_geoms.push(
1592
- make_line_geom(swiz(r0[0], back[1], val), swiz(r0[1], back[1], val)),
1593
- )
1594
- grid_geoms.push(
1595
- make_line_geom(swiz(back[0], r1[0], val), swiz(back[0], r1[1], val)),
1596
- )
1597
- tick_labels.push({
1598
- pos: swiz(back[0], back[1] + out_x * tick_label_dist, val),
1599
- text: format_num(val, `.3~g`),
1600
- })
1601
- }
1602
- }
1603
-
1604
- return { axis, color, label, line_geom, tick_geoms, grid_geoms, tick_labels, label_pos }
1605
- })
1606
- })
1607
-
1608
- let label_occlusion_frame: number | null = null
1609
- let tick_labels_occluded = false
1610
- const has_occluding_domain_labels = $derived(
1611
- label_stable && visible_domain_labels.length > 0,
1612
- )
1613
- const can_update_label_occlusion = $derived(
1614
- mounted &&
1615
- display.show_axis_labels &&
1616
- grid_config.length > 0 &&
1617
- Number.isFinite(zoom_scale) &&
1618
- container_width > 0 &&
1619
- container_height > 0,
1620
- )
1621
-
1622
- function update_label_occlusion(): void {
1623
- if (!wrapper) return
1624
- const tick_labels = Array.from(
1625
- wrapper.querySelectorAll<HTMLElement>(`.axis-tick-label`),
1626
- )
1627
- tick_labels_occluded = false
1628
- for (const tick_label of tick_labels) {
1629
- tick_label.style.visibility = ``
1630
- }
1631
- const domain_rects = Array.from(
1632
- wrapper.querySelectorAll<HTMLElement>(`.domain-label`),
1633
- )
1634
- .filter((domain_label) => {
1635
- const style = getComputedStyle(domain_label)
1636
- return style.display !== `none` && style.visibility !== `hidden`
1637
- })
1638
- .map((domain_label) => pad_rect(domain_label.getBoundingClientRect(), 1))
1639
- if (domain_rects.length === 0) return
1640
-
1641
- for (const tick_label of tick_labels) {
1642
- const style = getComputedStyle(tick_label)
1643
- if (style.display === `none` || style.visibility === `hidden`) continue
1644
- const tick_rect = tick_label.getBoundingClientRect()
1645
- if (domain_rects.some((domain_rect) => rects_overlap(tick_rect, domain_rect))) {
1646
- tick_label.style.visibility = `hidden`
1647
- tick_labels_occluded = true
1648
- }
1649
- }
1650
- }
1651
-
1652
- function schedule_label_occlusion_update(): void {
1653
- if (typeof requestAnimationFrame === `undefined`) return
1654
- if (label_occlusion_frame !== null) cancelAnimationFrame(label_occlusion_frame)
1655
- label_occlusion_frame = requestAnimationFrame(() => {
1656
- label_occlusion_frame = null
1657
- update_label_occlusion()
1658
- })
1659
- }
1660
-
1661
- // Update backside positions when camera crosses axis planes.
1662
- // Only updates when sign changes to avoid triggering geometry recreation every frame.
1663
- function update_backside(): void {
1664
- const cam = orbit_controls_ref?.object?.position
1665
- if (!cam) return
1666
- const [r0, r1, r2] = niced_range
1667
- // swiz: data[0]→Z, data[1]→X, data[2]→Y
1668
- const new_back_0 = cam.z > data_center.z ? r0[0] : r0[1]
1669
- const new_back_1 = cam.x > data_center.x ? r1[0] : r1[1]
1670
- const new_back_2 = cam.y > data_center.y ? r2[0] : r2[1]
1671
- if (back[0] !== new_back_0 || back[1] !== new_back_1 || back[2] !== new_back_2) {
1672
- back = [new_back_0, new_back_1, new_back_2]
1673
- out_x = cam.x > data_center.x ? -1 : 1
1674
- out_y = cam.y > data_center.y ? -1 : 1
1675
- }
1676
- }
1677
-
1678
- function store_camera_view_state(): void {
1679
- // Prime framing baseline on first user interaction so the next geometry
1680
- // change can preserve zoom/center immediately (not only from second change).
1681
- if (last_data_center === null) {
1682
- last_data_center = [data_center.x, data_center.y, data_center.z]
1683
- }
1684
- if (last_data_extent === null) {
1685
- last_data_extent = data_extent
1686
- }
1687
- const controls = orbit_controls_ref
1688
- const controls_camera = controls?.object
1689
- if (controls_camera) {
1690
- camera_position_override = [
1691
- controls_camera.position.x,
1692
- controls_camera.position.y,
1693
- controls_camera.position.z,
1694
- ]
1695
- if (controls_camera instanceof THREE.OrthographicCamera) {
1696
- orthographic_zoom_override = controls_camera.zoom
1697
- }
1698
- }
1699
- const controls_target = controls?.target
1700
- if (controls_target) {
1701
- camera_target_override = [
1702
- controls_target.x,
1703
- controls_target.y,
1704
- controls_target.z,
1705
- ]
1706
- }
1707
- }
1708
-
1709
- // Preserve user framing across temperature-driven geometry changes:
1710
- // shift camera/target with domain center and keep orthographic zoom relative to extent.
1711
- $effect(() => {
1712
- if (camera_position_override && camera_target_override && last_data_center) {
1713
- const [last_x, last_y, last_z] = last_data_center
1714
- const delta_x = data_center.x - last_x
1715
- const delta_y = data_center.y - last_y
1716
- const delta_z = data_center.z - last_z
1717
- if (delta_x !== 0 || delta_y !== 0 || delta_z !== 0) {
1718
- camera_position_override = [
1719
- camera_position_override[0] + delta_x,
1720
- camera_position_override[1] + delta_y,
1721
- camera_position_override[2] + delta_z,
1722
- ]
1723
- camera_target_override = [
1724
- camera_target_override[0] + delta_x,
1725
- camera_target_override[1] + delta_y,
1726
- camera_target_override[2] + delta_z,
1727
- ]
1728
- }
1729
- }
1730
- if (
1731
- orthographic_zoom_override !== null &&
1732
- last_data_extent !== null &&
1733
- last_data_extent > 0 &&
1734
- data_extent > 0
1735
- ) {
1736
- orthographic_zoom_override *= last_data_extent / data_extent
1737
- }
1738
- last_data_center = [data_center.x, data_center.y, data_center.z]
1739
- last_data_extent = data_extent
1740
- })
1741
-
1742
- $effect(() => {
1743
- const controls = orbit_controls_ref
1744
- if (!controls) return
1745
- const on_controls_change = (): void => {
1746
- update_backside()
1747
- store_camera_view_state()
1748
- if (has_occluding_domain_labels) schedule_label_occlusion_update()
1749
- }
1750
- controls.addEventListener(`change`, on_controls_change)
1751
- untrack(() => update_backside())
1752
- controls.update()
1753
- return () => controls.removeEventListener(`change`, on_controls_change)
1754
- })
1755
-
1756
- $effect(() => {
1757
- if (!can_update_label_occlusion) return
1758
- if (!has_occluding_domain_labels && !tick_labels_occluded) return
1759
- schedule_label_occlusion_update()
1760
- })
1761
-
1762
- $effect(() => {
1763
- set_fullscreen_bg(wrapper, fullscreen, `--chempot-3d-bg-fullscreen`)
1764
- })
1765
-
1766
- $effect(() => {
1767
- const grid_geometries = grid_config
1768
- return () => {
1769
- for (const grid_item of grid_geometries) {
1770
- dispose_geometry(grid_item.line_geom)
1771
- for (const tick_geometry of grid_item.tick_geoms) {
1772
- dispose_geometry(tick_geometry)
1773
- }
1774
- for (const line_geometry of grid_item.grid_geoms) {
1775
- dispose_geometry(line_geometry)
1776
- }
1777
- }
1778
- }
1779
- })
1780
-
1781
- const projection_planes = $derived.by(() => {
1782
- const projections = display.projections
1783
- if (!projections) return []
1784
- const [r0, r1, r2] = niced_range
1785
- const s0 = (r0[1] - r0[0]) * (display.projection_scale ?? 0.5)
1786
- const s1 = (r1[1] - r1[0]) * (display.projection_scale ?? 0.5)
1787
- const s2 = (r2[1] - r2[0]) * (display.projection_scale ?? 0.5)
1788
- const planes: {
1789
- key: string
1790
- pos: Vec3
1791
- rot: Vec3
1792
- size: [number, number]
1793
- color: string
1794
- }[] = []
1795
- if (projections.xy) {
1796
- planes.push({
1797
- key: `xy`,
1798
- pos: swiz((r0[0] + r0[1]) / 2, (r1[0] + r1[1]) / 2, back[2]),
1799
- rot: [-Math.PI / 2, 0, 0],
1800
- size: [s1, s0],
1801
- color: `#5dade2`,
1802
- })
1803
- }
1804
- if (projections.xz) {
1805
- planes.push({
1806
- key: `xz`,
1807
- pos: swiz((r0[0] + r0[1]) / 2, back[1], (r2[0] + r2[1]) / 2),
1808
- rot: [0, Math.PI / 2, 0],
1809
- size: [s0, s2],
1810
- color: `#58d68d`,
1811
- })
1812
- }
1813
- if (projections.yz) {
1814
- planes.push({
1815
- key: `yz`,
1816
- pos: swiz(back[0], (r1[0] + r1[1]) / 2, (r2[0] + r2[1]) / 2),
1817
- rot: [0, 0, 0],
1818
- size: [s1, s2],
1819
- color: `#f5b041`,
1820
- })
1821
- }
1822
- return planes
1823
- })
1824
-
1825
- const bounding_box_geometry = $derived.by(() => {
1826
- const [r0, r1, r2] = niced_range
1827
- const vertices = [
1828
- swiz(r0[0], r1[0], r2[0]),
1829
- swiz(r0[1], r1[0], r2[0]),
1830
- swiz(r0[1], r1[1], r2[0]),
1831
- swiz(r0[0], r1[1], r2[0]),
1832
- swiz(r0[0], r1[0], r2[1]),
1833
- swiz(r0[1], r1[0], r2[1]),
1834
- swiz(r0[1], r1[1], r2[1]),
1835
- swiz(r0[0], r1[1], r2[1]),
1836
- ]
1837
- const edges = [
1838
- [0, 1],
1839
- [1, 2],
1840
- [2, 3],
1841
- [3, 0],
1842
- [4, 5],
1843
- [5, 6],
1844
- [6, 7],
1845
- [7, 4],
1846
- [0, 4],
1847
- [1, 5],
1848
- [2, 6],
1849
- [3, 7],
1850
- ]
1851
- const positions: number[] = []
1852
- for (const [start_idx, end_idx] of edges) {
1853
- const start = vertices[start_idx]
1854
- const end = vertices[end_idx]
1855
- positions.push(start[0], start[1], start[2], end[0], end[1], end[2])
1856
- }
1857
- const geom = new THREE.BufferGeometry()
1858
- geom.setAttribute(`position`, new THREE.Float32BufferAttribute(positions, 3))
1859
- return geom
1860
- })
1861
-
1862
- function reset_controls(): void {
1863
- formal_chempots_override = null
1864
- label_stable_override = null
1865
- element_padding_override = null
1866
- default_min_limit_override = null
1867
- draw_formula_meshes_override = null
1868
- draw_formula_lines_override = null
1869
- color_mode_override = null
1870
- color_scale_override = null
1871
- reverse_color_scale_override = null
1872
- projection_elements_override = null
1873
- formulas_to_draw_override = null
1874
- formula_filter_query = ``
1875
- }
1876
-
1877
- function set_projection_axis(axis_idx: number, element: string): void {
1878
- if (!all_entry_elements.includes(element)) return
1879
- const next_projection = [...plot_elements]
1880
- if (next_projection.length !== 3) return
1881
- const current_owner_idx = next_projection.indexOf(element)
1882
- if (current_owner_idx !== -1 && current_owner_idx !== axis_idx) {
1883
- next_projection[current_owner_idx] = next_projection[axis_idx]
1884
- }
1885
- next_projection[axis_idx] = element
1886
- const normalized = normalize_projection_triplet(
1887
- next_projection,
1888
- all_entry_elements,
1889
- )
1890
- if (normalized) projection_elements_override = normalized
1891
- }
1892
-
1893
- function apply_projection_preset(preset_elements: string[]): void {
1894
- const normalized = normalize_projection_triplet(
1895
- preset_elements,
1896
- all_entry_elements,
1897
- )
1898
- if (normalized) projection_elements_override = normalized
1899
- }
1900
-
1901
- function toggle_formula_selection(formula: string): void {
1902
- const selected_formulas = new SvelteSet(formulas_to_draw)
1903
- if (selected_formulas.has(formula)) selected_formulas.delete(formula)
1904
- else selected_formulas.add(formula)
1905
- formulas_to_draw_override = [...selected_formulas]
1906
- }
1907
-
1908
- function select_surface_formulas(): void {
1909
- formulas_to_draw_override = render_domains
1910
- .filter((domain) => surface_formulas.has(domain.formula))
1911
- .map((domain) => domain.formula)
1912
- }
1913
-
1914
- function select_neighbor_formulas(): void {
1915
- if (hover_info?.view !== `3d`) return
1916
- const neighbors = domain_neighbors.get(hover_info.formula) ?? []
1917
- formulas_to_draw_override = [hover_info.formula, ...neighbors]
1918
- }
1919
-
1920
- function download_blob(blob: Blob, filename: string): void {
1921
- const url = URL.createObjectURL(blob)
1922
- const link = document.createElement(`a`)
1923
- link.href = url
1924
- link.download = filename
1925
- link.click()
1926
- URL.revokeObjectURL(url)
1927
- }
1928
-
1929
- let png_dpi = $state(150)
1930
- const export_basename = $derived(`chempot-${plot_elements.join(`-`)}`)
1931
-
1932
- function get_view_settings(): Record<string, unknown> {
1933
- const view_camera_position = orbit_controls_ref?.object?.position
1934
- const view_camera_target = orbit_controls_ref?.target
1935
- return {
1936
- elements: plot_elements,
1937
- camera_projection,
1938
- auto_rotate,
1939
- color_mode,
1940
- color_scale,
1941
- reverse_color_scale,
1942
- camera_position: view_camera_position
1943
- ? [view_camera_position.x, view_camera_position.y, view_camera_position.z]
1944
- : null,
1945
- camera_target: view_camera_target
1946
- ? [view_camera_target.x, view_camera_target.y, view_camera_target.z]
1947
- : null,
1948
- }
1949
- }
1950
-
1951
- interface OverlayTextItem {
1952
- x: number
1953
- y: number
1954
- text: string
1955
- font: string
1956
- font_size: string
1957
- font_family: string
1958
- font_weight: string
1959
- color: string
1960
- }
1961
- function get_overlay_text_items(canvas_rect: DOMRect): OverlayTextItem[] {
1962
- if (!wrapper) return []
1963
- const text_items: OverlayTextItem[] = []
1964
- for (
1965
- const element of wrapper.querySelectorAll(
1966
- `.tick-label, .axis-label, .domain-label`,
1967
- )
1968
- ) {
1969
- const html_element = element as HTMLElement
1970
- const style = getComputedStyle(html_element)
1971
- if (style.display === `none` || style.visibility === `hidden`) continue
1972
- const element_rect = html_element.getBoundingClientRect()
1973
- text_items.push({
1974
- x: element_rect.left + element_rect.width / 2 - canvas_rect.left,
1975
- y: element_rect.top + element_rect.height / 2 - canvas_rect.top,
1976
- text: html_element.textContent ?? ``,
1977
- font: style.font || `${style.fontSize} ${style.fontFamily}`,
1978
- font_size: style.fontSize || `11px`,
1979
- font_family: style.fontFamily || `sans-serif`,
1980
- font_weight: style.fontWeight || `400`,
1981
- color: style.color || `#333`,
1982
- })
1983
- }
1984
- return text_items
1985
- }
1986
-
1987
- function export_png_file(): void {
1988
- if (!wrapper) return
1989
- const gl_canvas = wrapper.querySelector(`canvas`)
1990
- if (!(gl_canvas instanceof HTMLCanvasElement)) return
1991
-
1992
- // Composite WebGL canvas + HTML overlay labels into a single image
1993
- const rect = gl_canvas.getBoundingClientRect()
1994
- const scale = Math.min(png_dpi / 72, 10)
1995
- const out = document.createElement(`canvas`)
1996
- out.width = Math.round(rect.width * scale)
1997
- out.height = Math.round(rect.height * scale)
1998
- const ctx = out.getContext(`2d`)
1999
- if (!ctx) return
2000
- ctx.scale(scale, scale)
2001
-
2002
- // Draw the WebGL canvas as background
2003
- ctx.drawImage(gl_canvas, 0, 0, rect.width, rect.height)
2004
-
2005
- // Draw all HTML overlay text (tick labels, axis labels, domain labels)
2006
- for (const text_item of get_overlay_text_items(rect)) {
2007
- ctx.font = text_item.font
2008
- ctx.fillStyle = text_item.color
2009
- ctx.textAlign = `center`
2010
- ctx.textBaseline = `middle`
2011
- ctx.fillText(text_item.text, text_item.x, text_item.y)
2012
- }
2013
-
2014
- out.toBlob((blob) => {
2015
- if (!blob) return
2016
- download_blob(blob, `${export_basename}.png`)
2017
- }, `image/png`)
2018
- }
2019
-
2020
- function xml_escape(text: string): string {
2021
- return text
2022
- .replaceAll(`&`, `&amp;`)
2023
- .replaceAll(`<`, `&lt;`)
2024
- .replaceAll(`>`, `&gt;`)
2025
- .replaceAll(`"`, `&quot;`)
2026
- .replaceAll(`'`, `&#39;`)
2027
- }
2028
-
2029
- function export_svg_file(): void {
2030
- if (!wrapper) return
2031
- const gl_canvas = wrapper.querySelector(`canvas`)
2032
- if (!(gl_canvas instanceof HTMLCanvasElement)) return
2033
- const canvas_rect = gl_canvas.getBoundingClientRect()
2034
- if (canvas_rect.width === 0 || canvas_rect.height === 0) return
2035
- const png_data_url = gl_canvas.toDataURL(`image/png`)
2036
- const text_nodes = get_overlay_text_items(canvas_rect).map((text_item) =>
2037
- `<text x="${text_item.x.toFixed(2)}" y="${
2038
- text_item.y.toFixed(2)
2039
- }" text-anchor="middle" dominant-baseline="central" fill="${
2040
- xml_escape(text_item.color)
2041
- }" font-size="${xml_escape(text_item.font_size)}" font-family="${
2042
- xml_escape(text_item.font_family)
2043
- }" font-weight="${xml_escape(text_item.font_weight)}">${
2044
- xml_escape(text_item.text)
2045
- }</text>`
2046
- )
2047
- const metadata = xml_escape(JSON.stringify(get_view_settings()))
2048
- const svg = [
2049
- `<?xml version="1.0" encoding="UTF-8"?>`,
2050
- `<svg xmlns="http://www.w3.org/2000/svg" width="${canvas_rect.width}" height="${canvas_rect.height}" viewBox="0 0 ${canvas_rect.width} ${canvas_rect.height}">`,
2051
- `<metadata>${metadata}</metadata>`,
2052
- `<image href="${png_data_url}" x="0" y="0" width="${canvas_rect.width}" height="${canvas_rect.height}" />`,
2053
- ...text_nodes,
2054
- `</svg>`,
2055
- ].join(``)
2056
- download_blob(
2057
- new Blob([svg], { type: `image/svg+xml` }),
2058
- `${export_basename}.svg`,
2059
- )
2060
- }
2061
-
2062
- function export_view_json_file(): void {
2063
- const json_text = JSON.stringify(get_view_settings(), null, 2)
2064
- download_blob(
2065
- new Blob([json_text], { type: `application/json` }),
2066
- `${export_basename}-view.json`,
2067
- )
2068
- }
2069
-
2070
- function export_glb_file(): void {
2071
- const gltf_exporter = new GLTFExporter()
2072
- const export_root = new THREE.Group()
2073
- if (colored_hull_geometry) {
2074
- export_root.add(
2075
- new THREE.Mesh(
2076
- colored_hull_geometry.clone(),
2077
- new THREE.MeshBasicMaterial({
2078
- vertexColors: true,
2079
- transparent: true,
2080
- opacity: color_mode === `none` ? 0.25 : 0.4,
2081
- side: THREE.DoubleSide,
2082
- }),
2083
- ),
2084
- )
2085
- }
2086
- export_root.add(
2087
- new THREE.LineSegments(
2088
- edge_geometry.clone(),
2089
- new THREE.LineBasicMaterial({ color: 0x333333 }),
2090
- ),
2091
- )
2092
- for (const { geometry, color } of formula_mesh_data) {
2093
- export_root.add(
2094
- new THREE.Mesh(
2095
- geometry.clone(),
2096
- new THREE.MeshBasicMaterial({
2097
- color: new THREE.Color(color),
2098
- transparent: true,
2099
- opacity: 0.13,
2100
- side: THREE.DoubleSide,
2101
- }),
2102
- ),
2103
- )
2104
- }
2105
- if (draw_formula_lines) {
2106
- for (const { geometry, color } of formula_edge_data) {
2107
- export_root.add(
2108
- new THREE.LineSegments(
2109
- geometry.clone(),
2110
- new THREE.LineBasicMaterial({ color: new THREE.Color(color) }),
2111
- ),
2112
- )
2113
- }
2114
- }
2115
- gltf_exporter.parse(
2116
- export_root,
2117
- (result) => {
2118
- if (!(result instanceof ArrayBuffer)) return
2119
- download_blob(
2120
- new Blob([result], { type: `model/gltf-binary` }),
2121
- `${export_basename}.glb`,
2122
- )
2123
- },
2124
- (err) => {
2125
- console.error(`Failed to export GLB:`, err)
2126
- },
2127
- { binary: true, onlyVisible: false },
2128
- )
2129
- }
2130
-
2131
- function get_json_string(): string {
2132
- return JSON.stringify(
2133
- {
2134
- elements: diagram_data?.elements ?? [],
2135
- domains: render_domains.map((domain) => ({
2136
- formula: domain.formula,
2137
- points_3d: domain.points_3d,
2138
- })),
2139
- lims: diagram_data?.lims ?? [],
2140
- view: get_view_settings(),
2141
- },
2142
- null,
2143
- 2,
2144
- )
2145
- }
2146
-
2147
- function export_json_file(): void {
2148
- download_blob(
2149
- new Blob([get_json_string()], { type: `application/json` }),
2150
- `${export_basename}.json`,
2151
- )
2152
- }
2153
-
2154
- async function copy_json(): Promise<void> {
2155
- try {
2156
- await navigator.clipboard.writeText(get_json_string())
2157
- copy_status = true
2158
- } catch (err) {
2159
- copy_status = false
2160
- console.error(`Failed to copy JSON to clipboard:`, err)
2161
- }
2162
- if (copy_timeout_id !== null) clearTimeout(copy_timeout_id)
2163
- copy_timeout_id = setTimeout(() => {
2164
- copy_status = false
2165
- copy_timeout_id = null
2166
- }, 1000)
2167
- }
2168
-
2169
- onDestroy(() => {
2170
- if (copy_timeout_id !== null) clearTimeout(copy_timeout_id)
2171
- if (label_occlusion_frame !== null) cancelAnimationFrame(label_occlusion_frame)
2172
- })
2173
-
2174
- let locked_hover_formula = $state<string | null>(null)
2175
- let tooltip_el = $state<HTMLElement>()
2176
-
2177
- const tooltip_pos = $derived.by(() => {
2178
- const pointer = hover_info?.pointer
2179
- if (!pointer) return { x: 4, y: 4 }
2180
- return constrain_tooltip_position(
2181
- pointer.x, pointer.y,
2182
- tooltip_el?.offsetWidth ?? 200,
2183
- tooltip_el?.offsetHeight ?? 100,
2184
- container_width, container_height,
2185
- { offset: 0 },
2186
- )
2187
- })
2188
-
2189
- function set_hover_info(domain_data: HoverMeshData, raw_event: unknown): void {
2190
- hover_info = with_hover_pointer<ChemPotHoverInfo>(
2191
- domain_data.info,
2192
- raw_event,
2193
- wrapper?.getBoundingClientRect() ?? null,
2194
- )
2195
- }
2196
-
2197
- function clear_hover_lock(): void {
2198
- locked_hover_formula = null
2199
- hover_info = null
2200
- }
2201
-
2202
- function handle_phase_hover(domain_data: HoverMeshData, raw_event: unknown): void {
2203
- if (locked_hover_formula && locked_hover_formula !== domain_data.formula) return
2204
- set_hover_info(domain_data, raw_event)
2205
- }
2206
-
2207
- function toggle_phase_lock(domain_data: HoverMeshData, raw_event: unknown): void {
2208
- if (locked_hover_formula === domain_data.formula) {
2209
- clear_hover_lock()
2210
- return
2211
- }
2212
- locked_hover_formula = domain_data.formula
2213
- set_hover_info(domain_data, raw_event)
2214
- }
2215
-
2216
- // Color mode cycling (keyboard shortcut 'c')
2217
- const color_modes: ChemPotColorMode[] = [
2218
- `none`,
2219
- `energy`,
2220
- `formation_energy`,
2221
- `arity`,
2222
- `entries`,
2223
- ]
2224
- function cycle_color_mode(): void {
2225
- const idx = color_modes.indexOf(color_mode)
2226
- color_mode_override = color_modes[(idx + 1) % color_modes.length]
2227
- }
2228
- </script>
2229
-
2230
- <svelte:document
2231
- onfullscreenchange={() => {
2232
- fullscreen = document.fullscreenElement === wrapper
2233
- }}
2234
- />
2235
-
2236
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
2237
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
2238
- <div
2239
- bind:this={wrapper}
2240
- bind:clientWidth={container_width}
2241
- bind:clientHeight={container_height}
2242
- class="chempot-diagram-3d"
2243
- class:fullscreen
2244
- style:width={fullscreen ? `100vw` : `100%`}
2245
- style:height={fullscreen ? `100vh` : `${render_height}px`}
2246
- role="application"
2247
- tabindex="0"
2248
- onkeydown={(event) => {
2249
- if (
2250
- event.target instanceof HTMLInputElement ||
2251
- event.target instanceof HTMLSelectElement
2252
- ) return
2253
- if (event.key === `Escape`) clear_hover_lock()
2254
- else if (event.key === `c`) cycle_color_mode()
2255
- else if (event.key === `f`) toggle_fullscreen(wrapper)
2256
- }}
2257
- onpointerdown={(event) => {
2258
- const target = event.target
2259
- if (
2260
- locked_hover_formula &&
2261
- (target === wrapper || target instanceof HTMLCanvasElement)
2262
- ) {
2263
- clear_hover_lock()
2264
- }
2265
- }}
2266
- >
2267
- <section>
2268
- <DraggablePane
2269
- bind:show={export_pane_open}
2270
- open_icon="Cross"
2271
- closed_icon="Export"
2272
- pane_props={{ class: `chempot-export-pane` }}
2273
- toggle_props={{
2274
- class: `chempot-export-toggle`,
2275
- title: `Export chemical potential diagram`,
2276
- }}
2277
- >
2278
- <h4>Export Image</h4>
2279
- <div class="export-row">
2280
- <label>
2281
- SVG
2282
- <button type="button" onclick={export_svg_file} title="SVG snapshot export">
2283
-
2284
- </button>
2285
- </label>
2286
- <label>
2287
- PNG
2288
- <button type="button" onclick={export_png_file} title="PNG ({png_dpi} DPI)">
2289
-
2290
- </button>
2291
- &nbsp;(DPI: <input
2292
- type="number"
2293
- min={50}
2294
- max={500}
2295
- bind:value={png_dpi}
2296
- title="Export resolution in dots per inch"
2297
- style="margin: 0 0 0 2pt"
2298
- />)
2299
- </label>
2300
- </div>
2301
- <h4>Export Data</h4>
2302
- <div class="export-row">
2303
- <label>
2304
- JSON
2305
- <button type="button" onclick={export_json_file} aria-label="Download JSON">
2306
-
2307
- </button>
2308
- <button
2309
- type="button"
2310
- onclick={copy_json}
2311
- aria-label="Copy JSON to clipboard"
2312
- >
2313
- {copy_status ? `✅` : `📋`}
2314
- </button>
2315
- </label>
2316
- <label>
2317
- View
2318
- <button
2319
- type="button"
2320
- onclick={export_view_json_file}
2321
- aria-label="Download view JSON"
2322
- >
2323
-
2324
- </button>
2325
- </label>
2326
- <label>
2327
- GLB
2328
- <button type="button" onclick={export_glb_file} aria-label="Download GLB">
2329
-
2330
- </button>
2331
- </label>
2332
- </div>
2333
- </DraggablePane>
2334
- <DraggablePane
2335
- bind:show={formula_picker_open}
2336
- open_icon="Cross"
2337
- closed_icon="Filter"
2338
- pane_props={{ class: `chempot-formula-pane` }}
2339
- toggle_props={{
2340
- class: `chempot-formula-toggle`,
2341
- title: `Formula overlays`,
2342
- }}
2343
- >
2344
- <h4>Formula Overlays</h4>
2345
- <div class="overlay-actions">
2346
- <button type="button" onclick={() => formulas_to_draw_override = []}>
2347
- Clear
2348
- </button>
2349
- <button type="button" onclick={select_surface_formulas}>Surface</button>
2350
- <button type="button" onclick={select_neighbor_formulas}>Neighbors</button>
2351
- </div>
2352
- <label class="overlay-search">
2353
- Search:
2354
- <input
2355
- type="text"
2356
- placeholder="Formula filter"
2357
- bind:value={formula_filter_query}
2358
- />
2359
- </label>
2360
- <div class="formula-list">
2361
- {#if filtered_formulas.length === 0}
2362
- <div class="formula-empty">No matching formulas</div>
2363
- {:else}
2364
- {#each filtered_formulas as formula, formula_idx (formula)}
2365
- {@const formula_overlay_idx = formulas_to_draw.indexOf(formula)}
2366
- <label>
2367
- <input
2368
- type="checkbox"
2369
- checked={formulas_to_draw.includes(formula)}
2370
- onchange={() => toggle_formula_selection(formula)}
2371
- />
2372
- <span
2373
- class="formula-color-dot"
2374
- style:background={formula_colors[
2375
- (formula_overlay_idx >= 0 ? formula_overlay_idx : formula_idx) %
2376
- formula_colors.length
2377
- ]}
2378
- ></span>
2379
- {get_electro_neg_formula(formula, true, ``, `.3~s`)}
2380
- </label>
2381
- {/each}
2382
- {/if}
2383
- </div>
2384
- </DraggablePane>
2385
-
2386
- <ScatterPlot3DControls
2387
- bind:show={controls_open}
2388
- bind:x_axis
2389
- bind:y_axis
2390
- bind:z_axis
2391
- bind:display
2392
- bind:camera_projection
2393
- bind:auto_rotate
2394
- series={controls_series}
2395
- toggle_props={{
2396
- class: `chempot-controls-toggle`,
2397
- title: `3D plot controls`,
2398
- }}
2399
- pane_props={{ class: `chempot-controls-pane` }}
2400
- >
2401
- <SettingsSection
2402
- title="ChemPot"
2403
- current_values={{
2404
- formal_chempots,
2405
- label_stable,
2406
- element_padding,
2407
- default_min_limit,
2408
- draw_formula_meshes,
2409
- draw_formula_lines,
2410
- color_mode,
2411
- color_scale,
2412
- reverse_color_scale,
2413
- }}
2414
- on_reset={reset_controls}
2415
- >
2416
- {#if has_multinary_system && plot_elements.length === 3}
2417
- <div class="projection-controls">
2418
- <div class="pane-row">
2419
- <label for="chempot-proj-x">X:</label>
2420
- <select
2421
- id="chempot-proj-x"
2422
- value={plot_elements[0]}
2423
- onchange={(event) => set_projection_axis(0, event.currentTarget.value)}
2424
- >
2425
- {#each all_entry_elements as element_name (element_name)}
2426
- <option value={element_name}>{element_name}</option>
2427
- {/each}
2428
- </select>
2429
- <label for="chempot-proj-y">Y:</label>
2430
- <select
2431
- id="chempot-proj-y"
2432
- value={plot_elements[1]}
2433
- onchange={(event) => set_projection_axis(1, event.currentTarget.value)}
2434
- >
2435
- {#each all_entry_elements as element_name (element_name)}
2436
- <option value={element_name}>{element_name}</option>
2437
- {/each}
2438
- </select>
2439
- <label for="chempot-proj-z">Z:</label>
2440
- <select
2441
- id="chempot-proj-z"
2442
- value={plot_elements[2]}
2443
- onchange={(event) => set_projection_axis(2, event.currentTarget.value)}
2444
- >
2445
- {#each all_entry_elements as element_name (element_name)}
2446
- <option value={element_name}>{element_name}</option>
2447
- {/each}
2448
- </select>
2449
- </div>
2450
- <div class="projection-presets">
2451
- {#each projection_presets as preset_elements (preset_elements.join(`|`))}
2452
- <button
2453
- type="button"
2454
- class:selected={preset_elements.join(`|`) === current_projection_key}
2455
- onclick={() => apply_projection_preset(preset_elements)}
2456
- title="Switch projection"
2457
- >
2458
- {preset_elements.join(`-`)}
2459
- </button>
2460
- {/each}
2461
- </div>
2462
- </div>
2463
- {/if}
2464
- <div class="chempot-checks">
2465
- <label>
2466
- <input
2467
- type="checkbox"
2468
- checked={formal_chempots}
2469
- onchange={() => {
2470
- formal_chempots_override = !formal_chempots
2471
- }}
2472
- /> Formal
2473
- </label>
2474
- <label>
2475
- <input
2476
- type="checkbox"
2477
- checked={label_stable}
2478
- onchange={() => {
2479
- label_stable_override = !label_stable
2480
- }}
2481
- /> Labels
2482
- </label>
2483
- <label>
2484
- <input
2485
- type="checkbox"
2486
- checked={draw_formula_meshes}
2487
- onchange={() => {
2488
- draw_formula_meshes_override = !draw_formula_meshes
2489
- }}
2490
- /> Meshes
2491
- </label>
2492
- <label>
2493
- <input
2494
- type="checkbox"
2495
- checked={draw_formula_lines}
2496
- onchange={() => {
2497
- draw_formula_lines_override = !draw_formula_lines
2498
- }}
2499
- /> Lines
2500
- </label>
2501
- </div>
2502
- <div class="chempot-nums">
2503
- <label>
2504
- Pad (eV)
2505
- <input
2506
- type="number"
2507
- min="0"
2508
- step="0.1"
2509
- value={element_padding}
2510
- oninput={(event) => {
2511
- element_padding_override = Number(event.currentTarget.value)
2512
- }}
2513
- />
2514
- </label>
2515
- <label>
2516
- Min (eV)
2517
- <input
2518
- type="number"
2519
- max="0"
2520
- step="1"
2521
- value={default_min_limit}
2522
- oninput={(event) => {
2523
- default_min_limit_override = Number(
2524
- event.currentTarget.value,
2525
- )
2526
- }}
2527
- />
2528
- </label>
2529
- </div>
2530
- <div class="pane-row">
2531
- <label for="chempot-color-mode">Color:</label>
2532
- <select
2533
- id="chempot-color-mode"
2534
- value={color_mode}
2535
- onchange={(event) => {
2536
- color_mode_override = event.currentTarget
2537
- .value as ChemPotColorMode
2538
- }}
2539
- >
2540
- <option value="none">None</option>
2541
- <option value="energy">Energy/atom</option>
2542
- <option value="formation_energy">Formation energy</option>
2543
- <option value="arity">Element count</option>
2544
- <option value="entries">Entry count</option>
2545
- </select>
2546
- </div>
2547
- {#if color_mode !== `none` && color_mode !== `arity`}
2548
- <div class="pane-row">
2549
- <label for="chempot-color-scale">Scale:</label>
2550
- <select
2551
- id="chempot-color-scale"
2552
- value={color_scale}
2553
- onchange={(event) => {
2554
- color_scale_override = event.currentTarget
2555
- .value as D3InterpolateName
2556
- }}
2557
- >
2558
- <option value="interpolateViridis">Viridis</option>
2559
- <option value="interpolatePlasma">Plasma</option>
2560
- <option value="interpolateInferno">Inferno</option>
2561
- <option value="interpolateMagma">Magma</option>
2562
- <option value="interpolateCividis">Cividis</option>
2563
- <option value="interpolateTurbo">Turbo</option>
2564
- <option value="interpolateRdYlBu">RdYlBu</option>
2565
- <option value="interpolateSpectral">Spectral</option>
2566
- </select>
2567
- <label>
2568
- <input
2569
- type="checkbox"
2570
- checked={reverse_color_scale}
2571
- onchange={() => {
2572
- reverse_color_scale_override = !reverse_color_scale
2573
- }}
2574
- /> Rev
2575
- </label>
2576
- </div>
2577
- {/if}
2578
- </SettingsSection>
2579
- </ScatterPlot3DControls>
2580
-
2581
- <button
2582
- type="button"
2583
- onclick={() => toggle_fullscreen(wrapper)}
2584
- title="{fullscreen ? `Exit` : `Enter`} fullscreen"
2585
- >
2586
- <Icon icon="{fullscreen ? `Exit` : ``}Fullscreen" />
2587
- </button>
2588
- </section>
2589
- {#if show_temperature_slider && temperature !== undefined}
2590
- <TemperatureSlider
2591
- class="chempot-temp-slider"
2592
- {available_temperatures}
2593
- bind:temperature
2594
- />
2595
- {/if}
2596
- <div class="canvas-clip">
2597
- {#if diagram_computing}
2598
- <div class="computing-state">
2599
- <Spinner text="Computing chemical potential domains..." style="--spinner-size: 1.2em" />
2600
- </div>
2601
- {:else if !diagram_data}
2602
- <div class="error-state" role="alert" aria-live="polite">
2603
- <p>Cannot compute chemical potential diagram.</p>
2604
- <p>Need at least 2 elements with elemental reference entries.</p>
2605
- </div>
2606
- {:else if mounted && typeof WebGLRenderingContext !== `undefined`}
2607
- <Canvas
2608
- createRenderer={(cvs) =>
2609
- new THREE.WebGLRenderer({
2610
- canvas: cvs,
2611
- alpha: true,
2612
- antialias: true,
2613
- preserveDrawingBuffer: true,
2614
- })}
2615
- >
2616
- <ChemPotScene3D>
2617
- {#if camera_projection === `orthographic`}
2618
- <!-- Orthographic camera matching pymatgen's projection style -->
2619
- <T.OrthographicCamera
2620
- makeDefault
2621
- position={camera_position}
2622
- zoom={orthographic_zoom}
2623
- near={0.1}
2624
- far={data_extent * 10}
2625
- >
2626
- <extras.OrbitControls
2627
- bind:ref={orbit_controls_ref}
2628
- enableRotate
2629
- enableZoom
2630
- enablePan
2631
- autoRotate={auto_rotate > 0}
2632
- autoRotateSpeed={auto_rotate}
2633
- target={camera_target}
2634
- />
2635
- </T.OrthographicCamera>
2636
- {:else}
2637
- <T.PerspectiveCamera
2638
- makeDefault
2639
- position={camera_position}
2640
- fov={50}
2641
- near={0.1}
2642
- far={data_extent * 10}
2643
- >
2644
- <extras.OrbitControls
2645
- bind:ref={orbit_controls_ref}
2646
- enableRotate
2647
- enableZoom
2648
- enablePan
2649
- autoRotate={auto_rotate > 0}
2650
- autoRotateSpeed={auto_rotate}
2651
- target={camera_target}
2652
- />
2653
- </T.PerspectiveCamera>
2654
- {/if}
2655
-
2656
- <!-- Ambient light for visibility -->
2657
- <T.AmbientLight intensity={0.8} />
2658
- <T.DirectionalLight position={[1, 1, 1]} intensity={0.5} />
2659
-
2660
- <!-- Vertex-colored hull for both plain and colored modes.
2661
- {#key domain_colors} forces Threlte to re-create the mesh whenever
2662
- colors change (covers color_mode, color_scale, and data updates),
2663
- since on-demand rendering won't detect mutated vertex color buffers. -->
2664
- {#if colored_hull_geometry}
2665
- {#key domain_colors}
2666
- <T.Mesh geometry={colored_hull_geometry}>
2667
- <T.MeshBasicMaterial
2668
- vertexColors
2669
- transparent
2670
- opacity={color_mode === `none` ? 0.25 : 0.4}
2671
- side={THREE.DoubleSide}
2672
- polygonOffset
2673
- polygonOffsetFactor={1}
2674
- polygonOffsetUnits={1}
2675
- />
2676
- </T.Mesh>
2677
- {/key}
2678
- {/if}
2679
-
2680
- <!-- Domain boundary edges (wireframe on top of opaque fills) -->
2681
- <T.LineSegments geometry={edge_geometry}>
2682
- <T.LineBasicMaterial color={0x333333} linewidth={1} />
2683
- </T.LineSegments>
2684
-
2685
- <!-- Invisible pick meshes for per-phase hover tooltip -->
2686
- {#each hover_mesh_data as domain_hover (domain_hover.formula)}
2687
- <T.Mesh
2688
- geometry={domain_hover.geometry}
2689
- onpointerenter={(event: unknown) => handle_phase_hover(domain_hover, event)}
2690
- onpointermove={(event: unknown) => handle_phase_hover(domain_hover, event)}
2691
- onpointerdown={(event: unknown) => toggle_phase_lock(domain_hover, event)}
2692
- onpointerleave={() => {
2693
- if (
2694
- !locked_hover_formula &&
2695
- hover_info?.formula === domain_hover.formula
2696
- ) {
2697
- hover_info = null
2698
- }
2699
- }}
2700
- >
2701
- <T.MeshBasicMaterial
2702
- transparent
2703
- opacity={0}
2704
- side={THREE.DoubleSide}
2705
- depthWrite={false}
2706
- />
2707
- </T.Mesh>
2708
- {/each}
2709
-
2710
- <!-- Formula overlay meshes (semi-transparent colored fill) -->
2711
- {#each formula_mesh_data as { geometry, color }, mesh_idx (mesh_idx)}
2712
- <T.Mesh {geometry}>
2713
- <T.MeshBasicMaterial
2714
- color={new THREE.Color(color)}
2715
- transparent
2716
- opacity={0.13}
2717
- side={THREE.DoubleSide}
2718
- depthWrite={false}
2719
- />
2720
- </T.Mesh>
2721
- {/each}
2722
-
2723
- <!-- Formula overlay edges (colored, thicker) -->
2724
- {#if draw_formula_lines}
2725
- {#each formula_edge_data as { geometry, color }, edge_idx (edge_idx)}
2726
- <T.LineSegments {geometry}>
2727
- <T.LineBasicMaterial color={new THREE.Color(color)} linewidth={2} />
2728
- </T.LineSegments>
2729
- {/each}
2730
- {/if}
2731
-
2732
- {#each projection_planes as plane (`${plane.key}-${projection_opacity}`)}
2733
- <T.Mesh position={plane.pos} rotation={plane.rot}>
2734
- <T.PlaneGeometry args={plane.size} />
2735
- <T.MeshBasicMaterial
2736
- color={plane.color}
2737
- opacity={projection_opacity}
2738
- transparent
2739
- side={THREE.DoubleSide}
2740
- depthWrite={false}
2741
- />
2742
- </T.Mesh>
2743
- {/each}
2744
-
2745
- {#if display.show_bounding_box}
2746
- <T.LineSegments geometry={bounding_box_geometry}>
2747
- <T.LineBasicMaterial color="#666" opacity={0.6} transparent />
2748
- </T.LineSegments>
2749
- {/if}
2750
-
2751
- <!-- Axes, ticks, grid lines, and labels -->
2752
- {#each grid_config as gc (gc.axis)}
2753
- {#if display.show_axes}
2754
- <!-- Main axis line -->
2755
- <T.Line geometry={gc.line_geom}>
2756
- <T.LineBasicMaterial color={gc.color} linewidth={2} />
2757
- </T.Line>
2758
- <!-- Tick marks -->
2759
- {#each gc.tick_geoms as tick_geom, tdx (tdx)}
2760
- <T.Line geometry={tick_geom}>
2761
- <T.LineBasicMaterial color={gc.color} />
2762
- </T.Line>
2763
- {/each}
2764
- {/if}
2765
- {#if display.show_grid}
2766
- <!-- Grid lines -->
2767
- {#each gc.grid_geoms as grid_geom, gdx (gdx)}
2768
- <T.Line geometry={grid_geom}>
2769
- <T.LineBasicMaterial color="#888" opacity={0.3} transparent />
2770
- </T.Line>
2771
- {/each}
2772
- {/if}
2773
- {#if display.show_axis_labels}
2774
- <!-- Tick labels (billboarded, always face camera) -->
2775
- {#each gc.tick_labels as tick, tick_idx (tick_idx)}
2776
- <extras.HTML
2777
- position={tick.pos}
2778
- center
2779
- portal={wrapper}
2780
- zIndexRange={[1, 0]}
2781
- >
2782
- <span class="tick-label axis-tick-label">{tick.text}</span>
2783
- </extras.HTML>
2784
- {/each}
2785
- <!-- Axis label -->
2786
- <extras.HTML
2787
- position={gc.label_pos}
2788
- center
2789
- portal={wrapper}
2790
- zIndexRange={[1, 0]}
2791
- >
2792
- <span class="axis-label" style:color={gc.color}>{@html gc.label}</span>
2793
- </extras.HTML>
2794
- {/if}
2795
- {/each}
2796
-
2797
- <!-- Domain labels -->
2798
- {#if label_stable}
2799
- {#each visible_domain_labels as domain (domain.formula)}
2800
- <extras.HTML
2801
- position={domain.position}
2802
- center
2803
- portal={wrapper}
2804
- zIndexRange={[5, 5]}
2805
- >
2806
- <span
2807
- class="domain-label"
2808
- style:font-size="{(domain.label_font_size * zoom_scale).toFixed(1)}px"
2809
- >
2810
- {#each formula_label_segments(domain.formula) as segment}
2811
- <span class:formula-subscript={segment.subscript}>{segment.text}</span>
2812
- {/each}
2813
- </span>
2814
- </extras.HTML>
2815
- {/each}
2816
- {/if}
2817
- </ChemPotScene3D>
2818
- </Canvas>
2819
- <!-- Color bar for continuous modes -->
2820
- {#if color_mode !== `none` && color_mode !== `arity` && color_range}
2821
- {@const color_bar_config = get_chempot_color_bar_config(
2822
- color_scale,
2823
- reverse_color_scale,
2824
- )}
2825
- <ColorBar
2826
- title={color_range.label}
2827
- range={[color_range.min, color_range.max]}
2828
- color_scale_fn={color_bar_config.color_scale_fn}
2829
- color_scale_domain={color_bar_config.color_scale_domain}
2830
- wrapper_style="position: absolute; bottom: 16px; left: 1em; width: 200px; z-index: 10;"
2831
- bar_style="height: 12px;"
2832
- title_style="margin-bottom: 4px;"
2833
- />
2834
- {/if}
2835
- <!-- Categorical legend for arity mode -->
2836
- {#if color_mode === `arity`}
2837
- <div class="arity-legend">
2838
- {#each arity_legend_labels as label, idx (label)}
2839
- <span>
2840
- <span style:background={arity_colors[idx]}></span>
2841
- {label}
2842
- </span>
2843
- {/each}
2844
- </div>
2845
- {/if}
2846
- {/if}
2847
- {#if render_local_tooltip && show_tooltip && hover_info?.view === `3d`}
2848
- <aside
2849
- bind:this={tooltip_el}
2850
- class="phase-tooltip"
2851
- style:left="{tooltip_pos.x}px"
2852
- style:top="{tooltip_pos.y}px"
2853
- >
2854
- <h4>
2855
- {#each formula_label_segments(hover_info.formula) as segment}
2856
- <span class:formula-subscript={segment.subscript}>{segment.text}</span>
2857
- {/each}
2858
- </h4>
2859
- {#if locked_hover_formula === hover_info.formula}
2860
- <p>Pinned · Press Esc to unlock</p>
2861
- {/if}
2862
- <p>
2863
- Vertices: {hover_info.n_vertices} · Edges: {hover_info.n_edges} · Points:
2864
- {hover_info.n_points}
2865
- </p>
2866
- <p>
2867
- Entries: {hover_info.matching_entry_count}
2868
- {#if hover_info.min_energy_per_atom !== null &&
2869
- hover_info.max_energy_per_atom !== null}
2870
- · E/atom: {format_num(hover_info.min_energy_per_atom, `.4~g`)}
2871
- to {format_num(hover_info.max_energy_per_atom, `.4~g`)} eV
2872
- {/if}
2873
- </p>
2874
- {#if tooltip_detail_level === `detailed`}
2875
- <h5>Axis ranges</h5>
2876
- {#each hover_info.axis_ranges as axis_range (axis_range.element)}
2877
- <p>
2878
- {axis_range.element}: {format_num(axis_range.min_val, `.4~g`)} to
2879
- {format_num(axis_range.max_val, `.4~g`)} eV
2880
- </p>
2881
- {/each}
2882
- <p>
2883
- Centroid: ({
2884
- hover_info.ann_loc.map((value) => format_num(value, `.3~g`)).join(
2885
- `, `,
2886
- )
2887
- })
2888
- </p>
2889
- {#if hover_info.touches_limits.length > 0}
2890
- <h5>Touches bounds</h5>
2891
- <p>{hover_info.touches_limits.join(`, `)}</p>
2892
- {/if}
2893
- {/if}
2894
- </aside>
2895
- {/if}
2896
- </div>
2897
- </div>
2898
-
2899
- <style>
2900
- .chempot-diagram-3d {
2901
- position: relative;
2902
- overflow: clip;
2903
- }
2904
- .canvas-clip {
2905
- position: relative;
2906
- overflow: clip;
2907
- width: 100%;
2908
- height: 100%;
2909
- }
2910
- .chempot-diagram-3d:fullscreen {
2911
- background: var(--chempot-3d-bg-fullscreen, var(--bg-color, #fff));
2912
- }
2913
- /* Threlte <extras.HTML portal={wrapper}> appends absolutely-positioned overlay divs
2914
- for 3D labels. pointer-events: none prevents them from blocking raycasting. */
2915
- .chempot-diagram-3d > :global(div[style*='position: absolute'][style*='top: 0']) {
2916
- pointer-events: none !important;
2917
- }
2918
- .chempot-diagram-3d > section {
2919
- position: absolute;
2920
- top: 1ex;
2921
- right: 1ex;
2922
- display: flex;
2923
- gap: 8px;
2924
- z-index: 20;
2925
- opacity: 0;
2926
- transition: opacity 0.25s ease;
2927
- pointer-events: none;
2928
- }
2929
- .chempot-diagram-3d:hover > section,
2930
- .chempot-diagram-3d:focus-within > section,
2931
- .chempot-diagram-3d > section:has(:global(.pane-open)) {
2932
- opacity: 1;
2933
- pointer-events: auto;
2934
- }
2935
- @media (hover: none) {
2936
- .chempot-diagram-3d > section {
2937
- opacity: 1;
2938
- pointer-events: auto;
2939
- }
2940
- }
2941
- .chempot-diagram-3d > section > :global(button),
2942
- .chempot-diagram-3d > section > :global(.pane-toggle) {
2943
- background: transparent;
2944
- border: none;
2945
- padding: 4px;
2946
- cursor: pointer;
2947
- border-radius: 3px;
2948
- color: var(--text-color, currentColor);
2949
- transition: background-color 0.2s;
2950
- display: flex;
2951
- font-size: clamp(0.75em, 1.5cqmin, 1em);
2952
- }
2953
- .chempot-diagram-3d > section > :global(button:hover),
2954
- .chempot-diagram-3d > section > :global(.pane-toggle:hover) {
2955
- background-color: color-mix(in srgb, currentColor 8%, transparent);
2956
- }
2957
- .chempot-diagram-3d :global(.chempot-temp-slider) {
2958
- top: var(--chempot-temp-slider-top, calc(1ex + 108px));
2959
- right: 4px;
2960
- z-index: 11;
2961
- }
2962
- .chempot-diagram-3d :global(.draggable-pane label) {
2963
- display: flex;
2964
- align-items: center;
2965
- gap: 4pt;
2966
- font-size: 0.9em;
2967
- }
2968
- .chempot-diagram-3d :global(.export-row) {
2969
- display: flex;
2970
- flex-wrap: wrap;
2971
- gap: 4pt 10pt;
2972
- margin: 0 0 4pt;
2973
- }
2974
- .chempot-diagram-3d :global(.export-row > label) {
2975
- margin: 0;
2976
- }
2977
- .chempot-diagram-3d :global(.export-row button) {
2978
- width: 1.4em;
2979
- height: 1.4em;
2980
- padding: 0;
2981
- display: inline-flex;
2982
- align-items: center;
2983
- justify-content: center;
2984
- }
2985
- .chempot-diagram-3d :global(.chempot-checks) {
2986
- display: flex;
2987
- flex-wrap: wrap;
2988
- gap: 1ex;
2989
- }
2990
- .chempot-diagram-3d :global(.chempot-nums) {
2991
- display: flex;
2992
- flex-wrap: wrap;
2993
- gap: 1ex;
2994
- margin: 4pt 0;
2995
- }
2996
- .chempot-diagram-3d :global(.projection-controls) {
2997
- margin: 0 0 6pt;
2998
- }
2999
- .chempot-diagram-3d :global(.projection-controls .pane-row) {
3000
- display: grid;
3001
- grid-template-columns:
3002
- auto minmax(4.5em, 1fr) auto minmax(4.5em, 1fr) auto minmax(4.5em, 1fr);
3003
- align-items: center;
3004
- gap: 3pt;
3005
- }
3006
- .chempot-diagram-3d :global(.projection-presets) {
3007
- margin-top: 4pt;
3008
- display: flex;
3009
- flex-wrap: wrap;
3010
- gap: 4pt;
3011
- }
3012
- .chempot-diagram-3d :global(.projection-presets button) {
3013
- border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
3014
- border-radius: 3px;
3015
- padding: 1px 5px;
3016
- background: transparent;
3017
- cursor: pointer;
3018
- font-size: 0.85em;
3019
- color: var(--text-color, currentColor);
3020
- }
3021
- .chempot-diagram-3d :global(.projection-presets button.selected) {
3022
- background: color-mix(in srgb, currentColor 14%, transparent);
3023
- }
3024
- .chempot-diagram-3d :global(.overlay-actions) {
3025
- display: flex;
3026
- gap: 3pt;
3027
- margin: 0 0 4pt;
3028
- }
3029
- .chempot-diagram-3d :global(.overlay-actions button) {
3030
- border: none;
3031
- border-radius: 3px;
3032
- padding: 2px 6px;
3033
- background: color-mix(in srgb, currentColor 10%, transparent);
3034
- cursor: pointer;
3035
- color: var(--text-color, currentColor);
3036
- font-size: 0.85em;
3037
- }
3038
- .chempot-diagram-3d :global(.overlay-search) {
3039
- display: flex;
3040
- align-items: center;
3041
- gap: 4pt;
3042
- margin: 0 0 4pt;
3043
- }
3044
- .chempot-diagram-3d :global(.overlay-search input) {
3045
- width: 100%;
3046
- min-width: 10em;
3047
- }
3048
- .chempot-diagram-3d :global(.formula-list) {
3049
- display: flex;
3050
- flex-wrap: wrap;
3051
- gap: 3pt;
3052
- max-height: min(42vh, 18rem);
3053
- overflow: auto;
3054
- padding: 2pt 0;
3055
- }
3056
- .chempot-diagram-3d :global(.formula-list label) {
3057
- display: inline-flex;
3058
- align-items: center;
3059
- gap: 3pt;
3060
- padding: 1px 5px;
3061
- border-radius: 3px;
3062
- font-size: 0.88em;
3063
- cursor: pointer;
3064
- background: color-mix(in srgb, currentColor 6%, transparent);
3065
- }
3066
- .chempot-diagram-3d :global(.formula-list label:has(input:checked)) {
3067
- background: color-mix(in srgb, currentColor 16%, transparent);
3068
- }
3069
- .chempot-diagram-3d :global(.formula-list input[type='checkbox']) {
3070
- position: absolute;
3071
- width: 1px;
3072
- height: 1px;
3073
- overflow: hidden;
3074
- clip: rect(0 0 0 0);
3075
- }
3076
- .chempot-diagram-3d :global(.formula-list label:has(input:focus-visible)) {
3077
- outline: 2px solid Highlight;
3078
- outline-offset: 1px;
3079
- }
3080
- .chempot-diagram-3d :global(.formula-color-dot) {
3081
- width: 0.55em;
3082
- height: 0.55em;
3083
- border-radius: 50%;
3084
- flex-shrink: 0;
3085
- }
3086
- .chempot-diagram-3d :global(.formula-empty) {
3087
- font-size: 0.9em;
3088
- opacity: 0.7;
3089
- }
3090
- .chempot-diagram-3d :global(.chempot-nums input[type='number']) {
3091
- width: 5em;
3092
- }
3093
- .chempot-diagram-3d :global(.draggable-pane select) {
3094
- flex: 1;
3095
- min-width: 0;
3096
- padding: 2px 4px;
3097
- }
3098
- .computing-state {
3099
- display: flex;
3100
- align-items: center;
3101
- justify-content: center;
3102
- min-height: 200px;
3103
- }
3104
- .error-state {
3105
- display: flex;
3106
- flex-direction: column;
3107
- align-items: center;
3108
- justify-content: center;
3109
- height: 100%;
3110
- color: var(--text-color, #666);
3111
- }
3112
- :is(.axis-label, .tick-label) {
3113
- pointer-events: none;
3114
- user-select: none;
3115
- white-space: nowrap;
3116
- }
3117
- .axis-label {
3118
- font: bold 13px sans-serif;
3119
- }
3120
- .axis-label :global(.axis-unit) {
3121
- font-weight: 300;
3122
- opacity: 0.7;
3123
- }
3124
- .tick-label {
3125
- font-size: 10px;
3126
- color: var(--text-color, #333);
3127
- }
3128
- .domain-label {
3129
- font-family: sans-serif;
3130
- color: var(--text-color, #333);
3131
- opacity: 0.7;
3132
- white-space: nowrap;
3133
- pointer-events: none;
3134
- }
3135
- .formula-subscript {
3136
- font-size: calc(11em / 12);
3137
- vertical-align: -0.28em;
3138
- }
3139
- .phase-tooltip {
3140
- position: absolute;
3141
- max-width: min(32rem, 92vw);
3142
- background: var(
3143
- --tooltip-bg,
3144
- light-dark(rgba(255, 255, 255, 0.95), rgba(0, 0, 0, 0.9))
3145
- );
3146
- color: var(--tooltip-text, var(--text-color, #222));
3147
- border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
3148
- border-radius: 6px;
3149
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
3150
- padding: 4px 6px;
3151
- font-size: 12px;
3152
- line-height: 1.25;
3153
- pointer-events: none;
3154
- z-index: 100;
3155
- }
3156
- .phase-tooltip h4 {
3157
- margin: 0 0 2px;
3158
- font-size: 13px;
3159
- }
3160
- .phase-tooltip p {
3161
- margin: 1px 0;
3162
- white-space: nowrap;
3163
- overflow: hidden;
3164
- text-overflow: ellipsis;
3165
- }
3166
- .phase-tooltip h5 {
3167
- margin-top: 4px;
3168
- margin-bottom: 0;
3169
- font-size: 12px;
3170
- font-weight: 600;
3171
- }
3172
- .arity-legend {
3173
- position: absolute;
3174
- bottom: 16px;
3175
- left: 1em;
3176
- display: flex;
3177
- gap: 10px;
3178
- font-size: 12px;
3179
- z-index: 10;
3180
- pointer-events: none;
3181
- }
3182
- .arity-legend > span {
3183
- display: flex;
3184
- align-items: center;
3185
- gap: 4px;
3186
- }
3187
- .arity-legend > span > span {
3188
- width: 10px;
3189
- height: 10px;
3190
- border-radius: 50%;
3191
- flex-shrink: 0;
3192
- }
3193
- </style>