matterviz 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/dist/EmptyState.svelte +10 -2
  2. package/dist/FilePicker.svelte +154 -96
  3. package/dist/Icon.svelte +20 -14
  4. package/dist/MillerIndexInput.svelte +27 -21
  5. package/dist/api/optimade.js +6 -6
  6. package/dist/app.css +216 -178
  7. package/dist/brillouin/BrillouinZone.svelte +299 -198
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +74 -55
  11. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  12. package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte +277 -165
  14. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  15. package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
  16. package/dist/brillouin/compute.js +11 -6
  17. package/dist/chempot-diagram/ChemPotDiagram.svelte +327 -0
  18. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  19. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +847 -0
  20. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  21. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +3194 -0
  22. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  23. package/dist/chempot-diagram/ChemPotScene3D.svelte +11 -0
  24. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  25. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  26. package/dist/chempot-diagram/async-compute.svelte.js +77 -0
  27. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  28. package/dist/chempot-diagram/chempot-worker.js +11 -0
  29. package/dist/chempot-diagram/color.d.ts +10 -0
  30. package/dist/chempot-diagram/color.js +32 -0
  31. package/dist/chempot-diagram/compute.d.ts +48 -0
  32. package/dist/chempot-diagram/compute.js +812 -0
  33. package/dist/chempot-diagram/index.d.ts +6 -0
  34. package/dist/chempot-diagram/index.js +6 -0
  35. package/dist/chempot-diagram/pointer.d.ts +16 -0
  36. package/dist/chempot-diagram/pointer.js +40 -0
  37. package/dist/chempot-diagram/temperature.d.ts +15 -0
  38. package/dist/chempot-diagram/temperature.js +36 -0
  39. package/dist/chempot-diagram/types.d.ts +86 -0
  40. package/dist/chempot-diagram/types.js +28 -0
  41. package/dist/colors/index.d.ts +3 -1
  42. package/dist/colors/index.js +9 -3
  43. package/dist/composition/BarChart.svelte +141 -77
  44. package/dist/composition/BubbleChart.svelte +107 -52
  45. package/dist/composition/Composition.svelte +100 -79
  46. package/dist/composition/Formula.svelte +108 -62
  47. package/dist/composition/FormulaFilter.svelte +973 -353
  48. package/dist/composition/FormulaFilter.svelte.d.ts +35 -1
  49. package/dist/composition/PieChart.svelte +199 -99
  50. package/dist/composition/PieChart.svelte.d.ts +1 -1
  51. package/dist/composition/format.d.ts +5 -0
  52. package/dist/composition/format.js +20 -3
  53. package/dist/composition/parse.js +14 -9
  54. package/dist/convex-hull/ConvexHull.svelte +93 -38
  55. package/dist/convex-hull/ConvexHull2D.svelte +551 -393
  56. package/dist/convex-hull/ConvexHull3D.svelte +1303 -825
  57. package/dist/convex-hull/ConvexHull4D.svelte +1012 -686
  58. package/dist/convex-hull/ConvexHullControls.svelte +115 -28
  59. package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
  60. package/dist/convex-hull/ConvexHullStats.svelte +821 -249
  61. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
  62. package/dist/convex-hull/ConvexHullTooltip.svelte +41 -16
  63. package/dist/convex-hull/GasPressureControls.svelte +104 -61
  64. package/dist/convex-hull/StructurePopup.svelte +25 -4
  65. package/dist/convex-hull/TemperatureSlider.svelte +45 -25
  66. package/dist/convex-hull/barycentric-coords.js +13 -7
  67. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  68. package/dist/convex-hull/demo-temperature.js +40 -0
  69. package/dist/convex-hull/gas-thermodynamics.js +17 -12
  70. package/dist/convex-hull/helpers.d.ts +10 -1
  71. package/dist/convex-hull/helpers.js +79 -38
  72. package/dist/convex-hull/index.d.ts +1 -0
  73. package/dist/convex-hull/index.js +1 -0
  74. package/dist/convex-hull/thermodynamics.d.ts +8 -21
  75. package/dist/convex-hull/thermodynamics.js +163 -69
  76. package/dist/convex-hull/types.d.ts +12 -12
  77. package/dist/convex-hull/types.js +0 -12
  78. package/dist/coordination/CoordinationBarPlot.svelte +232 -176
  79. package/dist/element/BohrAtom.svelte +56 -13
  80. package/dist/element/ElementHeading.svelte +7 -2
  81. package/dist/element/ElementPhoto.svelte +15 -9
  82. package/dist/element/ElementStats.svelte +10 -4
  83. package/dist/element/ElementTile.svelte +137 -73
  84. package/dist/element/Nucleus.svelte +39 -11
  85. package/dist/element/data.js +2 -14
  86. package/dist/element/data.json.gz +0 -0
  87. package/dist/element/types.d.ts +1 -0
  88. package/dist/feedback/ClickFeedback.svelte +16 -5
  89. package/dist/feedback/DragOverlay.svelte +10 -2
  90. package/dist/feedback/Spinner.svelte +4 -2
  91. package/dist/feedback/StatusMessage.svelte +8 -2
  92. package/dist/fermi-surface/FermiSlice.svelte +118 -88
  93. package/dist/fermi-surface/FermiSurface.svelte +336 -239
  94. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  95. package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
  96. package/dist/fermi-surface/FermiSurfaceScene.svelte +536 -343
  97. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  98. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
  99. package/dist/fermi-surface/compute.js +16 -20
  100. package/dist/fermi-surface/parse.js +37 -33
  101. package/dist/fermi-surface/symmetry.js +2 -7
  102. package/dist/fermi-surface/types.d.ts +3 -5
  103. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1527 -0
  104. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  105. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
  106. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +30 -0
  107. package/dist/heatmap-matrix/index.d.ts +53 -0
  108. package/dist/heatmap-matrix/index.js +100 -0
  109. package/dist/heatmap-matrix/shared.d.ts +2 -0
  110. package/dist/heatmap-matrix/shared.js +4 -0
  111. package/dist/icons.d.ts +111 -0
  112. package/dist/icons.js +158 -0
  113. package/dist/index.d.ts +5 -2
  114. package/dist/index.js +5 -2
  115. package/dist/io/decompress.js +1 -1
  116. package/dist/io/export.d.ts +3 -0
  117. package/dist/io/export.js +138 -140
  118. package/dist/io/file-drop.d.ts +7 -0
  119. package/dist/io/file-drop.js +43 -0
  120. package/dist/io/index.d.ts +2 -2
  121. package/dist/io/index.js +2 -112
  122. package/dist/io/is-binary.js +2 -3
  123. package/dist/io/types.d.ts +1 -0
  124. package/dist/io/url-drop.d.ts +2 -0
  125. package/dist/io/url-drop.js +117 -0
  126. package/dist/isosurface/Isosurface.svelte +220 -110
  127. package/dist/isosurface/IsosurfaceControls.svelte +65 -28
  128. package/dist/isosurface/parse.js +104 -56
  129. package/dist/isosurface/slice.d.ts +2 -1
  130. package/dist/isosurface/slice.js +8 -13
  131. package/dist/isosurface/types.d.ts +14 -1
  132. package/dist/isosurface/types.js +152 -5
  133. package/dist/labels.d.ts +2 -1
  134. package/dist/labels.js +12 -8
  135. package/dist/layout/FullscreenToggle.svelte +11 -2
  136. package/dist/layout/InfoCard.svelte +38 -6
  137. package/dist/layout/InfoTag.svelte +125 -94
  138. package/dist/layout/PropertyFilter.svelte +82 -37
  139. package/dist/layout/SettingsSection.svelte +85 -55
  140. package/dist/layout/SubpageGrid.svelte +82 -0
  141. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  142. package/dist/layout/index.d.ts +1 -0
  143. package/dist/layout/index.js +1 -0
  144. package/dist/layout/json-tree/JsonNode.svelte +266 -223
  145. package/dist/layout/json-tree/JsonTree.svelte +516 -429
  146. package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
  147. package/dist/layout/json-tree/JsonValue.svelte +281 -173
  148. package/dist/layout/json-tree/types.d.ts +10 -2
  149. package/dist/layout/json-tree/utils.d.ts +2 -0
  150. package/dist/layout/json-tree/utils.js +37 -2
  151. package/dist/marching-cubes.js +25 -2
  152. package/dist/math.d.ts +20 -17
  153. package/dist/math.js +474 -57
  154. package/dist/overlays/ContextMenu.svelte +66 -40
  155. package/dist/overlays/DraggablePane.svelte +331 -154
  156. package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
  157. package/dist/periodic-table/PeriodicTable.svelte +278 -145
  158. package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
  159. package/dist/periodic-table/PropertySelect.svelte +25 -7
  160. package/dist/periodic-table/TableInset.svelte +8 -3
  161. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +559 -267
  162. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
  163. package/dist/phase-diagram/PhaseDiagramControls.svelte +131 -51
  164. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
  165. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
  166. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  167. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +160 -110
  168. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +8 -1
  169. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +217 -86
  170. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
  171. package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
  172. package/dist/phase-diagram/build-diagram.js +9 -9
  173. package/dist/phase-diagram/colors.js +1 -3
  174. package/dist/phase-diagram/index.d.ts +2 -0
  175. package/dist/phase-diagram/index.js +2 -0
  176. package/dist/phase-diagram/parse.js +10 -9
  177. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  178. package/dist/phase-diagram/svg-to-diagram.js +869 -0
  179. package/dist/phase-diagram/types.d.ts +10 -0
  180. package/dist/phase-diagram/utils.d.ts +8 -4
  181. package/dist/phase-diagram/utils.js +219 -74
  182. package/dist/plot/AxisLabel.svelte +51 -0
  183. package/dist/plot/AxisLabel.svelte.d.ts +16 -0
  184. package/dist/plot/BarPlot.svelte +1461 -768
  185. package/dist/plot/BarPlot.svelte.d.ts +3 -3
  186. package/dist/plot/BarPlotControls.svelte +33 -6
  187. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  188. package/dist/plot/ColorBar.svelte +533 -383
  189. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  190. package/dist/plot/ColorScaleSelect.svelte +28 -7
  191. package/dist/plot/ElementScatter.svelte +38 -16
  192. package/dist/plot/FillArea.svelte +152 -92
  193. package/dist/plot/Histogram.svelte +1162 -709
  194. package/dist/plot/Histogram.svelte.d.ts +1 -1
  195. package/dist/plot/HistogramControls.svelte +81 -18
  196. package/dist/plot/HistogramControls.svelte.d.ts +6 -2
  197. package/dist/plot/InteractiveAxisLabel.svelte +34 -11
  198. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  199. package/dist/plot/Line.svelte +63 -28
  200. package/dist/plot/PlotControls.svelte +221 -96
  201. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  202. package/dist/plot/PlotLegend.svelte +174 -91
  203. package/dist/plot/PlotTooltip.svelte +45 -6
  204. package/dist/plot/PortalSelect.svelte +175 -146
  205. package/dist/plot/ReferenceLine.svelte +77 -22
  206. package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
  207. package/dist/plot/ReferenceLine3D.svelte +132 -107
  208. package/dist/plot/ReferencePlane.svelte +146 -123
  209. package/dist/plot/ScatterPlot.svelte +1880 -1156
  210. package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
  211. package/dist/plot/ScatterPlot3D.svelte +256 -131
  212. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  213. package/dist/plot/ScatterPlot3DControls.svelte +300 -297
  214. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
  215. package/dist/plot/ScatterPlot3DScene.svelte +608 -406
  216. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  217. package/dist/plot/ScatterPlotControls.svelte +150 -70
  218. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  219. package/dist/plot/ScatterPoint.svelte +98 -26
  220. package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
  221. package/dist/plot/SpacegroupBarPlot.svelte +142 -85
  222. package/dist/plot/Surface3D.svelte +159 -108
  223. package/dist/plot/ZeroLines.svelte +96 -0
  224. package/dist/plot/ZeroLines.svelte.d.ts +32 -0
  225. package/dist/plot/ZoomRect.svelte +23 -0
  226. package/dist/plot/ZoomRect.svelte.d.ts +8 -0
  227. package/dist/plot/axis-utils.d.ts +1 -1
  228. package/dist/plot/axis-utils.js +1 -3
  229. package/dist/plot/data-cleaning.js +12 -28
  230. package/dist/plot/data-transform.js +2 -1
  231. package/dist/plot/fill-utils.js +2 -0
  232. package/dist/plot/index.d.ts +6 -2
  233. package/dist/plot/index.js +6 -2
  234. package/dist/plot/interactions.d.ts +8 -10
  235. package/dist/plot/interactions.js +2 -3
  236. package/dist/plot/layout.d.ts +11 -2
  237. package/dist/plot/layout.js +44 -17
  238. package/dist/plot/reference-line.d.ts +5 -22
  239. package/dist/plot/reference-line.js +12 -84
  240. package/dist/plot/scales.js +24 -36
  241. package/dist/plot/types.d.ts +53 -40
  242. package/dist/plot/types.js +12 -7
  243. package/dist/plot/utils/label-placement.d.ts +32 -15
  244. package/dist/plot/utils/label-placement.js +227 -63
  245. package/dist/plot/utils/series-visibility.js +2 -3
  246. package/dist/plot/utils.d.ts +1 -0
  247. package/dist/plot/utils.js +14 -0
  248. package/dist/rdf/RdfPlot.svelte +173 -132
  249. package/dist/rdf/calc-rdf.js +4 -5
  250. package/dist/sanitize.d.ts +4 -0
  251. package/dist/sanitize.js +107 -0
  252. package/dist/settings.d.ts +21 -6
  253. package/dist/settings.js +63 -19
  254. package/dist/spectral/Bands.svelte +963 -412
  255. package/dist/spectral/Bands.svelte.d.ts +22 -2
  256. package/dist/spectral/BandsAndDos.svelte +90 -49
  257. package/dist/spectral/BrillouinBandsDos.svelte +151 -93
  258. package/dist/spectral/Dos.svelte +389 -258
  259. package/dist/spectral/helpers.d.ts +23 -1
  260. package/dist/spectral/helpers.js +119 -51
  261. package/dist/spectral/types.d.ts +2 -0
  262. package/dist/state.svelte.d.ts +1 -1
  263. package/dist/state.svelte.js +3 -2
  264. package/dist/structure/Arrow.svelte +59 -20
  265. package/dist/structure/AtomLegend.svelte +231 -129
  266. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  267. package/dist/structure/Bond.svelte +73 -47
  268. package/dist/structure/CanvasTooltip.svelte +10 -2
  269. package/dist/structure/CellSelect.svelte +148 -51
  270. package/dist/structure/Cylinder.svelte +33 -17
  271. package/dist/structure/Lattice.svelte +88 -33
  272. package/dist/structure/Structure.svelte +1077 -821
  273. package/dist/structure/Structure.svelte.d.ts +1 -1
  274. package/dist/structure/StructureControls.svelte +373 -139
  275. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  276. package/dist/structure/StructureExportPane.svelte +124 -89
  277. package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
  278. package/dist/structure/StructureInfoPane.svelte +304 -231
  279. package/dist/structure/StructureScene.svelte +919 -445
  280. package/dist/structure/StructureScene.svelte.d.ts +16 -7
  281. package/dist/structure/atom-properties.d.ts +6 -2
  282. package/dist/structure/atom-properties.js +42 -29
  283. package/dist/structure/bonding.js +6 -7
  284. package/dist/structure/export.js +22 -34
  285. package/dist/structure/ferrox-wasm-types.d.ts +3 -2
  286. package/dist/structure/ferrox-wasm-types.js +0 -3
  287. package/dist/structure/ferrox-wasm.d.ts +3 -2
  288. package/dist/structure/ferrox-wasm.js +2 -3
  289. package/dist/structure/index.d.ts +16 -0
  290. package/dist/structure/index.js +88 -6
  291. package/dist/structure/measure.d.ts +2 -2
  292. package/dist/structure/measure.js +4 -44
  293. package/dist/structure/parse.js +130 -155
  294. package/dist/structure/partial-occupancy.d.ts +25 -0
  295. package/dist/structure/partial-occupancy.js +99 -0
  296. package/dist/structure/pbc.d.ts +1 -0
  297. package/dist/structure/pbc.js +16 -6
  298. package/dist/structure/supercell.d.ts +2 -2
  299. package/dist/structure/supercell.js +12 -22
  300. package/dist/structure/validation.js +5 -3
  301. package/dist/symmetry/SymmetryStats.svelte +94 -37
  302. package/dist/symmetry/WyckoffTable.svelte +42 -14
  303. package/dist/symmetry/cell-transform.js +5 -3
  304. package/dist/symmetry/index.d.ts +7 -4
  305. package/dist/symmetry/index.js +87 -21
  306. package/dist/symmetry/spacegroups.js +148 -148
  307. package/dist/table/HeatmapTable.svelte +1112 -516
  308. package/dist/table/HeatmapTable.svelte.d.ts +12 -1
  309. package/dist/table/ToggleMenu.svelte +125 -90
  310. package/dist/table/index.d.ts +2 -0
  311. package/dist/table/index.js +2 -4
  312. package/dist/theme/ThemeControl.svelte +21 -12
  313. package/dist/time.js +4 -1
  314. package/dist/tooltip/TooltipContent.svelte +33 -8
  315. package/dist/trajectory/Trajectory.svelte +889 -687
  316. package/dist/trajectory/TrajectoryError.svelte +14 -3
  317. package/dist/trajectory/TrajectoryExportPane.svelte +148 -90
  318. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
  319. package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
  320. package/dist/trajectory/constants.d.ts +6 -0
  321. package/dist/trajectory/constants.js +7 -0
  322. package/dist/trajectory/extract.js +13 -31
  323. package/dist/trajectory/format-detect.d.ts +9 -0
  324. package/dist/trajectory/format-detect.js +76 -0
  325. package/dist/trajectory/frame-reader.d.ts +17 -0
  326. package/dist/trajectory/frame-reader.js +332 -0
  327. package/dist/trajectory/helpers.d.ts +14 -0
  328. package/dist/trajectory/helpers.js +172 -0
  329. package/dist/trajectory/index.d.ts +1 -0
  330. package/dist/trajectory/index.js +23 -14
  331. package/dist/trajectory/parse/ase.d.ts +2 -0
  332. package/dist/trajectory/parse/ase.js +77 -0
  333. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  334. package/dist/trajectory/parse/hdf5.js +129 -0
  335. package/dist/trajectory/parse/index.d.ts +12 -0
  336. package/dist/trajectory/parse/index.js +299 -0
  337. package/dist/trajectory/parse/lammps.d.ts +5 -0
  338. package/dist/trajectory/parse/lammps.js +179 -0
  339. package/dist/trajectory/parse/vasp.d.ts +2 -0
  340. package/dist/trajectory/parse/vasp.js +68 -0
  341. package/dist/trajectory/parse/xyz.d.ts +2 -0
  342. package/dist/trajectory/parse/xyz.js +110 -0
  343. package/dist/trajectory/plotting.js +13 -8
  344. package/dist/trajectory/types.d.ts +11 -0
  345. package/dist/trajectory/types.js +1 -0
  346. package/dist/utils.d.ts +3 -0
  347. package/dist/utils.js +17 -0
  348. package/dist/xrd/XrdPlot.svelte +337 -245
  349. package/dist/xrd/broadening.js +14 -9
  350. package/dist/xrd/calc-xrd.js +12 -19
  351. package/dist/xrd/parse.d.ts +1 -1
  352. package/dist/xrd/parse.js +17 -17
  353. package/package.json +103 -101
  354. package/readme.md +4 -4
  355. package/dist/trajectory/parse.d.ts +0 -42
  356. package/dist/trajectory/parse.js +0 -1267
  357. /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
  358. /package/dist/theme/{themes.js → themes.mjs} +0 -0
