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,240 +1,475 @@
1
- <script lang="ts">import { format_num } from '../labels';
2
- import { FullscreenToggle, set_fullscreen_bg, setup_fullscreen_effect, } from '../layout';
3
- import { compute_bounding_box_2d, polygon_centroid } from '../math';
4
- import { constrain_tooltip_position } from '../plot/layout';
5
- import { scaleLinear } from 'd3-scale';
6
- import PhaseDiagramControls from './PhaseDiagramControls.svelte';
7
- import PhaseDiagramExportPane from './PhaseDiagramExportPane.svelte';
8
- import PhaseDiagramTooltip from './PhaseDiagramTooltip.svelte';
9
- import { calculate_lever_rule, compute_label_properties, convert_temp, find_phase_at_point, format_composition, format_formula_svg, format_hover_info_text, generate_boundary_path, generate_region_path, get_multi_phase_gradient, get_phase_color, merge_phase_diagram_config, PHASE_COLOR_RGB, transform_vertices, } from './utils';
10
- let { data, config = $bindable({}), on_phase_hover, fullscreen = $bindable(false), wrapper = $bindable(), hovered_region = $bindable(null), show_boundaries = $bindable(true), show_labels = $bindable(true), show_special_points = $bindable(true), show_grid = $bindable(true), show_component_labels = $bindable(true), fullscreen_toggle = true, enable_export = true, show_controls = true, display_temp_unit = $bindable(), controls_open = $bindable(false), controls_props = {}, export_pane_open = $bindable(false), png_dpi = $bindable(150), export_filename = `phase-diagram`, x_axis = $bindable({}), y_axis = $bindable({}), tooltip, children, ...rest } = $props();
11
- // Merge config with centralized defaults using shared helper
12
- const merged_config = $derived(merge_phase_diagram_config(config));
13
- // Dimensions - use container size directly, no fallback to avoid layout shift
14
- let width = $state(0);
15
- let height = $state(0);
16
- // Margin from config
17
- const margin = $derived(merged_config.margin);
18
- // Pre-computed plot edges to avoid repeated calculations
19
- const left = $derived(margin.l);
20
- const right = $derived(width - margin.r);
21
- const top = $derived(margin.t);
22
- const bottom = $derived(height - margin.b);
23
- const plot_width = $derived(right - left);
24
- const plot_height = $derived(bottom - top);
25
- // Scales
26
- const x_scale = $derived(scaleLinear().domain([0, 1]).range([left, right]));
27
- // Temperature units (guard for initial render when data may be undefined)
28
- const data_temp_unit = $derived((data?.temperature_unit ?? `K`));
29
- const temp_unit = $derived(display_temp_unit ?? data_temp_unit);
30
- const temp_range = $derived(data?.temperature_range ?? [0, 1000]);
31
- // Convert temperature range for display
32
- const display_temp_range = $derived([
1
+ <script lang="ts">
2
+ import { format_num } from '../labels'
3
+ import {
4
+ FullscreenToggle,
5
+ set_fullscreen_bg,
6
+ setup_fullscreen_effect,
7
+ } from '../layout'
8
+ import { sanitize_svg } from '../sanitize'
9
+ import { compute_bounding_box_2d, polygon_centroid, type Vec2 } from '../math'
10
+ import type { AxisConfig } from '../plot'
11
+ import { constrain_tooltip_position } from '../plot/layout'
12
+ import { scaleLinear } from 'd3-scale'
13
+ import type { ComponentProps, Snippet } from 'svelte'
14
+ import type { HTMLAttributes } from 'svelte/elements'
15
+ import { build_diagram } from './build-diagram'
16
+ import type { DiagramInput } from './diagram-input'
17
+ import PhaseDiagramControls from './PhaseDiagramControls.svelte'
18
+ import PhaseDiagramEditorPane from './PhaseDiagramEditorPane.svelte'
19
+ import PhaseDiagramExportPane from './PhaseDiagramExportPane.svelte'
20
+ import PhaseDiagramTooltip from './PhaseDiagramTooltip.svelte'
21
+ import { parse_phase_diagram_svg } from './svg-to-diagram'
22
+ import type {
23
+ LeverRuleMode,
24
+ PhaseDiagramConfig,
25
+ PhaseDiagramData,
26
+ PhaseDiagramTooltipConfig,
27
+ PhaseHoverInfo,
28
+ PhaseRegion,
29
+ TempUnit,
30
+ } from './types'
31
+ import {
32
+ calculate_lever_rule,
33
+ calculate_vertical_lever_rule,
34
+ compute_label_properties,
35
+ compute_x_domain,
36
+ convert_temp,
37
+ find_phase_at_point,
38
+ format_composition,
39
+ format_formula_svg,
40
+ format_hover_info_text,
41
+ format_label_svg,
42
+ generate_boundary_path,
43
+ generate_region_path,
44
+ get_multi_phase_gradient,
45
+ get_phase_color,
46
+ merge_phase_diagram_config,
47
+ PHASE_COLOR_RGB,
48
+ transform_vertices,
49
+ } from './utils'
50
+
51
+ type Props = HTMLAttributes<HTMLDivElement> & {
52
+ data: PhaseDiagramData
53
+ config?: Partial<PhaseDiagramConfig>
54
+ // Hover callback
55
+ on_phase_hover?: (info: PhaseHoverInfo | null) => void
56
+ // Bindable state
57
+ fullscreen?: boolean
58
+ wrapper?: HTMLDivElement
59
+ hovered_region?: PhaseRegion | null
60
+ // Display options
61
+ show_boundaries?: boolean
62
+ show_labels?: boolean
63
+ show_special_points?: boolean
64
+ show_grid?: boolean
65
+ show_component_labels?: boolean
66
+ fullscreen_toggle?: boolean
67
+ enable_export?: boolean
68
+ show_controls?: boolean
69
+ // Temperature display unit (can differ from data.temperature_unit)
70
+ display_temp_unit?: `K` | `°C` | `°F`
71
+ // Controls pane
72
+ controls_open?: boolean
73
+ controls_props?: Partial<ComponentProps<typeof PhaseDiagramControls>>
74
+ // Export options
75
+ export_pane_open?: boolean
76
+ png_dpi?: number
77
+ export_filename?: string
78
+ // Lever rule mode (horizontal = composition tie-line, vertical = temperature tie-line)
79
+ lever_rule_mode?: LeverRuleMode
80
+ // Diagram input editor (for SVG drop editing)
81
+ diagram_input?: DiagramInput | null
82
+ editor_open?: boolean
83
+ // Axis configuration
84
+ x_axis?: AxisConfig
85
+ y_axis?: AxisConfig
86
+ // Custom tooltip - can be a snippet (replaces default), config object (adds prefix/suffix),
87
+ // or false to disable tooltip entirely
88
+ tooltip?: Snippet<[PhaseHoverInfo]> | PhaseDiagramTooltipConfig | false
89
+ children?: Snippet<
90
+ [{ width: number; height: number; fullscreen: boolean }]
91
+ >
92
+ }
93
+
94
+ let {
95
+ data,
96
+ config = $bindable({}),
97
+ on_phase_hover,
98
+ fullscreen = $bindable(false),
99
+ wrapper = $bindable(),
100
+ hovered_region = $bindable(null),
101
+ show_boundaries = $bindable(true),
102
+ show_labels = $bindable(true),
103
+ show_special_points = $bindable(true),
104
+ show_grid = $bindable(true),
105
+ show_component_labels = $bindable(true),
106
+ fullscreen_toggle = true,
107
+ enable_export = true,
108
+ show_controls = true,
109
+ display_temp_unit = $bindable(),
110
+ controls_open = $bindable(false),
111
+ controls_props = {},
112
+ export_pane_open = $bindable(false),
113
+ png_dpi = $bindable(150),
114
+ export_filename = `phase-diagram`,
115
+ lever_rule_mode = $bindable(`horizontal`),
116
+ diagram_input = $bindable<DiagramInput | null>(null),
117
+ editor_open = $bindable(false),
118
+ x_axis = $bindable({}),
119
+ y_axis = $bindable({}),
120
+ tooltip,
121
+ children,
122
+ ...rest
123
+ }: Props = $props()
124
+
125
+ // Shared icon/toggle styling for controls and export panes
126
+ const pane_icon_style = `width: 14px; height: 14px`
127
+ const pane_toggle_props = { style: `padding: 0` }
128
+
129
+ // Rebuild diagram data when diagram_input changes ($derived auto-recomputes)
130
+ const rebuilt_data = $derived.by(() => {
131
+ if (!diagram_input) return null
132
+ try {
133
+ return build_diagram(diagram_input)
134
+ } catch (error) {
135
+ console.warn(`Failed to rebuild diagram from input:`, error)
136
+ return null
137
+ }
138
+ })
139
+
140
+ // Override from direct PhaseDiagramData edits in the editor pane
141
+ let data_override = $state<PhaseDiagramData | null>(null)
142
+
143
+ // Clear data_override when source data changes (e.g. new SVG dropped or data prop updated)
144
+ $effect(() => {
145
+ if (diagram_input || data) data_override = null
146
+ })
147
+
148
+ // Use editor override first (clears rebuilt_data path), then rebuilt, then data prop
149
+ const effective_data = $derived(data_override ?? rebuilt_data ?? data)
150
+
151
+ // Handle SVG file drop directly on the component
152
+ function handle_svg_drop(event: DragEvent) {
153
+ event.preventDefault()
154
+ const file = event.dataTransfer?.files[0]
155
+ if (!file || (!file.name.endsWith(`.svg`) && file.type !== `image/svg+xml`)) {
156
+ return
157
+ }
158
+ const reader = new FileReader()
159
+ reader.addEventListener(`load`, () => {
160
+ try {
161
+ diagram_input = parse_phase_diagram_svg(reader.result as string)
162
+ } catch (error) {
163
+ console.error(`Failed to parse dropped SVG:`, error)
164
+ }
165
+ })
166
+ reader.readAsText(file)
167
+ }
168
+
169
+ // Merge config with centralized defaults using shared helper
170
+ const merged_config = $derived(merge_phase_diagram_config(config))
171
+
172
+ // Dimensions - use container size directly, no fallback to avoid layout shift
173
+ let width = $state(0)
174
+ let height = $state(0)
175
+
176
+ // Margin from config
177
+ const margin = $derived(merged_config.margin)
178
+
179
+ // Pre-computed plot edges to avoid repeated calculations
180
+ const left = $derived(margin.l)
181
+ const right = $derived(width - margin.r)
182
+ const top = $derived(margin.t)
183
+ const bottom = $derived(height - margin.b)
184
+ const plot_width = $derived(right - left)
185
+ const plot_height = $derived(bottom - top)
186
+
187
+ // Compute x domain from data extent, x_axis.range override, or default [0, 1]
188
+ // Auto-extends to 0/1 when edge regions contain a pure component
189
+ const x_domain = $derived(compute_x_domain(x_axis.range, effective_data))
190
+
191
+ // Scales
192
+ const x_scale = $derived(scaleLinear().domain(x_domain).range([left, right]))
193
+
194
+ // Temperature units (guard for initial render when data may be undefined)
195
+ const data_temp_unit = $derived<TempUnit>(
196
+ (effective_data?.temperature_unit ?? `K`) as TempUnit,
197
+ )
198
+ const temp_unit = $derived<TempUnit>(display_temp_unit ?? data_temp_unit)
199
+ const temp_range = $derived(effective_data?.temperature_range ?? [0, 1000])
200
+
201
+ // Convert temperature range for display
202
+ const display_temp_range = $derived<Vec2>([
33
203
  convert_temp(temp_range[0], data_temp_unit, temp_unit),
34
204
  convert_temp(temp_range[1], data_temp_unit, temp_unit),
35
- ]);
36
- // y_scale maps data temperatures to SVG coordinates
37
- // We keep this in data units so region vertices render correctly
38
- const y_scale = $derived(scaleLinear().domain(temp_range).range([bottom, top]));
39
- // y_scale_display maps display temperatures (after unit conversion) to SVG
40
- // Used for axis labels and ticks
41
- const y_scale_display = $derived(scaleLinear().domain(display_temp_range).range([bottom, top]));
42
- // Generate tick values using d3 scale's built-in ticks method
43
- const x_tick_count = $derived(typeof x_axis.ticks === `number` ? x_axis.ticks : 5);
44
- const y_tick_count = $derived(typeof y_axis.ticks === `number` ? y_axis.ticks : 6);
45
- const x_ticks = $derived(x_scale.ticks(x_tick_count));
46
- // Use display scale for y ticks so they show converted temperatures
47
- const y_ticks = $derived(y_scale_display.ticks(y_tick_count));
48
- // Transform regions to SVG coordinates
49
- const transformed_regions = $derived((data?.regions ?? []).map((region) => {
50
- const svg_vertices = transform_vertices(region.vertices, x_scale, y_scale);
51
- const { width, height } = compute_bounding_box_2d(svg_vertices);
52
- const label_props = compute_label_properties(region.name, { width, height }, merged_config.font_size);
53
- // Get gradient stops for multi-phase regions (2+, supports 3+ phases)
54
- const gradient = get_multi_phase_gradient(region.name);
55
- const x_coords = svg_vertices.map(([vx]) => vx);
56
- return {
205
+ ])
206
+
207
+ // y_scale maps data temperatures to SVG coordinates
208
+ // We keep this in data units so region vertices render correctly
209
+ const y_scale = $derived(
210
+ scaleLinear().domain(temp_range).range([bottom, top]),
211
+ )
212
+
213
+ // y_scale_display maps display temperatures (after unit conversion) to SVG
214
+ // Used for axis labels and ticks
215
+ const y_scale_display = $derived(
216
+ scaleLinear().domain(display_temp_range).range([bottom, top]),
217
+ )
218
+
219
+ // Generate tick values using d3 scale's built-in ticks method
220
+ const x_ticks = $derived(
221
+ x_scale.ticks(typeof x_axis.ticks === `number` ? x_axis.ticks : 5),
222
+ )
223
+ // Use display scale for y ticks so they show converted temperatures
224
+ const y_ticks = $derived(
225
+ y_scale_display.ticks(typeof y_axis.ticks === `number` ? y_axis.ticks : 6),
226
+ )
227
+
228
+ // Transform regions to SVG coordinates
229
+ const transformed_regions = $derived(
230
+ (effective_data?.regions ?? []).map((region) => {
231
+ const svg_vertices = transform_vertices(region.vertices, x_scale, y_scale)
232
+ const { width, height } = compute_bounding_box_2d(svg_vertices)
233
+ const label_props = compute_label_properties(
234
+ region.name,
235
+ { width, height },
236
+ merged_config.font_size,
237
+ )
238
+ // Get gradient stops for multi-phase regions (2+, supports 3+ phases)
239
+ const gradient = get_multi_phase_gradient(region.name)
240
+ const x_coords = svg_vertices.map(([vx]) => vx)
241
+ return {
57
242
  ...region,
58
243
  svg_path: generate_region_path(svg_vertices),
59
244
  label_pos: region.label_position
60
- ? [x_scale(region.label_position[0]), y_scale(region.label_position[1])]
61
- : polygon_centroid(svg_vertices),
245
+ ? [x_scale(region.label_position[0]), y_scale(region.label_position[1])]
246
+ : polygon_centroid(svg_vertices),
62
247
  label_rotation: label_props.rotation,
63
248
  label_lines: label_props.lines,
64
249
  label_scale: label_props.scale,
65
250
  gradient,
66
251
  x_min: Math.min(...x_coords),
67
252
  x_max: Math.max(...x_coords),
68
- };
69
- }));
70
- // Transform boundaries to SVG coordinates
71
- const transformed_boundaries = $derived((data?.boundaries ?? []).map((boundary) => ({
72
- ...boundary,
73
- svg_path: generate_boundary_path(transform_vertices(boundary.points, x_scale, y_scale)),
74
- })));
75
- // Transform special points to SVG coordinates
76
- const transformed_special_points = $derived((data?.special_points ?? []).map((point) => ({
77
- ...point,
78
- svg_x: x_scale(point.position[0]),
79
- svg_y: y_scale(point.position[1]),
80
- })));
81
- // Hover state
82
- let hover_info = $state(null);
83
- // Locked tooltip state (click to lock, click again to unlock)
84
- let locked_hover_info = $state(null);
85
- // Clear hover state helper (used in multiple places)
86
- function clear_hover() {
87
- hover_info = null;
88
- hovered_region = null;
89
- on_phase_hover?.(null);
90
- }
91
- // Handle click to lock/unlock tooltip
92
- function handle_click() {
253
+ }
254
+ }),
255
+ )
256
+
257
+ // Transform boundaries to SVG coordinates
258
+ const transformed_boundaries = $derived(
259
+ (effective_data?.boundaries ?? []).map((boundary) => ({
260
+ ...boundary,
261
+ svg_path: generate_boundary_path(
262
+ transform_vertices(boundary.points, x_scale, y_scale),
263
+ ),
264
+ })),
265
+ )
266
+
267
+ // Transform special points to SVG coordinates
268
+ const transformed_special_points = $derived(
269
+ (effective_data?.special_points ?? []).map((point) => ({
270
+ ...point,
271
+ svg_x: x_scale(point.position[0]),
272
+ svg_y: y_scale(point.position[1]),
273
+ })),
274
+ )
275
+
276
+ // Hover state
277
+ let hover_info = $state<PhaseHoverInfo | null>(null)
278
+ // Locked tooltip state (click to lock, click again to unlock)
279
+ let locked_hover_info = $state<PhaseHoverInfo | null>(null)
280
+
281
+ // Clear hover state helper (used in multiple places)
282
+ function clear_hover() {
283
+ hover_info = null
284
+ hovered_region = null
285
+ on_phase_hover?.(null)
286
+ }
287
+
288
+ // Handle click to lock/unlock tooltip
289
+ function handle_click() {
93
290
  if (locked_hover_info) {
94
- // Unlock if already locked
95
- locked_hover_info = null;
96
- }
97
- else if (hover_info) {
98
- // Lock current hover info
99
- locked_hover_info = { ...hover_info };
291
+ // Unlock if already locked
292
+ locked_hover_info = null
293
+ } else if (hover_info) {
294
+ // Lock current hover info
295
+ locked_hover_info = { ...hover_info }
100
296
  }
101
- }
102
- // Effective hover info - locked takes precedence
103
- const effective_hover_info = $derived(locked_hover_info ?? hover_info);
104
- // Copy feedback state
105
- let copy_feedback_visible = $state(false);
106
- let copy_feedback_pos = $state({ x: 0, y: 0 });
107
- let copy_feedback_timeout;
108
- // Handle double-click to copy tooltip data
109
- async function handle_double_click(event) {
110
- if (!hover_info)
111
- return;
297
+ }
298
+
299
+ // Effective hover info - locked takes precedence
300
+ const effective_hover_info = $derived(locked_hover_info ?? hover_info)
301
+
302
+ // Copy feedback state
303
+ let copy_feedback_visible = $state(false)
304
+ let copy_feedback_pos = $state({ x: 0, y: 0 })
305
+ let copy_feedback_timeout: ReturnType<typeof setTimeout> | undefined
306
+
307
+ // Handle double-click to copy tooltip data
308
+ async function handle_double_click(event: MouseEvent) {
309
+ if (!hover_info) return
112
310
  try {
113
- await navigator.clipboard.writeText(format_hover_info_text(hover_info, temp_unit, comp_unit, component_a, component_b));
114
- if (copy_feedback_timeout)
115
- clearTimeout(copy_feedback_timeout);
116
- copy_feedback_pos = { x: event.clientX, y: event.clientY };
117
- copy_feedback_visible = true;
118
- copy_feedback_timeout = setTimeout(() => {
119
- copy_feedback_visible = false;
120
- copy_feedback_timeout = undefined;
121
- }, 1500);
122
- }
123
- catch (err) {
124
- console.error(`Failed to copy phase data:`, err);
311
+ await navigator.clipboard.writeText(
312
+ format_hover_info_text(
313
+ hover_info,
314
+ temp_unit,
315
+ comp_unit,
316
+ component_a,
317
+ component_b,
318
+ data_temp_unit,
319
+ lever_rule_mode,
320
+ ),
321
+ )
322
+ if (copy_feedback_timeout) clearTimeout(copy_feedback_timeout)
323
+ copy_feedback_pos = { x: event.clientX, y: event.clientY }
324
+ copy_feedback_visible = true
325
+ copy_feedback_timeout = setTimeout(() => {
326
+ copy_feedback_visible = false
327
+ copy_feedback_timeout = undefined
328
+ }, 1500)
329
+ } catch (error) {
330
+ console.error(`Failed to copy phase data:`, error)
125
331
  }
126
- }
127
- // Tooltip element reference for measuring actual size
128
- let tooltip_el = $state(null);
129
- // Tooltip positioning using shared utility (uses effective_hover_info for locked state)
130
- const tooltip_pos = $derived.by(() => {
131
- const info = effective_hover_info;
132
- if (!info)
133
- return { x: 0, y: 0 };
134
- return constrain_tooltip_position(info.position.x, info.position.y, tooltip_el?.offsetWidth ?? 200, tooltip_el?.offsetHeight ?? 150, globalThis.innerWidth ?? 1000, globalThis.innerHeight ?? 800, { offset: 15 });
135
- });
136
- // Find nearest special point within threshold (in SVG pixels)
137
- function find_nearby_special_point(svg_x, svg_y, threshold = 20) {
138
- let nearest = null;
139
- let min_dist = threshold;
332
+ }
333
+
334
+ // Tooltip element reference for measuring actual size
335
+ let tooltip_el = $state<HTMLDivElement | null>(null)
336
+
337
+ // Tooltip positioning using shared utility (uses effective_hover_info for locked state)
338
+ const tooltip_pos = $derived.by(() => {
339
+ const info = effective_hover_info
340
+ if (!info) return { x: 0, y: 0 }
341
+ return constrain_tooltip_position(
342
+ info.position.x,
343
+ info.position.y,
344
+ tooltip_el?.offsetWidth ?? 200,
345
+ tooltip_el?.offsetHeight ?? 150,
346
+ globalThis.innerWidth ?? 1000,
347
+ globalThis.innerHeight ?? 800,
348
+ { offset: 15 },
349
+ )
350
+ })
351
+
352
+ // Find nearest special point within threshold (in SVG pixels)
353
+ function find_nearby_special_point(
354
+ svg_x: number,
355
+ svg_y: number,
356
+ threshold: number = 20,
357
+ ) {
358
+ let nearest: (typeof transformed_special_points)[0] | null = null
359
+ let min_dist = threshold
140
360
  for (const point of transformed_special_points) {
141
- const dist = Math.hypot(point.svg_x - svg_x, point.svg_y - svg_y);
142
- if (dist < min_dist) {
143
- min_dist = dist;
144
- nearest = point;
145
- }
361
+ const dist = Math.hypot(point.svg_x - svg_x, point.svg_y - svg_y)
362
+ if (dist < min_dist) {
363
+ min_dist = dist
364
+ nearest = point
365
+ }
146
366
  }
147
- return nearest;
148
- }
149
- // Pointer move handler (unified mouse/touch via Pointer Events API)
150
- function handle_pointer_move(event) {
151
- const svg = event.currentTarget;
152
- const rect = svg.getBoundingClientRect();
153
- const svg_x = event.clientX - rect.left;
154
- const svg_y = event.clientY - rect.top;
367
+ return nearest
368
+ }
369
+
370
+ // Pointer move handler (unified mouse/touch via Pointer Events API)
371
+ function handle_pointer_move(event: PointerEvent) {
372
+ const svg = event.currentTarget as SVGElement
373
+ const rect = svg.getBoundingClientRect()
374
+ const svg_x = event.clientX - rect.left
375
+ const svg_y = event.clientY - rect.top
376
+
155
377
  // Check if within plot area
156
- if (svg_x < left || svg_x > right || svg_y < top || svg_y > bottom || !data) {
157
- clear_hover();
158
- return;
378
+ if (
379
+ svg_x < left || svg_x > right || svg_y < top || svg_y > bottom ||
380
+ !effective_data
381
+ ) {
382
+ clear_hover()
383
+ return
159
384
  }
385
+
160
386
  // Convert to data coordinates and find phase
161
- const composition = x_scale.invert(svg_x);
162
- const temperature = y_scale.invert(svg_y);
163
- const region = find_phase_at_point(composition, temperature, data);
387
+ const composition = x_scale.invert(svg_x)
388
+ const temperature = y_scale.invert(svg_y)
389
+ const region = find_phase_at_point(composition, temperature, effective_data)
390
+
164
391
  // Check for nearby special point
165
392
  const nearby_special = show_special_points
166
- ? find_nearby_special_point(svg_x, svg_y)
167
- : null;
393
+ ? find_nearby_special_point(svg_x, svg_y)
394
+ : null
395
+
168
396
  if (region) {
169
- hovered_region = region;
170
- hover_info = {
171
- region,
172
- composition,
173
- temperature,
174
- position: { x: event.clientX, y: event.clientY },
175
- lever_rule: calculate_lever_rule(region, composition, temperature) ||
176
- undefined,
177
- special_point: nearby_special || undefined,
178
- };
179
- on_phase_hover?.(hover_info);
180
- }
181
- else {
182
- clear_hover();
397
+ hovered_region = region
398
+ hover_info = {
399
+ region,
400
+ composition,
401
+ temperature,
402
+ position: { x: event.clientX, y: event.clientY },
403
+ lever_rule: calculate_lever_rule(region, composition, temperature) ||
404
+ undefined,
405
+ vertical_lever_rule:
406
+ calculate_vertical_lever_rule(region, composition, temperature) ||
407
+ undefined,
408
+ special_point: nearby_special || undefined,
409
+ }
410
+ on_phase_hover?.(hover_info)
411
+ } else {
412
+ clear_hover()
183
413
  }
184
- }
185
- function handle_pointer_leave(event) {
414
+ }
415
+
416
+ function handle_pointer_leave(event: PointerEvent) {
186
417
  // Don't clear on touch lift (allows reading tooltip) or when locked
187
- if (event.pointerType === `touch` || locked_hover_info)
188
- return;
189
- clear_hover();
190
- }
191
- // Document-level keyboard shortcuts
192
- function handle_doc_keydown(event) {
418
+ if (event.pointerType === `touch` || locked_hover_info) return
419
+ clear_hover()
420
+ }
421
+
422
+ // Document-level keyboard shortcuts
423
+ function handle_doc_keydown(event: KeyboardEvent) {
193
424
  if ((event.ctrlKey || event.metaKey) && event.shiftKey && event.key === `E`) {
194
- event.preventDefault();
195
- export_pane_open = !export_pane_open;
196
- }
197
- else if (event.key === `Escape` && locked_hover_info) {
198
- locked_hover_info = null;
425
+ event.preventDefault()
426
+ export_pane_open = !export_pane_open
427
+ } else if (event.key === `Escape` && locked_hover_info) {
428
+ locked_hover_info = null
199
429
  }
200
- }
201
- // SVG keyboard handler (Enter/Space to toggle lock)
202
- function handle_svg_keydown(event) {
430
+ }
431
+
432
+ // SVG keyboard handler (Enter/Space to toggle lock)
433
+ function handle_svg_keydown(event: KeyboardEvent) {
203
434
  if (event.key === `Enter` || event.key === ` `) {
204
- event.preventDefault();
205
- handle_click();
435
+ event.preventDefault()
436
+ handle_click()
206
437
  }
207
- }
208
- // Fullscreen handling
209
- $effect(() => {
210
- setup_fullscreen_effect(fullscreen, wrapper);
211
- set_fullscreen_bg(wrapper, fullscreen, `--phase-diagram-bg-fullscreen`);
212
- });
213
- // Cleanup timeout on unmount to prevent memory leaks
214
- $effect(() => {
438
+ }
439
+
440
+ // Fullscreen handling
441
+ $effect(() => {
442
+ setup_fullscreen_effect(fullscreen, wrapper)
443
+ set_fullscreen_bg(wrapper, fullscreen, `--phase-diagram-bg-fullscreen`)
444
+ })
445
+
446
+ // Cleanup timeout on unmount to prevent memory leaks
447
+ $effect(() => {
215
448
  return () => {
216
- if (copy_feedback_timeout)
217
- clearTimeout(copy_feedback_timeout);
218
- };
219
- });
220
- // Component labels (guard for initial render when data may be undefined)
221
- const component_a = $derived(data?.components?.[0] ?? ``);
222
- const component_b = $derived(data?.components?.[1] ?? ``);
223
- const comp_unit = $derived(data?.composition_unit ?? `at%`);
224
- // Pseudo-binary support: format compound names with subscripts when enabled
225
- const use_subscripts = $derived(data?.pseudo_binary?.use_subscripts ?? true);
226
- // Formatted component labels for SVG axis labels (with tspan subscripts if compound)
227
- const component_a_svg = $derived(format_formula_svg(component_a, use_subscripts));
228
- const component_b_svg = $derived(format_formula_svg(component_b, use_subscripts));
229
- // Custom axis labels from data (for pseudo-binary or special cases)
230
- const data_x_axis_label = $derived(data?.x_axis_label);
231
- const data_y_axis_label = $derived(data?.y_axis_label);
232
- // Default x-axis label as a single string (avoids mixing plain text with {@html})
233
- const default_x_axis_label = $derived.by(() => {
234
- const prefix = comp_unit === `fraction` ? `x ` : ``;
235
- const unit = comp_unit === `fraction` ? `mole fraction` : comp_unit;
236
- return `${prefix}${component_b_svg} (${unit})`;
237
- });
449
+ if (copy_feedback_timeout) clearTimeout(copy_feedback_timeout)
450
+ }
451
+ })
452
+
453
+ // Component labels (guard for initial render when data may be undefined)
454
+ const component_a = $derived(effective_data?.components?.[0] ?? ``)
455
+ const component_b = $derived(effective_data?.components?.[1] ?? ``)
456
+ const comp_unit = $derived(effective_data?.composition_unit ?? `at%`)
457
+
458
+ // Pseudo-binary support: format compound names with subscripts when enabled
459
+ const use_subscripts = $derived(
460
+ effective_data?.pseudo_binary?.use_subscripts ?? true,
461
+ )
462
+
463
+ // Formatted component labels for SVG axis labels (with tspan subscripts if compound)
464
+ const component_a_svg = $derived(format_formula_svg(component_a, use_subscripts))
465
+ const component_b_svg = $derived(format_formula_svg(component_b, use_subscripts))
466
+
467
+ // Default x-axis label as a single string (avoids mixing plain text with {@html})
468
+ const default_x_axis_label = $derived.by(() => {
469
+ const prefix = comp_unit === `fraction` ? `x ` : ``
470
+ const unit = comp_unit === `fraction` ? `mole fraction` : comp_unit
471
+ return `${prefix}${component_b_svg} (${unit})`
472
+ })
238
473
  </script>
