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.
- package/dist/EmptyState.svelte +10 -2
- package/dist/FilePicker.svelte +154 -96
- package/dist/Icon.svelte +20 -14
- package/dist/MillerIndexInput.svelte +27 -21
- package/dist/api/optimade.js +6 -6
- package/dist/app.css +216 -178
- package/dist/brillouin/BrillouinZone.svelte +299 -198
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
- package/dist/brillouin/BrillouinZoneExportPane.svelte +74 -55
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
- package/dist/brillouin/BrillouinZoneScene.svelte +277 -165
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
- package/dist/brillouin/compute.js +11 -6
- package/dist/chempot-diagram/ChemPotDiagram.svelte +327 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +847 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +3194 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +11 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
- package/dist/chempot-diagram/async-compute.svelte.js +77 -0
- package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
- package/dist/chempot-diagram/chempot-worker.js +11 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +32 -0
- package/dist/chempot-diagram/compute.d.ts +48 -0
- package/dist/chempot-diagram/compute.js +812 -0
- package/dist/chempot-diagram/index.d.ts +6 -0
- package/dist/chempot-diagram/index.js +6 -0
- package/dist/chempot-diagram/pointer.d.ts +16 -0
- package/dist/chempot-diagram/pointer.js +40 -0
- package/dist/chempot-diagram/temperature.d.ts +15 -0
- package/dist/chempot-diagram/temperature.js +36 -0
- package/dist/chempot-diagram/types.d.ts +86 -0
- package/dist/chempot-diagram/types.js +28 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +9 -3
- package/dist/composition/BarChart.svelte +141 -77
- package/dist/composition/BubbleChart.svelte +107 -52
- package/dist/composition/Composition.svelte +100 -79
- package/dist/composition/Formula.svelte +108 -62
- package/dist/composition/FormulaFilter.svelte +973 -353
- package/dist/composition/FormulaFilter.svelte.d.ts +35 -1
- package/dist/composition/PieChart.svelte +199 -99
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/composition/format.d.ts +5 -0
- package/dist/composition/format.js +20 -3
- package/dist/composition/parse.js +14 -9
- package/dist/convex-hull/ConvexHull.svelte +93 -38
- package/dist/convex-hull/ConvexHull2D.svelte +551 -393
- package/dist/convex-hull/ConvexHull3D.svelte +1303 -825
- package/dist/convex-hull/ConvexHull4D.svelte +1012 -686
- package/dist/convex-hull/ConvexHullControls.svelte +115 -28
- package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
- package/dist/convex-hull/ConvexHullStats.svelte +821 -249
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +41 -16
- package/dist/convex-hull/GasPressureControls.svelte +104 -61
- package/dist/convex-hull/StructurePopup.svelte +25 -4
- package/dist/convex-hull/TemperatureSlider.svelte +45 -25
- package/dist/convex-hull/barycentric-coords.js +13 -7
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +40 -0
- package/dist/convex-hull/gas-thermodynamics.js +17 -12
- package/dist/convex-hull/helpers.d.ts +10 -1
- package/dist/convex-hull/helpers.js +79 -38
- package/dist/convex-hull/index.d.ts +1 -0
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +163 -69
- package/dist/convex-hull/types.d.ts +12 -12
- package/dist/convex-hull/types.js +0 -12
- package/dist/coordination/CoordinationBarPlot.svelte +232 -176
- package/dist/element/BohrAtom.svelte +56 -13
- package/dist/element/ElementHeading.svelte +7 -2
- package/dist/element/ElementPhoto.svelte +15 -9
- package/dist/element/ElementStats.svelte +10 -4
- package/dist/element/ElementTile.svelte +137 -73
- package/dist/element/Nucleus.svelte +39 -11
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/feedback/ClickFeedback.svelte +16 -5
- package/dist/feedback/DragOverlay.svelte +10 -2
- package/dist/feedback/Spinner.svelte +4 -2
- package/dist/feedback/StatusMessage.svelte +8 -2
- package/dist/fermi-surface/FermiSlice.svelte +118 -88
- package/dist/fermi-surface/FermiSurface.svelte +336 -239
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
- package/dist/fermi-surface/FermiSurfaceScene.svelte +536 -343
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
- package/dist/fermi-surface/compute.js +16 -20
- package/dist/fermi-surface/parse.js +37 -33
- package/dist/fermi-surface/symmetry.js +2 -7
- package/dist/fermi-surface/types.d.ts +3 -5
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1527 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +30 -0
- package/dist/heatmap-matrix/index.d.ts +53 -0
- package/dist/heatmap-matrix/index.js +100 -0
- package/dist/heatmap-matrix/shared.d.ts +2 -0
- package/dist/heatmap-matrix/shared.js +4 -0
- package/dist/icons.d.ts +111 -0
- package/dist/icons.js +158 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -2
- package/dist/io/decompress.js +1 -1
- package/dist/io/export.d.ts +3 -0
- package/dist/io/export.js +138 -140
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +43 -0
- package/dist/io/index.d.ts +2 -2
- package/dist/io/index.js +2 -112
- package/dist/io/is-binary.js +2 -3
- package/dist/io/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +117 -0
- package/dist/isosurface/Isosurface.svelte +220 -110
- package/dist/isosurface/IsosurfaceControls.svelte +65 -28
- package/dist/isosurface/parse.js +104 -56
- package/dist/isosurface/slice.d.ts +2 -1
- package/dist/isosurface/slice.js +8 -13
- package/dist/isosurface/types.d.ts +14 -1
- package/dist/isosurface/types.js +152 -5
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +12 -8
- package/dist/layout/FullscreenToggle.svelte +11 -2
- package/dist/layout/InfoCard.svelte +38 -6
- package/dist/layout/InfoTag.svelte +125 -94
- package/dist/layout/PropertyFilter.svelte +82 -37
- package/dist/layout/SettingsSection.svelte +85 -55
- package/dist/layout/SubpageGrid.svelte +82 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +266 -223
- package/dist/layout/json-tree/JsonTree.svelte +516 -429
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +281 -173
- package/dist/layout/json-tree/types.d.ts +10 -2
- package/dist/layout/json-tree/utils.d.ts +2 -0
- package/dist/layout/json-tree/utils.js +37 -2
- package/dist/marching-cubes.js +25 -2
- package/dist/math.d.ts +20 -17
- package/dist/math.js +474 -57
- package/dist/overlays/ContextMenu.svelte +66 -40
- package/dist/overlays/DraggablePane.svelte +331 -154
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/periodic-table/PeriodicTable.svelte +278 -145
- package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
- package/dist/periodic-table/PropertySelect.svelte +25 -7
- package/dist/periodic-table/TableInset.svelte +8 -3
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +559 -267
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +131 -51
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +160 -110
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +8 -1
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +217 -86
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
- package/dist/phase-diagram/build-diagram.js +9 -9
- package/dist/phase-diagram/colors.js +1 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/parse.js +10 -9
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +869 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +8 -4
- package/dist/phase-diagram/utils.js +219 -74
- package/dist/plot/AxisLabel.svelte +51 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +1461 -768
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +33 -6
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +533 -383
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ColorScaleSelect.svelte +28 -7
- package/dist/plot/ElementScatter.svelte +38 -16
- package/dist/plot/FillArea.svelte +152 -92
- package/dist/plot/Histogram.svelte +1162 -709
- package/dist/plot/Histogram.svelte.d.ts +1 -1
- package/dist/plot/HistogramControls.svelte +81 -18
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte +34 -11
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/Line.svelte +63 -28
- package/dist/plot/PlotControls.svelte +221 -96
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +174 -91
- package/dist/plot/PlotTooltip.svelte +45 -6
- package/dist/plot/PortalSelect.svelte +175 -146
- package/dist/plot/ReferenceLine.svelte +77 -22
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferenceLine3D.svelte +132 -107
- package/dist/plot/ReferencePlane.svelte +146 -123
- package/dist/plot/ScatterPlot.svelte +1880 -1156
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte +256 -131
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +300 -297
- package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
- package/dist/plot/ScatterPlot3DScene.svelte +608 -406
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +150 -70
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ScatterPoint.svelte +98 -26
- package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
- package/dist/plot/SpacegroupBarPlot.svelte +142 -85
- package/dist/plot/Surface3D.svelte +159 -108
- package/dist/plot/ZeroLines.svelte +96 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +23 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/axis-utils.js +1 -3
- package/dist/plot/data-cleaning.js +12 -28
- package/dist/plot/data-transform.js +2 -1
- package/dist/plot/fill-utils.js +2 -0
- package/dist/plot/index.d.ts +6 -2
- package/dist/plot/index.js +6 -2
- package/dist/plot/interactions.d.ts +8 -10
- package/dist/plot/interactions.js +2 -3
- package/dist/plot/layout.d.ts +11 -2
- package/dist/plot/layout.js +44 -17
- package/dist/plot/reference-line.d.ts +5 -22
- package/dist/plot/reference-line.js +12 -84
- package/dist/plot/scales.js +24 -36
- package/dist/plot/types.d.ts +53 -40
- package/dist/plot/types.js +12 -7
- package/dist/plot/utils/label-placement.d.ts +32 -15
- package/dist/plot/utils/label-placement.js +227 -63
- package/dist/plot/utils/series-visibility.js +2 -3
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +173 -132
- package/dist/rdf/calc-rdf.js +4 -5
- package/dist/sanitize.d.ts +4 -0
- package/dist/sanitize.js +107 -0
- package/dist/settings.d.ts +21 -6
- package/dist/settings.js +63 -19
- package/dist/spectral/Bands.svelte +963 -412
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/BandsAndDos.svelte +90 -49
- package/dist/spectral/BrillouinBandsDos.svelte +151 -93
- package/dist/spectral/Dos.svelte +389 -258
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +119 -51
- package/dist/spectral/types.d.ts +2 -0
- package/dist/state.svelte.d.ts +1 -1
- package/dist/state.svelte.js +3 -2
- package/dist/structure/Arrow.svelte +59 -20
- package/dist/structure/AtomLegend.svelte +231 -129
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/Bond.svelte +73 -47
- package/dist/structure/CanvasTooltip.svelte +10 -2
- package/dist/structure/CellSelect.svelte +148 -51
- package/dist/structure/Cylinder.svelte +33 -17
- package/dist/structure/Lattice.svelte +88 -33
- package/dist/structure/Structure.svelte +1077 -821
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureControls.svelte +373 -139
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureExportPane.svelte +124 -89
- package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +304 -231
- package/dist/structure/StructureScene.svelte +919 -445
- package/dist/structure/StructureScene.svelte.d.ts +16 -7
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +42 -29
- package/dist/structure/bonding.js +6 -7
- package/dist/structure/export.js +22 -34
- package/dist/structure/ferrox-wasm-types.d.ts +3 -2
- package/dist/structure/ferrox-wasm-types.js +0 -3
- package/dist/structure/ferrox-wasm.d.ts +3 -2
- package/dist/structure/ferrox-wasm.js +2 -3
- package/dist/structure/index.d.ts +16 -0
- package/dist/structure/index.js +88 -6
- package/dist/structure/measure.d.ts +2 -2
- package/dist/structure/measure.js +4 -44
- package/dist/structure/parse.js +130 -155
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +99 -0
- package/dist/structure/pbc.d.ts +1 -0
- package/dist/structure/pbc.js +16 -6
- package/dist/structure/supercell.d.ts +2 -2
- package/dist/structure/supercell.js +12 -22
- package/dist/structure/validation.js +5 -3
- package/dist/symmetry/SymmetryStats.svelte +94 -37
- package/dist/symmetry/WyckoffTable.svelte +42 -14
- package/dist/symmetry/cell-transform.js +5 -3
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +87 -21
- package/dist/symmetry/spacegroups.js +148 -148
- package/dist/table/HeatmapTable.svelte +1112 -516
- package/dist/table/HeatmapTable.svelte.d.ts +12 -1
- package/dist/table/ToggleMenu.svelte +125 -90
- package/dist/table/index.d.ts +2 -0
- package/dist/table/index.js +2 -4
- package/dist/theme/ThemeControl.svelte +21 -12
- package/dist/time.js +4 -1
- package/dist/tooltip/TooltipContent.svelte +33 -8
- package/dist/trajectory/Trajectory.svelte +889 -687
- package/dist/trajectory/TrajectoryError.svelte +14 -3
- package/dist/trajectory/TrajectoryExportPane.svelte +148 -90
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +13 -31
- package/dist/trajectory/format-detect.d.ts +9 -0
- package/dist/trajectory/format-detect.js +76 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +332 -0
- package/dist/trajectory/helpers.d.ts +14 -0
- package/dist/trajectory/helpers.js +172 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +23 -14
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +77 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +129 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +299 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +179 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +68 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +110 -0
- package/dist/trajectory/plotting.js +13 -8
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +17 -0
- package/dist/xrd/XrdPlot.svelte +337 -245
- package/dist/xrd/broadening.js +14 -9
- package/dist/xrd/calc-xrd.js +12 -19
- package/dist/xrd/parse.d.ts +1 -1
- package/dist/xrd/parse.js +17 -17
- package/package.json +103 -101
- package/readme.md +4 -4
- package/dist/trajectory/parse.d.ts +0 -42
- package/dist/trajectory/parse.js +0 -1267
- /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
- /package/dist/theme/{themes.js → themes.mjs} +0 -0
|
@@ -1,240 +1,475 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const
|
|
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 (
|
|
157
|
-
|
|
158
|
-
|
|
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,
|
|
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
|
-
|
|
167
|
-
|
|
393
|
+
? find_nearby_special_point(svg_x, svg_y)
|
|
394
|
+
: null
|
|
395
|
+
|
|
168
396
|
if (region) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
205
|
-
|
|
435
|
+
event.preventDefault()
|
|
436
|
+
handle_click()
|
|
206
437
|
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// Component labels (guard for initial render when data may be undefined)
|
|
221
|
-
const component_a = $derived(
|
|
222
|
-
const component_b = $derived(
|
|
223
|
-
const comp_unit = $derived(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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={
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
|
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?.
|
|
424
|
-
{@const
|
|
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
|
|
427
|
-
{@const
|
|
428
|
-
{@const
|
|
429
|
-
{@
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
{
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
|
545
|
-
{@html
|
|
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={
|
|
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
|
|
588
|
-
{@html
|
|
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 {
|