@@ -1,440 +1,598 @@
1
- <script lang="ts">import { normalize_show_controls } from '../controls';
2
- import { ClickFeedback, DragOverlay } from '../feedback';
3
- import Icon from '../Icon.svelte';
4
- import { symbol_map } from '../labels';
5
- import { set_fullscreen_bg, setup_fullscreen_effect } from '../layout';
6
- import { ScatterPlot } from '../plot';
7
- import { DEFAULTS } from '../settings';
8
- import ConvexHullControls from './ConvexHullControls.svelte';
9
- import ConvexHullInfoPane from './ConvexHullInfoPane.svelte';
10
- import ConvexHullTooltip from './ConvexHullTooltip.svelte';
11
- import GasPressureControls from './GasPressureControls.svelte';
12
- import * as helpers from './helpers';
13
- import { CONVEX_HULL_STYLE, default_controls, default_hull_config } from './index';
14
- import StructurePopup from './StructurePopup.svelte';
15
- import TemperatureSlider from './TemperatureSlider.svelte';
16
- import * as thermo from './thermodynamics';
17
- import { is_unary_entry } from './types';
18
- // Binary convex hull rendered as energy vs composition (x in [0, 1])
19
- let { entries = [], controls = {}, config = {}, on_point_click, on_point_hover, fullscreen = $bindable(DEFAULTS.convex_hull.binary.fullscreen), enable_info_pane = true, wrapper = $bindable(), label_threshold = 50, show_stable = $bindable(DEFAULTS.convex_hull.binary.show_stable), show_unstable = $bindable(DEFAULTS.convex_hull.binary.show_unstable), color_mode = $bindable(DEFAULTS.convex_hull.binary.color_mode), color_scale = $bindable(DEFAULTS.convex_hull.binary.color_scale), info_pane_open = $bindable(DEFAULTS.convex_hull.binary.info_pane_open), legend_pane_open = $bindable(DEFAULTS.convex_hull.binary.legend_pane_open), max_hull_dist_show_phases = $bindable(DEFAULTS.convex_hull.binary.max_hull_dist_show_phases), max_hull_dist_show_labels = $bindable(DEFAULTS.convex_hull.binary.max_hull_dist_show_labels), show_stable_labels = $bindable(DEFAULTS.convex_hull.binary.show_stable_labels), show_unstable_labels = $bindable(DEFAULTS.convex_hull.binary.show_unstable_labels), on_file_drop, enable_click_selection = true, enable_structure_preview = true, energy_source_mode = $bindable(`precomputed`), phase_stats = $bindable(null), display = $bindable({ x_grid: false, y_grid: false }), stable_entries = $bindable([]), unstable_entries = $bindable([]), highlighted_entries = $bindable([]), highlight_style = {}, x_axis = {}, y_axis = {}, selected_entry = $bindable(null), temperature = $bindable(), interpolate_temperature = true, max_interpolation_gap = 500, gas_config, gas_pressures = $bindable({}), children, tooltip: custom_tooltip, ...rest } = $props();
20
- const merged_controls = $derived({ ...default_controls, ...controls });
21
- const controls_config = $derived(normalize_show_controls(merged_controls.show));
22
- const merged_config = $derived({
1
+ <script lang="ts">
2
+ import type { D3InterpolateName } from '../colors'
3
+ import type { CompositionType } from '../composition'
4
+ import { normalize_show_controls } from '../controls'
5
+ import { sanitize_html } from '../sanitize'
6
+ import type { ElementSymbol } from '../element'
7
+ import { ClickFeedback, DragOverlay } from '../feedback'
8
+ import Icon from '../Icon.svelte'
9
+ import type { D3SymbolName } from '../labels'
10
+ import { symbol_map } from '../labels'
11
+ import { set_fullscreen_bg, setup_fullscreen_effect } from '../layout'
12
+ import type {
13
+ AxisConfig,
14
+ ScatterHandlerEvent,
15
+ ScatterHandlerProps,
16
+ UserContentProps,
17
+ } from '../plot'
18
+ import { ScatterPlot } from '../plot'
19
+ import { DEFAULTS } from '../settings'
20
+ import type { AnyStructure } from '../structure'
21
+ import { SvelteMap } from 'svelte/reactivity'
22
+ import ConvexHullControls from './ConvexHullControls.svelte'
23
+ import ConvexHullInfoPane from './ConvexHullInfoPane.svelte'
24
+ import ConvexHullTooltip from './ConvexHullTooltip.svelte'
25
+ import GasPressureControls from './GasPressureControls.svelte'
26
+ import * as helpers from './helpers'
27
+ import type { BaseConvexHullProps } from './index'
28
+ import { CONVEX_HULL_STYLE, default_controls, default_hull_config } from './index'
29
+ import StructurePopup from './StructurePopup.svelte'
30
+ import TemperatureSlider from './TemperatureSlider.svelte'
31
+ import * as thermo from './thermodynamics'
32
+ import type {
33
+ ConvexHullEntry,
34
+ HighlightStyle,
35
+ HoverData3D,
36
+ PhaseData,
37
+ } from './types'
38
+ import { compute_hull_stability, is_unary_entry } from './helpers'
39
+
40
+ // Binary convex hull rendered as energy vs composition (x in [0, 1])
41
+ let {
42
+ entries = [],
43
+ controls = {},
44
+ config = {},
45
+ on_point_click,
46
+ on_point_hover,
47
+ fullscreen = $bindable(DEFAULTS.convex_hull.binary.fullscreen),
48
+ enable_info_pane = true,
49
+ wrapper = $bindable(),
50
+ label_threshold = 50,
51
+ show_stable = $bindable(DEFAULTS.convex_hull.binary.show_stable),
52
+ show_unstable = $bindable(DEFAULTS.convex_hull.binary.show_unstable),
53
+ color_mode = $bindable(DEFAULTS.convex_hull.binary.color_mode),
54
+ color_scale = $bindable(
55
+ DEFAULTS.convex_hull.binary.color_scale as D3InterpolateName,
56
+ ),
57
+ info_pane_open = $bindable(DEFAULTS.convex_hull.binary.info_pane_open),
58
+ legend_pane_open = $bindable(DEFAULTS.convex_hull.binary.legend_pane_open),
59
+ max_hull_dist_show_phases = $bindable(
60
+ DEFAULTS.convex_hull.binary.max_hull_dist_show_phases,
61
+ ),
62
+ max_hull_dist_show_labels = $bindable(
63
+ DEFAULTS.convex_hull.binary.max_hull_dist_show_labels,
64
+ ),
65
+ show_stable_labels = $bindable(DEFAULTS.convex_hull.binary.show_stable_labels),
66
+ show_unstable_labels = $bindable(
67
+ DEFAULTS.convex_hull.binary.show_unstable_labels,
68
+ ),
69
+ on_file_drop,
70
+ enable_click_selection = true,
71
+ enable_structure_preview = true,
72
+ energy_source_mode = $bindable(`precomputed`),
73
+ phase_stats = $bindable(null),
74
+ display = $bindable({ x_grid: false, y_grid: false }),
75
+ stable_entries = $bindable([]),
76
+ unstable_entries = $bindable([]),
77
+ highlighted_entries = $bindable([]),
78
+ highlight_style = {},
79
+ x_axis = {},
80
+ y_axis = {},
81
+ selected_entry = $bindable(null),
82
+ temperature = $bindable(),
83
+ interpolate_temperature = true,
84
+ max_interpolation_gap = 500,
85
+ gas_config,
86
+ gas_pressures = $bindable({}),
87
+ children,
88
+ tooltip: custom_tooltip,
89
+ ...rest
90
+ }: BaseConvexHullProps<ConvexHullEntry> & {
91
+ highlight_style?: HighlightStyle
92
+ x_axis?: AxisConfig
93
+ y_axis?: AxisConfig
94
+ } = $props()
95
+
96
+ const merged_controls = $derived({ ...default_controls, ...controls })
97
+ const controls_config = $derived(normalize_show_controls(merged_controls.show))
98
+ const merged_config = $derived({
23
99
  ...default_hull_config,
24
100
  point_size: 6, // Binary diagrams use slightly smaller points
25
101
  ...config,
26
102
  colors: { ...default_hull_config.colors, ...(config.colors || {}) },
27
103
  margin: { t: 40, r: 40, b: 60, l: 60, ...(config.margin || {}) },
28
- });
29
- // Merge highlight style with defaults (consistent with 3D/4D)
30
- const merged_highlight_style = $derived(helpers.merge_highlight_style(highlight_style));
31
- // Helper to check if entry is highlighted
32
- const is_highlighted = (entry) => helpers.is_entry_highlighted(entry, highlighted_entries);
33
- // Temperature-dependent free energy support
34
- const { has_temp_data, available_temperatures } = $derived(helpers.analyze_temperature_data(entries));
35
- // Initialize or reset temperature when it's undefined or no longer valid
36
- $effect(() => {
37
- if (has_temp_data &&
38
- available_temperatures.length > 0 &&
39
- (temperature === undefined || !available_temperatures.includes(temperature)))
40
- temperature = available_temperatures[0];
41
- });
42
- // Filter entries by temperature when in temperature mode
43
- const temp_filtered_entries = $derived(has_temp_data && temperature !== undefined
44
- ? helpers.filter_entries_at_temperature(entries, temperature, {
104
+ })
105
+
106
+ // Merge highlight style with defaults (consistent with 3D/4D)
107
+ const merged_highlight_style = $derived(
108
+ helpers.merge_highlight_style(highlight_style),
109
+ )
110
+
111
+ // Helper to check if entry is highlighted
112
+ const is_highlighted = (entry: ConvexHullEntry): boolean =>
113
+ helpers.is_entry_highlighted(entry, highlighted_entries)
114
+
115
+ // Temperature-dependent free energy support
116
+ const { has_temp_data, available_temperatures } = $derived(
117
+ helpers.analyze_temperature_data(entries),
118
+ )
119
+
120
+ // Initialize or reset temperature when it's undefined or no longer valid
121
+ $effect(() => {
122
+ if (
123
+ has_temp_data &&
124
+ available_temperatures.length > 0 &&
125
+ (temperature === undefined || !available_temperatures.includes(temperature))
126
+ ) temperature = available_temperatures[0]
127
+ })
128
+
129
+ // Filter entries by temperature when in temperature mode
130
+ const temp_filtered_entries = $derived(
131
+ has_temp_data && temperature !== undefined
132
+ ? helpers.filter_entries_at_temperature(entries, temperature, {
45
133
  interpolate: interpolate_temperature,
46
134
  max_interpolation_gap,
47
- })
48
- : entries);
49
- // Gas-dependent chemical potential support (corrections based on T, P)
50
- const { entries: gas_corrected_entries, analysis: gas_analysis, merged_config: merged_gas_config, } = $derived(helpers.get_gas_corrected_entries(temp_filtered_entries, gas_config, gas_pressures, temperature ?? helpers.DEFAULT_GAS_TEMP));
51
- let { // Compute energy mode information
52
- has_precomputed_e_form, has_precomputed_hull, can_compute_e_form, can_compute_hull, energy_mode, unary_refs, } = $derived(helpers.compute_energy_mode_info(gas_corrected_entries, thermo.find_lowest_energy_unary_refs, energy_source_mode));
53
- const effective_entries = $derived(helpers.get_effective_entries(gas_corrected_entries, energy_mode, unary_refs, thermo.compute_e_form_per_atom));
54
- // Process data and element set
55
- const pd_data = $derived(thermo.process_hull_entries(effective_entries));
56
- const polymorph_stats_map = $derived(helpers.compute_all_polymorph_stats(effective_entries)); // Pre-compute polymorph stats once for O(1) tooltip lookups
57
- const elements = $derived.by(() => {
135
+ })
136
+ : entries,
137
+ )
138
+
139
+ // Gas-dependent chemical potential support (corrections based on T, P)
140
+ const {
141
+ entries: gas_corrected_entries,
142
+ analysis: gas_analysis,
143
+ merged_config: merged_gas_config,
144
+ } = $derived(
145
+ helpers.get_gas_corrected_entries(
146
+ temp_filtered_entries,
147
+ gas_config,
148
+ gas_pressures,
149
+ temperature ?? helpers.DEFAULT_GAS_TEMP,
150
+ ),
151
+ )
152
+
153
+ let { // Compute energy mode information
154
+ has_precomputed_e_form,
155
+ has_precomputed_hull,
156
+ can_compute_e_form,
157
+ can_compute_hull,
158
+ energy_mode,
159
+ unary_refs,
160
+ } = $derived(
161
+ helpers.compute_energy_mode_info(
162
+ gas_corrected_entries,
163
+ thermo.find_lowest_energy_unary_refs,
164
+ energy_source_mode,
165
+ ),
166
+ )
167
+
168
+ const effective_entries = $derived(
169
+ helpers.get_effective_entries(
170
+ gas_corrected_entries,
171
+ energy_mode,
172
+ unary_refs,
173
+ thermo.compute_e_form_per_atom,
174
+ ),
175
+ )
176
+
177
+ // Process data and element set
178
+ const pd_data = $derived(thermo.process_hull_entries(effective_entries))
179
+
180
+ const polymorph_stats_map = $derived(
181
+ helpers.compute_all_polymorph_stats(effective_entries),
182
+ ) // Pre-compute polymorph stats once for O(1) tooltip lookups
183
+
184
+ const elements = $derived.by(() => {
58
185
  if (pd_data.elements.length > 2) {
59
- console.error(`ConvexHull2D: Dataset contains ${pd_data.elements.length} elements, but binary diagrams require exactly 2. Found: [${pd_data.elements.join(`, `)}]`);
60
- return [];
186
+ console.error(
187
+ `ConvexHull2D: Dataset contains ${pd_data.elements.length} elements, but binary diagrams require exactly 2. Found: [${
188
+ pd_data.elements.join(`, `)
189
+ }]`,
190
+ )
191
+ return []
61
192
  }
62
- return pd_data.elements;
63
- });
64
- // Coordinate computation ----------------------------------------------------
65
- function compute_binary_coordinates(raw_entries, elems) {
66
- if (elems.length !== 2)
67
- return [];
68
- const [el1, el2] = elems;
69
- const coords = [];
193
+ return pd_data.elements
194
+ })
195
+
196
+ // Coordinate computation ----------------------------------------------------
197
+ function compute_binary_coordinates(
198
+ raw_entries: PhaseData[],
199
+ elems: ElementSymbol[],
200
+ ): ConvexHullEntry[] {
201
+ if (elems.length !== 2) return []
202
+ const [el1, el2] = elems
203
+ const coords: ConvexHullEntry[] = []
70
204
  for (const entry of raw_entries) {
71
- // Require formation energy per atom to place along y
72
- const e_form = entry.e_form_per_atom;
73
- if (typeof e_form !== `number`)
74
- continue;
75
- const total = Object.values(entry.composition).reduce((s, v) => s + v, 0);
76
- if (total <= 0)
77
- continue;
78
- const frac_b = (entry.composition[el2] || 0) / total;
79
- const is_element = is_unary_entry(entry);
80
- coords.push({ ...entry, x: frac_b, y: e_form, z: 0, is_element, visible: true });
205
+ // Require formation energy per atom to place along y
206
+ const e_form = entry.e_form_per_atom
207
+ if (typeof e_form !== `number`) continue
208
+ const total = Object.values(entry.composition).reduce((s, v) => s + v, 0)
209
+ if (total <= 0) continue
210
+ const frac_b = (entry.composition[el2] || 0) / total
211
+ const is_element = is_unary_entry(entry)
212
+ coords.push({ ...entry, x: frac_b, y: e_form, z: 0, is_element, visible: true })
81
213
  }
82
214
  // Ensure elemental references at x=0 and x=1 with y=0 to close the hull
83
- const el_a = coords.find((e) => e.is_element && e.x === 0);
84
- const el_b = coords.find((e) => e.is_element && e.x === 1);
215
+ const el_a: ConvexHullEntry | undefined = coords.find((e) =>
216
+ e.is_element && e.x === 0
217
+ )
218
+ const el_b: ConvexHullEntry | undefined = coords.find((e) =>
219
+ e.is_element && e.x === 1
220
+ )
85
221
  if (!el_a) {
86
- coords.push({
87
- composition: { [el1]: 1 },
88
- energy: 0,
89
- x: 0,
90
- y: 0,
91
- z: 0,
92
- is_element: true,
93
- visible: true,
94
- });
222
+ coords.push({
223
+ composition: { [el1]: 1 } as CompositionType,
224
+ energy: 0,
225
+ x: 0,
226
+ y: 0,
227
+ z: 0,
228
+ is_element: true,
229
+ visible: true,
230
+ })
95
231
  }
96
232
  if (!el_b) {
97
- coords.push({
98
- composition: { [el2]: 1 },
99
- energy: 0,
100
- x: 1,
101
- y: 0,
102
- z: 0,
103
- is_element: true,
104
- visible: true,
105
- });
233
+ coords.push({
234
+ composition: { [el2]: 1 } as CompositionType,
235
+ energy: 0,
236
+ x: 1,
237
+ y: 0,
238
+ z: 0,
239
+ is_element: true,
240
+ visible: true,
241
+ })
106
242
  }
107
- return coords;
108
- }
109
- function compute_lower_hull(points) {
110
- // Andrew's monotone chain for lower hull
111
- const sorted = [...points].sort((p1, p2) => (p1.x - p2.x) || (p1.y - p2.y));
112
- const lower = [];
113
- const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
114
- for (const p of sorted) {
115
- while (lower.length >= 2 &&
116
- cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0)
117
- lower.pop();
118
- lower.push(p);
119
- }
120
- return lower;
121
- }
122
- function interpolate_on_hull(hull, x) {
123
- if (hull.length < 2)
124
- return null;
125
- if (x <= hull[0].x)
126
- return hull[0].y;
127
- if (x >= hull[hull.length - 1].x)
128
- return hull[hull.length - 1].y;
129
- for (let i = 0; i < hull.length - 1; i++) {
130
- const p1 = hull[i];
131
- const p2 = hull[i + 1];
132
- if (x >= p1.x && x <= p2.x) {
133
- const t = (x - p1.x) / Math.max(1e-12, p2.x - p1.x);
134
- return p1.y * (1 - t) + p2.y * t;
135
- }
136
- }
137
- return null;
138
- }
139
- const coords_entries = $derived.by(() => {
140
- if (elements.length !== 2)
141
- return [];
243
+ return coords
244
+ }
245
+
246
+ const coords_entries = $derived.by(() => {
247
+ if (elements.length !== 2) return []
142
248
  try {
143
- return compute_binary_coordinates(pd_data.entries, elements);
249
+ return compute_binary_coordinates(pd_data.entries, elements)
250
+ } catch (error) {
251
+ console.error(`Error computing binary coordinates:`, error)
252
+ return []
144
253
  }
145
- catch (error) {
146
- console.error(`Error computing binary coordinates:`, error);
147
- return [];
148
- }
149
- });
150
- // Compute hull and enrich entries with e_above_hull (before filtering)
151
- const { all_enriched_entries, hull_points } = $derived.by(() => {
254
+ })
255
+
256
+ // Compute hull and enrich entries with e_above_hull (before filtering)
257
+ const { all_enriched_entries, hull_points } = $derived.by(() => {
152
258
  if (coords_entries.length === 0) {
153
- return { all_enriched_entries: [], hull_points: [] };
259
+ return { all_enriched_entries: [], hull_points: [] }
154
260
  }
155
- // Build lower hull: group by x, use lowest energy per x
156
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- local var in derived
157
- const by_x = new Map();
261
+
262
+ // Build lower hull input: one minimum-energy point per composition x.
263
+ // Excluded entries don't participate in hull construction.
264
+ const min_y_by_x = new SvelteMap<number, number>()
158
265
  for (const entry of coords_entries) {
159
- const group = by_x.get(entry.x);
160
- if (group)
161
- group.push(entry);
162
- else
163
- by_x.set(entry.x, [entry]);
266
+ if (entry.exclude_from_hull) continue
267
+ const current_min_y = min_y_by_x.get(entry.x)
268
+ if (current_min_y === undefined || entry.y < current_min_y) {
269
+ min_y_by_x.set(entry.x, entry.y)
270
+ }
164
271
  }
165
- const hull_input = [...by_x].map(([x_coord, entries]) => ({
166
- x: x_coord,
167
- y: Math.min(...entries.map((e) => e.y)),
168
- }));
169
- const hull_points = compute_lower_hull(hull_input);
272
+
273
+ const hull_input = [...min_y_by_x].map(([x_coord, min_y]) => ({
274
+ x: x_coord,
275
+ y: min_y,
276
+ }))
277
+ const hull_points = thermo.compute_lower_hull_2d(hull_input)
278
+
170
279
  const all_enriched_entries = coords_entries.map((entry) => {
171
- const y_hull = interpolate_on_hull(hull_points, entry.x);
172
- const e_above_hull = y_hull == null ? 0 : Math.max(0, entry.y - y_hull);
173
- return {
174
- ...entry,
175
- e_above_hull,
176
- is_stable: e_above_hull <= 1e-9,
177
- visible: true,
178
- };
179
- });
180
- return { all_enriched_entries, hull_points };
181
- });
182
- // Auto threshold: show all for few entries, use default for many, interpolate between
183
- const max_hull_dist_in_data = $derived(helpers.calc_max_hull_dist_in_data(all_enriched_entries));
184
- const auto_default_threshold = $derived(helpers.compute_auto_hull_dist_threshold(all_enriched_entries.length, max_hull_dist_in_data, DEFAULTS.convex_hull.binary.max_hull_dist_show_phases));
185
- // Initialize threshold to auto value on first load
186
- let initialized = $state(false);
187
- $effect(() => {
280
+ const y_hull = thermo.interpolate_hull_2d(hull_points, entry.x)
281
+ const raw_dist = y_hull == null ? 0 : entry.y - y_hull
282
+ return {
283
+ ...entry, ...compute_hull_stability(raw_dist, entry.exclude_from_hull), visible: true,
284
+ }
285
+ })
286
+ return { all_enriched_entries, hull_points }
287
+ })
288
+
289
+ // Auto threshold: show all for few entries, use default for many, interpolate between
290
+ const max_hull_dist_in_data = $derived(
291
+ helpers.calc_max_hull_dist_in_data(all_enriched_entries),
292
+ )
293
+ const auto_default_threshold = $derived(helpers.compute_auto_hull_dist_threshold(
294
+ all_enriched_entries.length,
295
+ max_hull_dist_in_data,
296
+ DEFAULTS.convex_hull.binary.max_hull_dist_show_phases,
297
+ ))
298
+
299
+ // Initialize threshold to auto value on first load
300
+ let initialized = $state(false)
301
+ $effect(() => {
188
302
  if (!initialized && all_enriched_entries.length > 0) {
189
- initialized = true;
190
- max_hull_dist_show_phases = auto_default_threshold;
303
+ initialized = true
304
+ max_hull_dist_show_phases = auto_default_threshold
191
305
  }
192
- });
193
- // Filter by threshold and compute visibility
194
- const plot_entries = $derived(all_enriched_entries
195
- .filter((e) => e.is_stable || (e.e_above_hull ?? 0) <= max_hull_dist_show_phases)
196
- .map((e) => ({
197
- ...e,
198
- visible: (e.is_stable && show_stable) || (!e.is_stable && show_unstable),
199
- })));
200
- // Update bindable entries arrays when plot_entries change (single pass)
201
- $effect(() => {
202
- const stable = [];
203
- const unstable = [];
306
+ })
307
+
308
+ // Filter by threshold and compute visibility
309
+ const plot_entries = $derived(
310
+ all_enriched_entries
311
+ .filter((e) =>
312
+ e.is_stable || (e.e_above_hull ?? 0) <= max_hull_dist_show_phases
313
+ )
314
+ .map((e) => ({
315
+ ...e,
316
+ visible: (e.is_stable && show_stable) || (!e.is_stable && show_unstable),
317
+ })),
318
+ )
319
+
320
+ // Update bindable entries arrays when plot_entries change (single pass)
321
+ $effect(() => {
322
+ const stable: ConvexHullEntry[] = []
323
+ const unstable: ConvexHullEntry[] = []
204
324
  for (const entry of plot_entries) {
205
- if (entry.is_stable)
206
- stable.push(entry);
207
- else
208
- unstable.push(entry);
325
+ if (entry.is_stable) stable.push(entry)
326
+ else unstable.push(entry)
209
327
  }
210
- stable_entries = stable;
211
- unstable_entries = unstable;
212
- });
213
- let reset_counter = $state(0);
214
- // Drag and drop state (to match 3D/4D components)
215
- let drag_over = $state(false);
216
- // Copy feedback state
217
- let copy_feedback = $state({ visible: false, position: { x: 0, y: 0 } });
218
- // Structure popup state
219
- let structure_popup = $state({
328
+ stable_entries = stable
329
+ unstable_entries = unstable
330
+ })
331
+
332
+ let reset_counter = $state(0)
333
+ // Drag and drop state (to match 3D/4D components)
334
+ let drag_over = $state(false)
335
+ // Copy feedback state
336
+ let copy_feedback = $state({ visible: false, position: { x: 0, y: 0 } })
337
+
338
+ // Structure popup state
339
+ let structure_popup = $state<{
340
+ open: boolean
341
+ structure: AnyStructure | null
342
+ entry: ConvexHullEntry | null
343
+ place_right: boolean
344
+ }>({
220
345
  open: false,
221
346
  structure: null,
222
347
  entry: null,
223
348
  place_right: true,
224
- });
225
- // Axis mapping helpers ------------------------------------------------------
226
- const x_domain = $derived([0, 1]);
227
- const y_domain = $derived.by(() => {
228
- const ys = plot_entries.map((entry) => entry.y);
229
- if (ys.length === 0)
230
- return [-1, 0];
231
- const min_y_data = Math.min(...ys);
232
- const max_y_data = Math.max(...ys);
233
- const span = Math.max(1e-9, max_y_data - min_y_data);
234
- const pad = 0.05 * span;
235
- return [min_y_data - pad, max_y_data + pad];
236
- });
237
- // Build ScatterPlot series --------------------------------------------------
238
- // Map MarkerSymbol to D3SymbolName (type-safe via symbol_map lookup)
239
- const marker_to_d3_symbol = (marker) => {
240
- if (!marker)
241
- return undefined;
242
- const name = marker.charAt(0).toUpperCase() + marker.slice(1);
243
- return name in symbol_map ? name : undefined;
244
- };
245
- // Pre-compute visible entries to avoid redundant filtering
246
- const visible_entries = $derived(plot_entries.filter((e) => e.visible));
247
- const scatter_points_series = $derived.by(() => {
248
- const is_energy_mode = color_mode === `energy`;
249
- const count = visible_entries.length;
349
+ })
350
+
351
+ // Axis mapping helpers ------------------------------------------------------
352
+ const x_domain = $derived<[number, number]>([0, 1])
353
+ const y_domain = $derived.by((): [number, number] => {
354
+ const ys = plot_entries.map((entry) => entry.y)
355
+ if (ys.length === 0) return [-1, 0]
356
+ const min_y_data = Math.min(...ys)
357
+ const max_y_data = Math.max(...ys)
358
+ const span = Math.max(1e-9, max_y_data - min_y_data)
359
+ const pad = 0.05 * span
360
+ return [min_y_data - pad, max_y_data + pad]
361
+ })
362
+
363
+ // Build ScatterPlot series --------------------------------------------------
364
+
365
+ // Map MarkerSymbol to D3SymbolName (type-safe via symbol_map lookup)
366
+ const marker_to_d3_symbol = (marker?: string): D3SymbolName | undefined => {
367
+ if (!marker) return undefined
368
+ const name = marker.charAt(0).toUpperCase() + marker.slice(1)
369
+ return name in symbol_map ? (name as D3SymbolName) : undefined
370
+ }
371
+
372
+ // Pre-compute visible entries to avoid redundant filtering
373
+ const visible_entries = $derived(plot_entries.filter((e) => e.visible))
374
+
375
+ const scatter_points_series = $derived.by(() => {
376
+ const is_energy_mode = color_mode === `energy`
377
+ const count = visible_entries.length
378
+
250
379
  // Single pass: extract x, y, color_values, and point_style simultaneously
251
- const x_vals = new Array(count);
252
- const y_vals = new Array(count);
253
- const color_values = is_energy_mode ? new Array(count) : [];
254
- const point_style = new Array(count);
380
+ const x_vals: number[] = new Array(count)
381
+ const y_vals: number[] = new Array(count)
382
+ const color_values: number[] = is_energy_mode ? new Array(count) : []
383
+ const point_style = new Array(count)
384
+
255
385
  for (let idx = 0; idx < count; idx++) {
256
- const entry = visible_entries[idx];
257
- x_vals[idx] = entry.x;
258
- y_vals[idx] = entry.y;
259
- if (is_energy_mode)
260
- color_values[idx] = entry.e_above_hull ?? 0;
261
- const is_stable = entry.is_stable || entry.e_above_hull === 0;
262
- const base_radius = entry.size || (is_stable ? 6 : 4);
263
- const hl = is_highlighted(entry) ? merged_highlight_style : null;
264
- point_style[idx] = {
265
- fill: hl && (hl.effect === `color` || hl.effect === `both`)
266
- ? hl.color
267
- : is_energy_mode
268
- ? undefined
269
- : merged_config.colors?.[is_stable ? `stable` : `unstable`],
270
- stroke: is_stable ? `#ffffff` : `#000000`,
271
- radius: hl && (hl.effect === `size` || hl.effect === `both`)
272
- ? base_radius * hl.size_multiplier
273
- : base_radius,
274
- symbol_type: marker_to_d3_symbol(entry.marker),
275
- is_highlighted: !!hl,
276
- highlight_effect: hl?.effect,
277
- highlight_color: hl?.color,
278
- };
386
+ const entry = visible_entries[idx]
387
+ x_vals[idx] = entry.x
388
+ y_vals[idx] = entry.y
389
+ if (is_energy_mode) color_values[idx] = entry.e_above_hull ?? 0
390
+
391
+ const is_stable = entry.is_stable || entry.e_above_hull === 0
392
+ const base_radius = entry.size || (is_stable ? 6 : 4)
393
+ const hl = is_highlighted(entry) ? merged_highlight_style : null
394
+
395
+ point_style[idx] = {
396
+ fill: hl && (hl.effect === `color` || hl.effect === `both`)
397
+ ? hl.color
398
+ : is_energy_mode
399
+ ? undefined
400
+ : merged_config.colors?.[is_stable ? `stable` : `unstable`],
401
+ stroke: is_stable ? `#ffffff` : `#000000`,
402
+ radius: hl && (hl.effect === `size` || hl.effect === `both`)
403
+ ? base_radius * hl.size_multiplier
404
+ : base_radius,
405
+ symbol_type: marker_to_d3_symbol(entry.marker),
406
+ is_highlighted: !!hl,
407
+ highlight_effect: hl?.effect,
408
+ highlight_color: hl?.color,
409
+ }
279
410
  }
411
+
280
412
  return {
281
- x: x_vals,
282
- y: y_vals,
283
- metadata: visible_entries,
284
- markers: `points`,
285
- point_style,
286
- ...(is_energy_mode ? { color_values } : {}),
287
- };
288
- });
289
- const hull_segments_series = $derived.by(() => {
290
- if (!merged_config.show_hull || hull_points.length < 2)
291
- return [];
292
- const segments = [];
413
+ x: x_vals,
414
+ y: y_vals,
415
+ metadata: visible_entries,
416
+ markers: `points` as const,
417
+ point_style,
418
+ ...(is_energy_mode ? { color_values } : {}),
419
+ }
420
+ })
421
+
422
+ const hull_segments_series = $derived.by(() => {
423
+ if (!merged_config.show_hull || hull_points.length < 2) return []
424
+ const segments = []
293
425
  for (let idx = 0; idx < hull_points.length - 1; idx++) {
294
- const p1 = hull_points[idx];
295
- const p2 = hull_points[idx + 1];
296
- segments.push({
297
- x: [p1.x, p2.x],
298
- y: [p1.y, p2.y],
299
- markers: `line`,
300
- line_style: {
301
- stroke: CONVEX_HULL_STYLE.structure_line.color,
302
- stroke_width: CONVEX_HULL_STYLE.structure_line.line_width,
303
- line_dash: `${CONVEX_HULL_STYLE.structure_line.dash[0]},${CONVEX_HULL_STYLE.structure_line.dash[1]}`,
304
- },
305
- });
426
+ const p1 = hull_points[idx]
427
+ const p2 = hull_points[idx + 1]
428
+ segments.push({
429
+ x: [p1.x, p2.x] as const,
430
+ y: [p1.y, p2.y] as const,
431
+ markers: `line` as const,
432
+ line_style: {
433
+ stroke: CONVEX_HULL_STYLE.structure_line.color,
434
+ stroke_width: CONVEX_HULL_STYLE.structure_line.line_width,
435
+ line_dash: `${CONVEX_HULL_STYLE.structure_line.dash[0]},${
436
+ CONVEX_HULL_STYLE.structure_line.dash[1]
437
+ }`,
438
+ },
439
+ })
306
440
  }
307
- return segments;
308
- });
309
- const scatter_series = $derived([scatter_points_series, ...hull_segments_series]);
310
- // Map selected_entry to ScatterPlot point index (series_idx: 0 = points series)
311
- // Use object identity comparison (e === entry) instead of entry_id comparison
312
- // because synthetic elemental entries lack entry_id, and undefined === undefined
313
- // would incorrectly match the first entry with undefined entry_id
314
- const selected_scatter_point = $derived.by(() => {
315
- const entry = selected_entry;
316
- if (!entry)
317
- return null;
318
- const idx = visible_entries.findIndex((vis_entry) => vis_entry === entry);
319
- return idx >= 0 ? { series_idx: 0, point_idx: idx } : null;
320
- });
321
- // Convex hull statistics - compute internally and expose via bindable prop
322
- $effect(() => {
323
- phase_stats = thermo.get_convex_hull_stats(plot_entries, elements, 3);
324
- });
325
- function extract_structure_from_entry(entry) {
326
- if (!entry.entry_id)
327
- return null;
328
- const orig_entry = entries.find((ent) => ent.entry_id === entry.entry_id);
329
- return orig_entry?.structure || null;
330
- }
331
- function reset_all() {
332
- fullscreen = DEFAULTS.convex_hull.binary.fullscreen;
333
- info_pane_open = DEFAULTS.convex_hull.binary.info_pane_open;
334
- legend_pane_open = DEFAULTS.convex_hull.binary.legend_pane_open;
335
- color_mode = DEFAULTS.convex_hull.binary.color_mode;
336
- color_scale = DEFAULTS.convex_hull.binary.color_scale;
337
- show_stable = DEFAULTS.convex_hull.binary.show_stable;
338
- show_unstable = DEFAULTS.convex_hull.binary.show_unstable;
339
- show_stable_labels = DEFAULTS.convex_hull.binary.show_stable_labels;
340
- show_unstable_labels = DEFAULTS.convex_hull.binary.show_unstable_labels;
441
+ return segments
442
+ })
443
+
444
+ const scatter_series = $derived([scatter_points_series, ...hull_segments_series])
445
+
446
+ // Map selected_entry to ScatterPlot point index (series_idx: 0 = points series)
447
+ // Use object identity comparison (e === entry) instead of entry_id comparison
448
+ // because synthetic elemental entries lack entry_id, and undefined === undefined
449
+ // would incorrectly match the first entry with undefined entry_id
450
+ const selected_scatter_point = $derived.by(() => {
451
+ const entry = selected_entry
452
+ if (!entry) return null
453
+ const idx = visible_entries.findIndex((vis_entry) => vis_entry === entry)
454
+ return idx >= 0 ? { series_idx: 0, point_idx: idx } : null
455
+ })
456
+
457
+ // Convex hull statistics - compute internally and expose via bindable prop
458
+ $effect(() => {
459
+ phase_stats = thermo.get_convex_hull_stats(plot_entries, elements, 3)
460
+ })
461
+
462
+ function extract_structure_from_entry(
463
+ entry: ConvexHullEntry,
464
+ ): AnyStructure | null {
465
+ if (!entry.entry_id) return null
466
+ const orig_entry = entries.find((ent) => ent.entry_id === entry.entry_id)
467
+ return orig_entry?.structure as AnyStructure || null
468
+ }
469
+
470
+ function reset_all() {
471
+ fullscreen = DEFAULTS.convex_hull.binary.fullscreen
472
+ info_pane_open = DEFAULTS.convex_hull.binary.info_pane_open
473
+ legend_pane_open = DEFAULTS.convex_hull.binary.legend_pane_open
474
+ color_mode = DEFAULTS.convex_hull.binary.color_mode
475
+ color_scale = DEFAULTS.convex_hull.binary.color_scale as D3InterpolateName
476
+ show_stable = DEFAULTS.convex_hull.binary.show_stable
477
+ show_unstable = DEFAULTS.convex_hull.binary.show_unstable
478
+ show_stable_labels = DEFAULTS.convex_hull.binary.show_stable_labels
479
+ show_unstable_labels = DEFAULTS.convex_hull.binary.show_unstable_labels
341
480
  // Use auto-computed threshold based on entry count instead of static default
342
- max_hull_dist_show_phases = auto_default_threshold;
343
- max_hull_dist_show_labels = DEFAULTS.convex_hull.binary.max_hull_dist_show_labels;
344
- reset_counter += 1;
345
- }
346
- // Track whether any settings differ from defaults (to show/hide reset button)
347
- // Must match all properties that reset_all() resets
348
- // Use auto_default_threshold for comparison since that's the effective default
349
- const has_changes_to_reset = $derived(fullscreen !== DEFAULTS.convex_hull.binary.fullscreen ||
350
- info_pane_open !== DEFAULTS.convex_hull.binary.info_pane_open ||
351
- legend_pane_open !== DEFAULTS.convex_hull.binary.legend_pane_open ||
352
- color_mode !== DEFAULTS.convex_hull.binary.color_mode ||
353
- color_scale !== DEFAULTS.convex_hull.binary.color_scale ||
354
- show_stable !== DEFAULTS.convex_hull.binary.show_stable ||
355
- show_unstable !== DEFAULTS.convex_hull.binary.show_unstable ||
356
- show_stable_labels !== DEFAULTS.convex_hull.binary.show_stable_labels ||
357
- show_unstable_labels !== DEFAULTS.convex_hull.binary.show_unstable_labels ||
358
- // Compare with auto-computed threshold, with small tolerance for floating point
359
- Math.abs(max_hull_dist_show_phases - auto_default_threshold) > 0.001 ||
360
- max_hull_dist_show_labels !==
361
- DEFAULTS.convex_hull.binary.max_hull_dist_show_labels);
362
- // Custom hover tooltip state used with ScatterPlot events
363
- let hover_data = $state(null);
364
- const handle_keydown = (event) => {
365
- if (event.target.tagName.match(/INPUT|TEXTAREA/))
366
- return;
367
- const actions = {
368
- b: () => color_mode = color_mode === `stability` ? `energy` : `stability`,
369
- s: () => show_stable = !show_stable,
370
- u: () => show_unstable = !show_unstable,
371
- l: () => show_stable_labels = !show_stable_labels,
372
- };
373
- actions[event.key.toLowerCase()]?.();
374
- };
375
- async function handle_file_drop(event) {
376
- drag_over = false;
377
- const data = await helpers.parse_hull_entries_from_drop(event);
378
- if (data)
379
- on_file_drop?.(data);
380
- }
381
- async function copy_entry_data(entry, position) {
481
+ max_hull_dist_show_phases = auto_default_threshold
482
+ max_hull_dist_show_labels = DEFAULTS.convex_hull.binary.max_hull_dist_show_labels
483
+ reset_counter += 1
484
+ }
485
+
486
+ // Track whether any settings differ from defaults (to show/hide reset button)
487
+ // Must match all properties that reset_all() resets
488
+ // Use auto_default_threshold for comparison since that's the effective default
489
+ const has_changes_to_reset = $derived(
490
+ fullscreen !== DEFAULTS.convex_hull.binary.fullscreen ||
491
+ info_pane_open !== DEFAULTS.convex_hull.binary.info_pane_open ||
492
+ legend_pane_open !== DEFAULTS.convex_hull.binary.legend_pane_open ||
493
+ color_mode !== DEFAULTS.convex_hull.binary.color_mode ||
494
+ color_scale !== DEFAULTS.convex_hull.binary.color_scale ||
495
+ show_stable !== DEFAULTS.convex_hull.binary.show_stable ||
496
+ show_unstable !== DEFAULTS.convex_hull.binary.show_unstable ||
497
+ show_stable_labels !== DEFAULTS.convex_hull.binary.show_stable_labels ||
498
+ show_unstable_labels !== DEFAULTS.convex_hull.binary.show_unstable_labels ||
499
+ // Compare with auto-computed threshold, with small tolerance for floating point
500
+ Math.abs(max_hull_dist_show_phases - auto_default_threshold) > 0.001 ||
501
+ max_hull_dist_show_labels !==
502
+ DEFAULTS.convex_hull.binary.max_hull_dist_show_labels,
503
+ )
504
+
505
+ // Custom hover tooltip state used with ScatterPlot events
506
+ let hover_data = $state<HoverData3D<ConvexHullEntry> | null>(null)
507
+
508
+ const handle_keydown = (event: KeyboardEvent) => {
509
+ if ((event.target as HTMLElement).tagName.match(/INPUT|TEXTAREA/)) return
510
+ const actions: Record<string, () => void> = {
511
+ b: () => color_mode = color_mode === `stability` ? `energy` : `stability`,
512
+ s: () => show_stable = !show_stable,
513
+ u: () => show_unstable = !show_unstable,
514
+ l: () => show_stable_labels = !show_stable_labels,
515
+ }
516
+ actions[event.key.toLowerCase()]?.()
517
+ }
518
+
519
+ async function handle_file_drop(event: DragEvent): Promise<void> {
520
+ drag_over = false
521
+ const data = await helpers.parse_hull_entries_from_drop(event)
522
+ if (data) on_file_drop?.(data)
523
+ }
524
+
525
+ async function copy_entry_data(
526
+ entry: ConvexHullEntry,
527
+ position: { x: number; y: number },
528
+ ) {
382
529
  await helpers.copy_entry_to_clipboard(entry, position, (visible, pos) => {
383
- copy_feedback.visible = visible;
384
- copy_feedback.position = pos;
385
- });
386
- }
387
- function close_structure_popup() {
388
- structure_popup = { open: false, structure: null, entry: null, place_right: true };
389
- selected_entry = null;
390
- }
391
- // Track last clicked entry for double-click detection
392
- let last_clicked_entry = null;
393
- let last_click_time = 0;
394
- function handle_point_click_internal(data) {
395
- const { metadata: entry, event } = data;
396
- if (!entry)
397
- return;
398
- const now = Date.now();
530
+ copy_feedback.visible = visible
531
+ copy_feedback.position = pos
532
+ })
533
+ }
534
+
535
+ function close_structure_popup() {
536
+ structure_popup = { open: false, structure: null, entry: null, place_right: true }
537
+ selected_entry = null
538
+ }
539
+
540
+ // Track last clicked entry for double-click detection
541
+ let last_clicked_entry: ConvexHullEntry | null = null
542
+ let last_click_time = 0
543
+
544
+ function handle_point_click_internal(data: ScatterHandlerEvent<ConvexHullEntry>) {
545
+ const { metadata: entry, event } = data
546
+ if (!entry) return
547
+
548
+ const now = Date.now()
399
549
  const is_double_click = last_clicked_entry === entry &&
400
- now - last_click_time < 300;
550
+ now - last_click_time < 300
551
+
401
552
  if (is_double_click) {
402
- // Double-click: copy to clipboard
403
- copy_entry_data(entry, { x: event.clientX, y: event.clientY });
404
- last_clicked_entry = null;
405
- last_click_time = 0;
406
- }
407
- else {
408
- // Single click: select entry and optionally show structure preview
409
- last_clicked_entry = entry;
410
- last_click_time = now;
411
- on_point_click?.(entry);
412
- if (enable_click_selection) {
413
- selected_entry = entry;
414
- if (enable_structure_preview) {
415
- const structure = extract_structure_from_entry(entry);
416
- if (structure) {
417
- const viewport_width = globalThis.innerWidth;
418
- const click_x = event.clientX;
419
- const space_on_right = viewport_width - click_x;
420
- const space_on_left = click_x;
421
- const place_right = space_on_right >= space_on_left;
422
- structure_popup = { open: true, structure, entry, place_right };
423
- event.stopPropagation();
424
- }
425
- }
553
+ // Double-click: copy to clipboard
554
+ copy_entry_data(entry, { x: event.clientX, y: event.clientY })
555
+ last_clicked_entry = null
556
+ last_click_time = 0
557
+ } else {
558
+ // Single click: select entry and optionally show structure preview
559
+ last_clicked_entry = entry
560
+ last_click_time = now
561
+
562
+ on_point_click?.(entry)
563
+ if (enable_click_selection) {
564
+ selected_entry = entry
565
+ if (enable_structure_preview) {
566
+ const structure = extract_structure_from_entry(entry)
567
+ if (structure) {
568
+ const viewport_width = globalThis.innerWidth
569
+ const click_x = event.clientX
570
+ const space_on_right = viewport_width - click_x
571
+ const space_on_left = click_x
572
+ const place_right = space_on_right >= space_on_left
573
+
574
+ structure_popup = { open: true, structure, entry, place_right }
575
+ event.stopPropagation()
576
+ }
426
577
  }
578
+ }
427
579
  }
428
- }
429
- // Fullscreen handling
430
- $effect(() => {
431
- setup_fullscreen_effect(fullscreen, wrapper);
432
- set_fullscreen_bg(wrapper, fullscreen, `--hull-2d-bg-fullscreen`);
433
- });
434
- let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#0072B2`};
580
+ }
581
+
582
+ // Fullscreen handling
583
+ $effect(() => {
584
+ setup_fullscreen_effect(fullscreen, wrapper)
585
+ set_fullscreen_bg(wrapper, fullscreen, `--hull-2d-bg-fullscreen`)
586
+ })
587
+
588
+ let style = $derived(
589
+ `--hull-stable-color:${merged_config.colors?.stable || `#0072B2`};
435
590
  --hull-unstable-color:${merged_config.colors?.unstable || `#E69F00`};
436
591
  --hull-edge-color:${merged_config.colors?.edge || `var(--text-color, #212121)`};
437
- --hull-text-color:${merged_config.colors?.annotation || `var(--text-color, #212121)`};`);
592
+ --hull-text-color:${
593
+ merged_config.colors?.annotation || `var(--text-color, #212121)`
594
+ };`,
595
+ )
438
596
  </script>
439
597
 
440
598
  <svelte:document
@@ -592,7 +750,7 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
592
750
  selected_entry,
593
751
  })}
594
752
  <h3 style="position: absolute; left: 1em; top: 1ex; margin: 0">
595
- {@html merged_controls.title || phase_stats?.chemical_system || ``}
753
+ {@html sanitize_html(merged_controls.title || phase_stats?.chemical_system || ``)}
596
754
  </h3>
597
755
 
598
756
  <ClickFeedback