239
474
 
240
475
  <!-- Grid lines snippet for DRY rendering -->
@@ -251,6 +486,50 @@ const default_x_axis_label = $derived.by(() => {
251
486
  {/each}
252
487
  {/snippet}
253
488
 
489
+ <!-- Tie-line snippet: renders line with white outline, phase endpoints, and cursor marker -->
490
+ {#snippet tie_line_viz(
491
+ x1: number,
492
+ y1: number,
493
+ x2: number,
494
+ y2: number,
495
+ endpoints: Array<{ cx: number; cy: number; color: string }>,
496
+ cursor_cx: number,
497
+ cursor_cy: number,
498
+ )}
499
+ {@const tl = merged_config.tie_line}
500
+ <g class="tie-line" class:locked={locked_hover_info}>
501
+ {#each [`white`, `rgb(${PHASE_COLOR_RGB.tie_line})`] as stroke (stroke)}
502
+ <line
503
+ {x1}
504
+ {y1}
505
+ {x2}
506
+ {y2}
507
+ {stroke}
508
+ stroke-width={tl.stroke_width + (stroke === `white` ? 1 : 0)}
509
+ stroke-linecap="round"
510
+ />
511
+ {/each}
512
+ {#each endpoints as ep, idx (idx)}
513
+ <circle
514
+ cx={ep.cx}
515
+ cy={ep.cy}
516
+ r={tl.endpoint_radius}
517
+ fill="rgb({ep.color})"
518
+ stroke="white"
519
+ stroke-width={1.5}
520
+ />
521
+ {/each}
522
+ <circle
523
+ cx={cursor_cx}
524
+ cy={cursor_cy}
525
+ r={tl.cursor_radius}
526
+ fill="rgb({PHASE_COLOR_RGB.tie_line})"
527
+ stroke="white"
528
+ stroke-width={2}
529
+ />
530
+ </g>
531
+ {/snippet}
532
+
254
533
  <svelte:document
255
534
  onfullscreenchange={() => {
256
535
  fullscreen = Boolean(document.fullscreenElement)
@@ -266,7 +545,9 @@ const default_x_axis_label = $derived.by(() => {
266
545
  bind:clientWidth={width}
267
546
  bind:clientHeight={height}
268
547
  role="img"
269
- aria-label={`${component_a}-${component_b} binary phase diagram`}
548
+ aria-label="{component_a}-{component_b} binary phase diagram"
549
+ ondrop={handle_svg_drop}
550
+ ondragover={(ev) => ev.preventDefault()}
270
551
  >
271
552
  {#if width > 0 && height > 0}
272
553
  <!-- Header controls -->
@@ -280,23 +561,36 @@ const default_x_axis_label = $derived.by(() => {
280
561
  bind:show_grid
281
562
  bind:show_component_labels
282
563
  bind:config
564
+ bind:lever_rule_mode
283
565
  bind:x_axis
284
566
  bind:y_axis
285
567
  bind:png_dpi
286
- {data}
568
+ data={effective_data}
287
569
  {enable_export}
288
570
  {...controls_props}
571
+ icon_style={pane_icon_style}
572
+ toggle_props={pane_toggle_props}
289
573
  />
290
574
  {/if}
291
575
  {#if enable_export}
292
576
  <PhaseDiagramExportPane
293
577
  bind:export_pane_open
294
578
  bind:png_dpi
295
- {data}
579
+ data={effective_data}
296
580
  {wrapper}
297
581
  filename={export_filename}
582
+ icon_style={pane_icon_style}
583
+ toggle_props={pane_toggle_props}
298
584
  />
299
585
  {/if}
586
+ <PhaseDiagramEditorPane
587
+ bind:editor_open
588
+ bind:diagram_input
589
+ data={effective_data}
590
+ ondata={(edited) => data_override = edited}
591
+ icon_style={pane_icon_style}
592
+ toggle_props={pane_toggle_props}
593
+ />
300
594
  {#if fullscreen_toggle}
301
595
  <FullscreenToggle bind:fullscreen />
302
596
  {/if}
@@ -382,7 +676,7 @@ const default_x_axis_label = $derived.by(() => {
382
676
  <path
383
677
  d={boundary.svg_path}
384
678
  fill="none"
385
- stroke={boundary.style?.color || merged_config.colors.boundary}
679
+ stroke={boundary.style?.color ?? merged_config.colors.boundary}
386
680
  stroke-width={boundary.style?.width || 2}
387
681
  stroke-dasharray={boundary.style?.dash || ``}
388
682
  stroke-linecap="round"
@@ -411,7 +705,7 @@ const default_x_axis_label = $derived.by(() => {
411
705
  font-weight="500"
412
706
  class="region-label"
413
707
  >
414
- {line}
708
+ {@html sanitize_svg(format_label_svg(line, use_subscripts))}
415
709
  </text>
416
710
  {/each}
417
711
  </g>
@@ -420,51 +714,40 @@ const default_x_axis_label = $derived.by(() => {
420
714
  {/if}
421
715
 
422
716
  <!-- Tie-line visualization for two-phase regions -->
423
- {#if effective_hover_info?.lever_rule}
424
- {@const info = effective_hover_info}
717
+ {#if lever_rule_mode === `vertical` && effective_hover_info?.vertical_lever_rule}
718
+ {@const vlr = effective_hover_info.vertical_lever_rule}
719
+ {@const cx = x_scale(effective_hover_info.composition)}
720
+ {@const y_bot = y_scale(vlr.bottom_temperature)}
721
+ {@const y_top = y_scale(vlr.top_temperature)}
722
+ {@render tie_line_viz(
723
+ cx,
724
+ y_bot,
725
+ cx,
726
+ y_top,
727
+ [
728
+ { cx, cy: y_bot, color: get_phase_color(vlr.bottom_phase, `rgb`) },
729
+ { cx, cy: y_top, color: get_phase_color(vlr.top_phase, `rgb`) },
730
+ ],
731
+ cx,
732
+ y_scale(effective_hover_info.temperature),
733
+ )}
734
+ {:else if lever_rule_mode === `horizontal` && effective_hover_info?.lever_rule}
425
735
  {@const lr = effective_hover_info.lever_rule}
426
- {@const y_pos = y_scale(info.temperature)}
427
- {@const x_left = x_scale(lr.left_composition)}
428
- {@const x_right = x_scale(lr.right_composition)}
429
- {@const tie_line = merged_config.tie_line}
430
- {@const endpoints = [
431
- { cx: x_left, color: get_phase_color(lr.left_phase, `rgb`) },
432
- { cx: x_right, color: get_phase_color(lr.right_phase, `rgb`) },
433
- ]}
434
- <g class="tie-line" class:locked={locked_hover_info}>
435
- <!-- Horizontal tie-line with white outline for contrast -->
436
- {#each [`white`, `rgb(${PHASE_COLOR_RGB.tie_line})`] as stroke (stroke)}
437
- <line
438
- x1={x_left}
439
- y1={y_pos}
440
- x2={x_right}
441
- y2={y_pos}
442
- {stroke}
443
- stroke-width={tie_line.stroke_width + (stroke === `white` ? 1 : 0)}
444
- stroke-linecap="round"
445
- />
446
- {/each}
447
- <!-- Phase endpoints -->
448
- {#each endpoints as { cx, color } (cx)}
449
- <circle
450
- {cx}
451
- cy={y_pos}
452
- r={tie_line.endpoint_radius}
453
- fill="rgb({color})"
454
- stroke="white"
455
- stroke-width={1.5}
456
- />
457
- {/each}
458
- <!-- Cursor position marker -->
459
- <circle
460
- cx={x_scale(info.composition)}
461
- cy={y_pos}
462
- r={tie_line.cursor_radius}
463
- fill="rgb({PHASE_COLOR_RGB.tie_line})"
464
- stroke="white"
465
- stroke-width={2}
466
- />
467
- </g>
736
+ {@const cy = y_scale(effective_hover_info.temperature)}
737
+ {@const x_l = x_scale(lr.left_composition)}
738
+ {@const x_r = x_scale(lr.right_composition)}
739
+ {@render tie_line_viz(
740
+ x_l,
741
+ cy,
742
+ x_r,
743
+ cy,
744
+ [
745
+ { cx: x_l, cy, color: get_phase_color(lr.left_phase, `rgb`) },
746
+ { cx: x_r, cy, color: get_phase_color(lr.right_phase, `rgb`) },
747
+ ],
748
+ x_scale(effective_hover_info.composition),
749
+ cy,
750
+ )}
468
751
  {/if}
469
752
 
470
753
  <!-- Special points (rendered last for highest z-index) -->
@@ -540,11 +823,11 @@ const default_x_axis_label = $derived.by(() => {
540
823
  font-size={merged_config.font_size + 2}
541
824
  >
542
825
  {#if x_axis.label}
543
- {@html x_axis.label}
544
- {:else if data_x_axis_label}
545
- {@html data_x_axis_label}
826
+ {@html sanitize_svg(x_axis.label)}
827
+ {:else if effective_data?.x_axis_label}
828
+ {@html sanitize_svg(effective_data.x_axis_label)}
546
829
  {:else}
547
- {@html default_x_axis_label}
830
+ {@html sanitize_svg(default_x_axis_label)}
548
831
  {/if}
549
832
  </text>
550
833
  </g>
@@ -577,15 +860,15 @@ const default_x_axis_label = $derived.by(() => {
577
860
  <text
578
861
  transform="rotate(-90)"
579
862
  x={-(top + plot_height / 2)}
580
- y={12}
863
+ y={16}
581
864
  text-anchor="middle"
582
865
  fill={merged_config.colors.text}
583
866
  font-size={merged_config.font_size + 2}
584
867
  >
585
868
  {#if y_axis.label}
586
- {@html y_axis.label}
587
- {:else if data_y_axis_label}
588
- {@html data_y_axis_label}
869
+ {@html sanitize_svg(y_axis.label)}
870
+ {:else if effective_data?.y_axis_label}
871
+ {@html sanitize_svg(effective_data.y_axis_label)}
589
872
  {:else}
590
873
  Temperature ({temp_unit})
591
874
  {/if}
@@ -602,7 +885,7 @@ const default_x_axis_label = $derived.by(() => {
602
885
  font-size={merged_config.font_size + 2}
603
886
  font-weight="bold"
604
887
  >
605
- {@html component_a_svg}
888
+ {@html sanitize_svg(component_a_svg)}
606
889
  </text>
607
890
  <text
608
891
  x={right}
@@ -612,7 +895,7 @@ const default_x_axis_label = $derived.by(() => {
612
895
  font-size={merged_config.font_size + 2}
613
896
  font-weight="bold"
614
897
  >
615
- {@html component_b_svg}
898
+ {@html sanitize_svg(component_b_svg)}
616
899
  </text>
617
900
  {/if}
618
901
  </svg>
@@ -636,9 +919,13 @@ const default_x_axis_label = $derived.by(() => {
636
919
  <PhaseDiagramTooltip
637
920
  hover_info={effective_hover_info}
638
921
  temperature_unit={temp_unit}
922
+ data_temperature_unit={data_temp_unit}
639
923
  composition_unit={comp_unit}
640
924
  {component_a}
641
925
  {component_b}
926
+ boundaries={effective_data?.boundaries ?? []}
927
+ {lever_rule_mode}
928
+ {use_subscripts}
642
929
  {tooltip}
643
930
  />
644
931
  {/if}
@@ -699,9 +986,14 @@ const default_x_axis_label = $derived.by(() => {
699
986
  opacity: 0;
700
987
  transition: opacity 0.2s ease;
701
988
  }
989
+ /* Keep editor toggle always visible so users discover the edit feature */
990
+ .binary-phase-diagram :global(.pd-editor-toggle) {
991
+ opacity: 1;
992
+ }
702
993
  .binary-phase-diagram:is(:hover, :focus-within)
703
994
  :is(:global(.pane-toggle), .header-controls),
704
- .binary-phase-diagram :global(.pane-toggle:is(:focus-visible, [aria-expanded='true'])) {
995
+ .binary-phase-diagram :global(.pane-toggle:is(:focus-visible, [aria-expanded='true'])),
996
+ .header-controls:has(:global(.pane-open)) {
705
997
  opacity: 1;
706
998
  }
707
999
  .phase-regions path {