matterviz 0.3.0 → 0.3.2
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/FilePicker.svelte +37 -20
- package/dist/Icon.svelte +2 -2
- package/dist/MillerIndexInput.svelte +60 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/app.css +38 -2
- package/dist/brillouin/BrillouinZone.svelte +20 -62
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
- package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +33 -0
- package/dist/chempot-diagram/compute.d.ts +38 -0
- package/dist/chempot-diagram/compute.js +650 -0
- package/dist/chempot-diagram/index.d.ts +5 -0
- package/dist/chempot-diagram/index.js +5 -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 +37 -0
- package/dist/chempot-diagram/types.d.ts +83 -0
- package/dist/chempot-diagram/types.js +27 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +4 -0
- package/dist/composition/BarChart.svelte +13 -22
- package/dist/composition/BubbleChart.svelte +5 -3
- package/dist/composition/FormulaFilter.svelte +770 -90
- package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/convex-hull/ConvexHull.svelte +14 -1
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +14 -45
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +396 -134
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +93 -42
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +94 -31
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
- package/dist/convex-hull/ConvexHullStats.svelte +697 -128
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/GasPressureControls.svelte +72 -38
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
- package/dist/convex-hull/TemperatureSlider.svelte +46 -19
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/gas-thermodynamics.js +16 -5
- package/dist/convex-hull/helpers.d.ts +7 -1
- package/dist/convex-hull/helpers.js +45 -15
- package/dist/convex-hull/index.d.ts +15 -1
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +106 -17
- package/dist/convex-hull/types.d.ts +7 -0
- package/dist/convex-hull/types.js +11 -0
- package/dist/coordination/CoordinationBarPlot.svelte +29 -46
- package/dist/element/BohrAtom.svelte +1 -1
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +21 -65
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/compute.js +1 -21
- package/dist/fermi-surface/marching-cubes.d.ts +2 -13
- package/dist/fermi-surface/marching-cubes.js +2 -519
- package/dist/fermi-surface/parse.js +17 -23
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -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 +119 -0
- package/dist/icons.js +119 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/io/export.js +15 -3
- 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/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +118 -0
- package/dist/isosurface/Isosurface.svelte +231 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +273 -0
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
- package/dist/isosurface/index.d.ts +5 -0
- package/dist/isosurface/index.js +6 -0
- package/dist/isosurface/parse.d.ts +6 -0
- package/dist/isosurface/parse.js +548 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +145 -0
- package/dist/isosurface/types.d.ts +55 -0
- package/dist/isosurface/types.js +178 -0
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +1 -0
- package/dist/layout/InfoTag.svelte +62 -62
- package/dist/layout/SubpageGrid.svelte +74 -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 +226 -53
- package/dist/layout/json-tree/JsonTree.svelte +425 -51
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +218 -97
- package/dist/layout/json-tree/types.d.ts +27 -2
- package/dist/layout/json-tree/utils.d.ts +14 -1
- package/dist/layout/json-tree/utils.js +254 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +519 -0
- package/dist/math.d.ts +8 -0
- package/dist/math.js +374 -7
- package/dist/overlays/ContextMenu.svelte +3 -2
- package/dist/overlays/DraggablePane.svelte +163 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +865 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +7 -4
- package/dist/phase-diagram/utils.js +149 -59
- package/dist/plot/AxisLabel.svelte +26 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +473 -228
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +3 -2
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +54 -54
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ElementScatter.svelte +4 -3
- package/dist/plot/FillArea.svelte +4 -1
- package/dist/plot/Histogram.svelte +320 -230
- package/dist/plot/Histogram.svelte.d.ts +2 -2
- package/dist/plot/HistogramControls.svelte +29 -10
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
- package/dist/plot/PlotControls.svelte +109 -27
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +1 -1
- package/dist/plot/PortalSelect.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferencePlane.svelte +1 -3
- package/dist/plot/ScatterPlot.svelte +343 -209
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +203 -250
- package/dist/plot/ScatterPlot3DScene.svelte +4 -7
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +95 -55
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ZeroLines.svelte +44 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +21 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/data-cleaning.js +1 -5
- 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 +10 -19
- package/dist/plot/layout.d.ts +7 -1
- package/dist/plot/layout.js +12 -4
- package/dist/plot/reference-line.d.ts +4 -21
- package/dist/plot/reference-line.js +7 -81
- package/dist/plot/types.d.ts +42 -17
- package/dist/plot/types.js +10 -0
- package/dist/plot/utils/label-placement.js +14 -11
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/index.d.ts +1 -1
- package/dist/rdf/index.js +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +37 -3
- package/dist/spectral/Bands.svelte +515 -143
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +65 -9
- package/dist/spectral/types.d.ts +2 -0
- package/dist/structure/AtomLegend.svelte +31 -10
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Lattice.svelte +2 -0
- package/dist/structure/Structure.svelte +716 -173
- package/dist/structure/Structure.svelte.d.ts +7 -2
- package/dist/structure/StructureControls.svelte +26 -14
- package/dist/structure/StructureControls.svelte.d.ts +5 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +386 -95
- package/dist/structure/StructureScene.svelte.d.ts +15 -4
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +38 -25
- package/dist/structure/export.js +10 -7
- 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 +1 -2
- package/dist/structure/index.d.ts +7 -0
- package/dist/structure/index.js +22 -0
- package/dist/structure/parse.js +19 -16
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +102 -0
- package/dist/structure/validation.js +6 -3
- package/dist/symmetry/SymmetryStats.svelte +18 -4
- package/dist/symmetry/WyckoffTable.svelte +18 -10
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +83 -18
- package/dist/table/HeatmapTable.svelte +468 -69
- package/dist/table/HeatmapTable.svelte.d.ts +13 -1
- package/dist/table/ToggleMenu.svelte +291 -44
- package/dist/table/ToggleMenu.svelte.d.ts +4 -1
- package/dist/table/index.d.ts +3 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +1 -0
- package/dist/trajectory/Trajectory.svelte +147 -145
- package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +3 -5
- 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 +339 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +187 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +11 -4
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +76 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +121 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +304 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +169 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +65 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +109 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/dist/xrd/XrdPlot.svelte +6 -4
- package/dist/xrd/calc-xrd.js +0 -1
- package/package.json +33 -23
- 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
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
<script lang="ts">import { is_dark_mode, watch_dark_mode } from '../colors';
|
|
1
|
+
<script lang="ts">import { add_alpha, AXIS_COLORS, is_dark_mode, NEG_AXIS_COLORS, PLOT_COLORS, vesta_hex, watch_dark_mode, } from '../colors';
|
|
2
2
|
import { normalize_show_controls } from '../controls';
|
|
3
|
-
import { ClickFeedback, DragOverlay } from '../feedback';
|
|
3
|
+
import { ClickFeedback, DragOverlay, Spinner } from '../feedback';
|
|
4
4
|
import Icon from '../Icon.svelte';
|
|
5
5
|
import { format_num } from '../labels';
|
|
6
6
|
import { set_fullscreen_bg, setup_fullscreen_effect, toggle_fullscreen, } from '../layout';
|
|
7
|
+
import { to_radians } from '../math';
|
|
7
8
|
import { ColorBar, PlotTooltip } from '../plot';
|
|
8
9
|
import { DEFAULTS } from '../settings';
|
|
10
|
+
import { Canvas, T } from '@threlte/core';
|
|
11
|
+
import * as extras from '@threlte/extras';
|
|
9
12
|
import { ticks } from 'd3-array';
|
|
10
13
|
import { SvelteMap } from 'svelte/reactivity';
|
|
14
|
+
import { PerspectiveCamera, WebGLRenderer } from 'three';
|
|
11
15
|
import { get_ternary_3d_coordinates, get_triangle_centroid, get_triangle_edges, get_triangle_vertical_edges, TRIANGLE_VERTICES, } from './barycentric-coords';
|
|
12
16
|
import ConvexHullControls from './ConvexHullControls.svelte';
|
|
13
17
|
import ConvexHullInfoPane from './ConvexHullInfoPane.svelte';
|
|
@@ -18,7 +22,7 @@ import { CONVEX_HULL_STYLE, default_controls, default_hull_config } from './inde
|
|
|
18
22
|
import StructurePopup from './StructurePopup.svelte';
|
|
19
23
|
import TemperatureSlider from './TemperatureSlider.svelte';
|
|
20
24
|
import * as thermo from './thermodynamics';
|
|
21
|
-
let { entries = [], controls = {}, config = {}, on_point_click, on_point_hover, fullscreen = $bindable(DEFAULTS.convex_hull.ternary.fullscreen), enable_fullscreen = true, enable_info_pane = true, wrapper = $bindable(), label_threshold = 50, show_stable = $bindable(DEFAULTS.convex_hull.ternary.show_stable), show_unstable = $bindable(DEFAULTS.convex_hull.ternary.show_unstable), show_hull_faces = $bindable(DEFAULTS.convex_hull.ternary.show_hull_faces), hull_face_opacity = $bindable(DEFAULTS.convex_hull.ternary.hull_face_opacity), color_mode = $bindable(DEFAULTS.convex_hull.ternary.color_mode), color_scale = $bindable(DEFAULTS.convex_hull.ternary.color_scale), info_pane_open = $bindable(DEFAULTS.convex_hull.ternary.info_pane_open), legend_pane_open = $bindable(DEFAULTS.convex_hull.ternary.legend_pane_open), max_hull_dist_show_phases = $bindable(DEFAULTS.convex_hull.ternary.max_hull_dist_show_phases), max_hull_dist_show_labels = $bindable(DEFAULTS.convex_hull.ternary.max_hull_dist_show_labels), show_stable_labels = $bindable(DEFAULTS.convex_hull.ternary.show_stable_labels), show_unstable_labels = $bindable(DEFAULTS.convex_hull.ternary.show_unstable_labels), on_file_drop, enable_click_selection = true, enable_structure_preview = true, energy_source_mode = $bindable(`precomputed`), phase_stats = $bindable(null), stable_entries = $bindable([]), unstable_entries = $bindable([]), highlighted_entries = $bindable([]), highlight_style = {}, selected_entry = $bindable(null), temperature = $bindable(), interpolate_temperature = true, max_interpolation_gap = 500, gas_config, gas_pressures = $bindable({}), children, tooltip, ...rest } = $props();
|
|
25
|
+
let { entries = [], controls = {}, config = {}, on_point_click, on_point_hover, fullscreen = $bindable(DEFAULTS.convex_hull.ternary.fullscreen), enable_fullscreen = true, enable_info_pane = true, wrapper = $bindable(), label_threshold = 50, show_stable = $bindable(DEFAULTS.convex_hull.ternary.show_stable), show_unstable = $bindable(DEFAULTS.convex_hull.ternary.show_unstable), show_hull_faces = $bindable(DEFAULTS.convex_hull.ternary.show_hull_faces), hull_face_opacity = $bindable(DEFAULTS.convex_hull.ternary.hull_face_opacity), hull_face_color_mode = $bindable(DEFAULTS.convex_hull.ternary.hull_face_color_mode), element_colors = vesta_hex, color_mode = $bindable(DEFAULTS.convex_hull.ternary.color_mode), color_scale = $bindable(DEFAULTS.convex_hull.ternary.color_scale), info_pane_open = $bindable(DEFAULTS.convex_hull.ternary.info_pane_open), legend_pane_open = $bindable(DEFAULTS.convex_hull.ternary.legend_pane_open), max_hull_dist_show_phases = $bindable(DEFAULTS.convex_hull.ternary.max_hull_dist_show_phases), max_hull_dist_show_labels = $bindable(DEFAULTS.convex_hull.ternary.max_hull_dist_show_labels), show_stable_labels = $bindable(DEFAULTS.convex_hull.ternary.show_stable_labels), show_unstable_labels = $bindable(DEFAULTS.convex_hull.ternary.show_unstable_labels), on_file_drop, enable_click_selection = true, enable_structure_preview = true, energy_source_mode = $bindable(`precomputed`), phase_stats = $bindable(null), stable_entries = $bindable([]), unstable_entries = $bindable([]), highlighted_entries = $bindable([]), highlight_style = {}, selected_entry = $bindable(null), temperature = $bindable(), interpolate_temperature = true, max_interpolation_gap = 500, gizmo = true, gas_config, gas_pressures = $bindable({}), children, tooltip, ...rest } = $props();
|
|
22
26
|
const merged_controls = $derived({ ...default_controls, ...controls });
|
|
23
27
|
const controls_config = $derived(normalize_show_controls(merged_controls.show));
|
|
24
28
|
const merged_config = $derived({
|
|
@@ -136,6 +140,98 @@ const camera_default = {
|
|
|
136
140
|
center_y: -50, // Shift up to better show the formation energy funnel
|
|
137
141
|
};
|
|
138
142
|
let camera = $state({ ...camera_default });
|
|
143
|
+
// === Gizmo state & coordinate mapping ===
|
|
144
|
+
// ConvexHull3D uses Rz(azimuth) then Rx(-elevation), viewing along -z_rotated.
|
|
145
|
+
// These helpers convert between that system and Three.js camera position/up.
|
|
146
|
+
const GIZMO_CAM_DIST = 5;
|
|
147
|
+
const MIN_ELEV_FOR_Z_AXIS = 5; // degrees — below this, z-axis ticks collapse to a point
|
|
148
|
+
let gizmo_cam_ref = $state();
|
|
149
|
+
let gizmo_orbit_ref = $state(undefined);
|
|
150
|
+
let gizmo_active = $state(false);
|
|
151
|
+
// Convert elevation/azimuth (degrees) to Three.js camera position + up vector.
|
|
152
|
+
function gizmo_camera(elev_deg, azim_deg) {
|
|
153
|
+
const [elev, azim] = [to_radians(elev_deg), to_radians(azim_deg)];
|
|
154
|
+
const [se, ce, sa, ca] = [
|
|
155
|
+
Math.sin(elev),
|
|
156
|
+
Math.cos(elev),
|
|
157
|
+
Math.sin(azim),
|
|
158
|
+
Math.cos(azim),
|
|
159
|
+
];
|
|
160
|
+
return {
|
|
161
|
+
position: [
|
|
162
|
+
-sa * se * GIZMO_CAM_DIST,
|
|
163
|
+
-ca * se * GIZMO_CAM_DIST,
|
|
164
|
+
ce * GIZMO_CAM_DIST,
|
|
165
|
+
],
|
|
166
|
+
up: [sa * ce, ca * ce, se],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// Derived gizmo camera state, avoids recomputing in the template
|
|
170
|
+
const gizmo_cam_state = $derived(gizmo_camera(camera.elevation, camera.azimuth));
|
|
171
|
+
// Center camera on the triangle's visual center for a given elevation.
|
|
172
|
+
// The centroid (rotation center) sits at 1/3 height while the bbox
|
|
173
|
+
// center is at 1/2 height — a difference of sqrt(3)/12 in data units.
|
|
174
|
+
// Scale by cos(elevation) so offset only applies in near-top-down views.
|
|
175
|
+
function center_camera(elev_deg) {
|
|
176
|
+
camera.center_x = 0;
|
|
177
|
+
// 0.6 matches the draw_data_points() scale factor that maps data coords to canvas pixels
|
|
178
|
+
const scale = Math.min(canvas_dims.width, canvas_dims.height) * 0.6 * camera.zoom;
|
|
179
|
+
camera.center_y = Math.sqrt(3) / 12 * scale * Math.cos(to_radians(elev_deg));
|
|
180
|
+
}
|
|
181
|
+
// Sync: ConvexHull3D → Three.js gizmo camera (on main canvas drag)
|
|
182
|
+
$effect(() => {
|
|
183
|
+
if (gizmo_active)
|
|
184
|
+
return;
|
|
185
|
+
const cam = gizmo_cam_ref;
|
|
186
|
+
if (!cam)
|
|
187
|
+
return;
|
|
188
|
+
const { position, up } = gizmo_camera(camera.elevation, camera.azimuth);
|
|
189
|
+
cam.position.set(...position);
|
|
190
|
+
cam.up.set(...up);
|
|
191
|
+
cam.lookAt(0, 0, 0);
|
|
192
|
+
gizmo_orbit_ref?.update?.();
|
|
193
|
+
});
|
|
194
|
+
// Sync: gizmo → ConvexHull3D (during and after gizmo animation)
|
|
195
|
+
function sync_gizmo_to_camera() {
|
|
196
|
+
const cam = gizmo_cam_ref;
|
|
197
|
+
if (!cam)
|
|
198
|
+
return;
|
|
199
|
+
const { x: cx, y: cy, z: cz } = cam.position;
|
|
200
|
+
const dist = Math.sqrt(cx * cx + cy * cy + cz * cz);
|
|
201
|
+
if (dist < 1e-6)
|
|
202
|
+
return;
|
|
203
|
+
const elev_rad = Math.acos(Math.max(-1, Math.min(1, cz / dist)));
|
|
204
|
+
const sin_elev = Math.sin(elev_rad);
|
|
205
|
+
const azim_deg = Math.abs(sin_elev) > 1e-6
|
|
206
|
+
? Math.atan2(-cx / (dist * sin_elev), -cy / (dist * sin_elev)) * 180 / Math.PI
|
|
207
|
+
: 0;
|
|
208
|
+
const elev_deg = elev_rad * 180 / Math.PI;
|
|
209
|
+
camera.elevation = elev_deg;
|
|
210
|
+
camera.azimuth = azim_deg;
|
|
211
|
+
center_camera(elev_deg);
|
|
212
|
+
}
|
|
213
|
+
// Gizmo axis colors (constant — AXIS_COLORS/NEG_AXIS_COLORS never change)
|
|
214
|
+
const gizmo_axis_options = Object.fromEntries([...AXIS_COLORS, ...NEG_AXIS_COLORS].map(([axis, color, hover_color]) => [axis, {
|
|
215
|
+
color,
|
|
216
|
+
labelColor: `#111`,
|
|
217
|
+
opacity: 0.85,
|
|
218
|
+
hover: { color: hover_color, labelColor: `#222`, opacity: 1 },
|
|
219
|
+
}]));
|
|
220
|
+
// Extract placement from gizmo options (not a Threlte Gizmo prop)
|
|
221
|
+
const gizmo_placement = $derived(typeof gizmo === `object` && gizmo?.placement ? gizmo.placement : `top-right`);
|
|
222
|
+
// Merge constant axis options with consumer overrides (exclude our custom placement)
|
|
223
|
+
const gizmo_props = $derived.by(() => {
|
|
224
|
+
if (typeof gizmo !== `object` || !gizmo) {
|
|
225
|
+
return { background: { enabled: false }, size: 80, ...gizmo_axis_options };
|
|
226
|
+
}
|
|
227
|
+
const { placement: _, ...threlte_opts } = gizmo;
|
|
228
|
+
return {
|
|
229
|
+
background: { enabled: false },
|
|
230
|
+
size: 80,
|
|
231
|
+
...gizmo_axis_options,
|
|
232
|
+
...threlte_opts,
|
|
233
|
+
};
|
|
234
|
+
});
|
|
139
235
|
// Interaction state
|
|
140
236
|
let is_dragging = $state(false);
|
|
141
237
|
let drag_started = $state(false);
|
|
@@ -150,17 +246,6 @@ let selected_structure = $state(null);
|
|
|
150
246
|
let modal_place_right = $state(true);
|
|
151
247
|
// Hull face color (customizable via controls)
|
|
152
248
|
let hull_face_color = $state(`#4caf50`);
|
|
153
|
-
// Utility: convert hex color to rgba string with alpha
|
|
154
|
-
function hex_to_rgba(hex, alpha) {
|
|
155
|
-
const normalized = hex.trim();
|
|
156
|
-
const m = normalized.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
|
|
157
|
-
if (!m)
|
|
158
|
-
return `rgba(0,0,0,${alpha})`;
|
|
159
|
-
const r = parseInt(m[1], 16);
|
|
160
|
-
const g = parseInt(m[2], 16);
|
|
161
|
-
const b = parseInt(m[3], 16);
|
|
162
|
-
return `rgba(${r}, ${g}, ${b}, ${Math.max(0, Math.min(1, alpha))})`;
|
|
163
|
-
}
|
|
164
249
|
// Pulsating highlight for selected point
|
|
165
250
|
let pulse_time = $state(0);
|
|
166
251
|
let pulse_opacity = $derived(0.3 + 0.4 * Math.sin(pulse_time * 4));
|
|
@@ -186,7 +271,7 @@ $effect(() => {
|
|
|
186
271
|
$effect(() => {
|
|
187
272
|
// deno-fmt-ignore
|
|
188
273
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
189
|
-
[show_hull_faces, color_mode, color_scale, show_stable_labels, show_unstable_labels, max_hull_dist_show_labels, camera.elevation, camera.azimuth, camera.zoom, camera.center_x, camera.center_y, plot_entries, hull_face_color, hull_face_opacity, highlighted_entries, text_color];
|
|
274
|
+
[show_hull_faces, color_mode, color_scale, show_stable_labels, show_unstable_labels, max_hull_dist_show_labels, camera.elevation, camera.azimuth, camera.zoom, camera.center_x, camera.center_y, plot_entries, hull_face_color, hull_face_opacity, hull_face_color_mode, element_colors, highlighted_entries, text_color];
|
|
190
275
|
render_once();
|
|
191
276
|
});
|
|
192
277
|
// Function to extract structure data from a convex hull entry
|
|
@@ -212,6 +297,8 @@ function reset_all() {
|
|
|
212
297
|
show_hull_faces = DEFAULTS.convex_hull.ternary.show_hull_faces;
|
|
213
298
|
hull_face_color = DEFAULTS.convex_hull.ternary.hull_face_color;
|
|
214
299
|
hull_face_opacity = DEFAULTS.convex_hull.ternary.hull_face_opacity;
|
|
300
|
+
hull_face_color_mode = DEFAULTS.convex_hull.ternary
|
|
301
|
+
.hull_face_color_mode;
|
|
215
302
|
}
|
|
216
303
|
const handle_keydown = (event) => {
|
|
217
304
|
const target = event.target;
|
|
@@ -222,6 +309,10 @@ const handle_keydown = (event) => {
|
|
|
222
309
|
if (target === canvas) {
|
|
223
310
|
event.stopPropagation();
|
|
224
311
|
}
|
|
312
|
+
if (event.key === `Escape` && modal_open) {
|
|
313
|
+
close_structure_popup();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
225
316
|
// Handle Enter for keyboard accessibility - select hovered entry
|
|
226
317
|
if (event.key === `Enter`) {
|
|
227
318
|
const entry = hover_data?.entry;
|
|
@@ -246,6 +337,11 @@ const handle_keydown = (event) => {
|
|
|
246
337
|
}
|
|
247
338
|
const actions = {
|
|
248
339
|
r: reset_camera,
|
|
340
|
+
t: () => {
|
|
341
|
+
camera.elevation = 0;
|
|
342
|
+
camera.azimuth = 0;
|
|
343
|
+
center_camera(0);
|
|
344
|
+
},
|
|
249
345
|
b: () => color_mode = color_mode === `stability` ? `energy` : `stability`,
|
|
250
346
|
s: () => show_stable = !show_stable,
|
|
251
347
|
u: () => show_unstable = !show_unstable,
|
|
@@ -381,6 +477,9 @@ function draw_element_labels() {
|
|
|
381
477
|
function draw_z_axis_ticks() {
|
|
382
478
|
if (!ctx || elements.length !== 3)
|
|
383
479
|
return;
|
|
480
|
+
// Hide z-axis in near-top-down views where ticks collapse to a point
|
|
481
|
+
if (Math.abs(camera.elevation) < MIN_ELEV_FOR_Z_AXIS)
|
|
482
|
+
return;
|
|
384
483
|
const { min: e_min, max: e_max, center: e_mid } = energy_range;
|
|
385
484
|
if (Math.abs(e_max - e_min) < 1e-6)
|
|
386
485
|
return;
|
|
@@ -403,97 +502,151 @@ function draw_z_axis_ticks() {
|
|
|
403
502
|
ctx.stroke();
|
|
404
503
|
ctx.fillText(format_num(tick, `.2~`), x - tick_len - 4, y);
|
|
405
504
|
}
|
|
406
|
-
// Rotated axis label (
|
|
505
|
+
// Rotated axis label: Eform (eV/atom) with "form" as subscript
|
|
407
506
|
const { x: lx, y: ly } = project_3d_point(axis_x, axis_y, e_mid);
|
|
507
|
+
const fs = merged_config.font_size ?? 12;
|
|
508
|
+
const sub_fs = Math.round(fs * 0.75);
|
|
408
509
|
ctx.translate(lx - 50 * canvas_dims.scale, ly);
|
|
409
510
|
ctx.rotate(-Math.PI / 2);
|
|
410
|
-
ctx.textAlign = `
|
|
411
|
-
|
|
412
|
-
ctx.
|
|
511
|
+
ctx.textAlign = `left`;
|
|
512
|
+
// Measure widths in each font, then draw — reordered to minimize font switches
|
|
513
|
+
ctx.font = `bold ${fs}px Arial`;
|
|
514
|
+
const e_width = ctx.measureText(`E`).width;
|
|
515
|
+
const suffix_width = ctx.measureText(` (eV/atom)`).width;
|
|
516
|
+
ctx.font = `${sub_fs}px Arial`;
|
|
517
|
+
const form_width = ctx.measureText(`form`).width;
|
|
518
|
+
const offset = -(e_width + form_width + suffix_width) / 2;
|
|
519
|
+
// Draw subscript while sub-font is still active
|
|
520
|
+
ctx.fillText(`form`, offset + e_width, fs * 0.3);
|
|
521
|
+
ctx.font = `bold ${fs}px Arial`;
|
|
522
|
+
ctx.fillText(`E`, offset, 0);
|
|
523
|
+
ctx.fillText(` (eV/atom)`, offset + e_width + form_width, 0);
|
|
413
524
|
ctx.restore();
|
|
414
525
|
}
|
|
415
526
|
function draw_convex_hull_faces() {
|
|
416
527
|
if (!ctx || !show_hull_faces || hull_faces.length === 0)
|
|
417
528
|
return;
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
|
|
529
|
+
// Lazy computation for uniform mode: normalize alpha by formation energy
|
|
530
|
+
let norm_alpha = null;
|
|
531
|
+
if (hull_face_color_mode === `uniform`) {
|
|
532
|
+
const formation_energies = plot_entries.map((e) => e.e_form_per_atom ?? 0);
|
|
533
|
+
const min_fe = Math.min(0, ...formation_energies);
|
|
534
|
+
norm_alpha = (z) => {
|
|
535
|
+
const t = Math.max(0, Math.min(1, (0 - z) / Math.max(1e-6, 0 - min_fe)));
|
|
536
|
+
return t * hull_face_opacity;
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
// Lazy computation for formation_energy mode
|
|
540
|
+
let energy_face_scale = null;
|
|
541
|
+
let min_z = 0;
|
|
542
|
+
if (hull_face_color_mode === `formation_energy`) {
|
|
543
|
+
const all_z = hull_faces.flatMap((tri) => tri.vertices.map((v) => v.z));
|
|
544
|
+
min_z = Math.min(...all_z);
|
|
545
|
+
energy_face_scale = helpers.get_energy_color_scale(`energy`, color_scale, all_z.map((z) => ({ e_above_hull: z - min_z })));
|
|
546
|
+
}
|
|
547
|
+
// Helper to get face color based on mode
|
|
548
|
+
const get_face_color = (tri, tri_idx) => {
|
|
549
|
+
if (hull_face_color_mode === `uniform`) {
|
|
550
|
+
return hull_face_color;
|
|
551
|
+
}
|
|
552
|
+
if (hull_face_color_mode === `formation_energy`) {
|
|
553
|
+
const avg_z = (tri.vertices[0].z + tri.vertices[1].z + tri.vertices[2].z) / 3;
|
|
554
|
+
return energy_face_scale(avg_z - min_z);
|
|
555
|
+
}
|
|
556
|
+
if (hull_face_color_mode === `dominant_element`) {
|
|
557
|
+
// Find element vertex closest to face centroid in 2D ternary space
|
|
558
|
+
const { x: cx, y: cy } = tri.centroid;
|
|
559
|
+
const dists = TRIANGLE_VERTICES.map(([tx, ty]) => Math.hypot(cx - tx, cy - ty));
|
|
560
|
+
const el = elements[dists.indexOf(Math.min(...dists))];
|
|
561
|
+
return element_colors[el] ?? `#888888`;
|
|
562
|
+
}
|
|
563
|
+
if (hull_face_color_mode === `facet_index`) {
|
|
564
|
+
return PLOT_COLORS[tri_idx % PLOT_COLORS.length];
|
|
565
|
+
}
|
|
566
|
+
return hull_face_color;
|
|
424
567
|
};
|
|
425
568
|
// Sort faces by depth for proper rendering
|
|
426
|
-
const faces_with_depth = hull_faces.map((tri) => {
|
|
569
|
+
const faces_with_depth = hull_faces.map((tri, tri_idx) => {
|
|
427
570
|
const centroid_proj = project_3d_point(tri.centroid.x, tri.centroid.y, tri.centroid.z);
|
|
428
|
-
return { tri, depth: centroid_proj.depth };
|
|
571
|
+
return { tri, tri_idx, depth: centroid_proj.depth };
|
|
429
572
|
});
|
|
430
573
|
faces_with_depth.sort((a, b) => a.depth - b.depth); // Back to front
|
|
431
574
|
// Draw each face (lower hull only)
|
|
432
|
-
for (const { tri } of faces_with_depth) {
|
|
575
|
+
for (const { tri, tri_idx } of faces_with_depth) {
|
|
433
576
|
const [p1, p2, p3] = tri.vertices;
|
|
434
577
|
const proj1 = project_3d_point(p1.x, p1.y, p1.z);
|
|
435
578
|
const proj2 = project_3d_point(p2.x, p2.y, p2.z);
|
|
436
579
|
const proj3 = project_3d_point(p3.x, p3.y, p3.z);
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
det;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
ctx
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
580
|
+
const face_color = get_face_color(tri, tri_idx);
|
|
581
|
+
// For uniform mode, use gradient with variable opacity
|
|
582
|
+
// For other modes, use fixed opacity
|
|
583
|
+
if (hull_face_color_mode === `uniform`) {
|
|
584
|
+
// Build per-face linear gradient in screen space matching linear variation of formation energy
|
|
585
|
+
const a1 = norm_alpha(p1.z);
|
|
586
|
+
const a2 = norm_alpha(p2.z);
|
|
587
|
+
const a3 = norm_alpha(p3.z);
|
|
588
|
+
// Solve a*x + b*y + c = alpha at the three projected vertices
|
|
589
|
+
const x1 = proj1.x, y1 = proj1.y;
|
|
590
|
+
const x2 = proj2.x, y2 = proj2.y;
|
|
591
|
+
const x3 = proj3.x, y3 = proj3.y;
|
|
592
|
+
const det = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
|
|
593
|
+
let coef_a = 0, coef_b = 0, coef_c = (a1 + a2 + a3) / 3;
|
|
594
|
+
if (Math.abs(det) > 1e-9) {
|
|
595
|
+
coef_a = (a1 * (y2 - y3) + a2 * (y3 - y1) + a3 * (y1 - y2)) / det;
|
|
596
|
+
coef_b = (a1 * (x3 - x2) + a2 * (x1 - x3) + a3 * (x2 - x1)) / det;
|
|
597
|
+
coef_c = (a1 * (x2 * y3 - x3 * y2) + a2 * (x3 * y1 - x1 * y3) +
|
|
598
|
+
a3 * (x1 * y2 - x2 * y1)) /
|
|
599
|
+
det;
|
|
600
|
+
}
|
|
601
|
+
// Helper to draw filled+stroked triangle (ctx guaranteed non-null by early return)
|
|
602
|
+
const draw_tri = (fill, stroke_alpha) => {
|
|
603
|
+
ctx.save();
|
|
604
|
+
ctx.beginPath();
|
|
605
|
+
ctx.moveTo(proj1.x, proj1.y);
|
|
606
|
+
ctx.lineTo(proj2.x, proj2.y);
|
|
607
|
+
ctx.lineTo(proj3.x, proj3.y);
|
|
608
|
+
ctx.closePath();
|
|
609
|
+
ctx.fillStyle = fill;
|
|
610
|
+
ctx.fill();
|
|
611
|
+
ctx.strokeStyle = add_alpha(face_color, Math.min(0.6, stroke_alpha));
|
|
612
|
+
ctx.lineWidth = 1;
|
|
613
|
+
ctx.stroke();
|
|
614
|
+
ctx.restore();
|
|
615
|
+
};
|
|
616
|
+
// Gradient direction is the screen-space gradient of alpha
|
|
617
|
+
const mag = Math.hypot(coef_a, coef_b);
|
|
618
|
+
if (mag < 1e-9) {
|
|
619
|
+
// Fallback: uniform fill if nearly flat
|
|
620
|
+
const avg_alpha = (a1 + a2 + a3) / 3;
|
|
621
|
+
draw_tri(add_alpha(face_color, avg_alpha), avg_alpha * 3);
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
const vx = coef_a / mag;
|
|
625
|
+
const vy = coef_b / mag;
|
|
626
|
+
const cx = (x1 + x2 + x3) / 3;
|
|
627
|
+
const cy = (y1 + y2 + y3) / 3;
|
|
628
|
+
const alpha_c = coef_a * cx + coef_b * cy + coef_c;
|
|
629
|
+
const alpha_min = Math.min(a1, a2, a3);
|
|
630
|
+
const alpha_max = Math.max(a1, a2, a3);
|
|
631
|
+
const s_min = (alpha_min - alpha_c) / mag;
|
|
632
|
+
const s_max = (alpha_max - alpha_c) / mag;
|
|
633
|
+
const grad = ctx.createLinearGradient(cx + vx * s_min, cy + vy * s_min, cx + vx * s_max, cy + vy * s_max);
|
|
634
|
+
grad.addColorStop(0, add_alpha(face_color, alpha_min));
|
|
635
|
+
grad.addColorStop(1, add_alpha(face_color, alpha_max));
|
|
636
|
+
draw_tri(grad, alpha_max * 3);
|
|
637
|
+
}
|
|
470
638
|
}
|
|
471
639
|
else {
|
|
472
|
-
|
|
473
|
-
const vy = b / mag;
|
|
474
|
-
const cx = (x1 + x2 + x3) / 3;
|
|
475
|
-
const cy = (y1 + y2 + y3) / 3;
|
|
476
|
-
const alpha_c = a * cx + b * cy + c;
|
|
477
|
-
const alpha_min = Math.min(a1, a2, a3);
|
|
478
|
-
const alpha_max = Math.max(a1, a2, a3);
|
|
479
|
-
const s_min = (alpha_min - alpha_c) / mag;
|
|
480
|
-
const s_max = (alpha_max - alpha_c) / mag;
|
|
481
|
-
const gx0 = cx + vx * s_min;
|
|
482
|
-
const gy0 = cy + vy * s_min;
|
|
483
|
-
const gx1 = cx + vx * s_max;
|
|
484
|
-
const gy1 = cy + vy * s_max;
|
|
485
|
-
const grad = ctx.createLinearGradient(gx0, gy0, gx1, gy1);
|
|
486
|
-
grad.addColorStop(0, hex_to_rgba(hull_face_color, alpha_min));
|
|
487
|
-
grad.addColorStop(1, hex_to_rgba(hull_face_color, alpha_max));
|
|
640
|
+
// Non-uniform modes: solid color with fixed opacity
|
|
488
641
|
ctx.save();
|
|
489
642
|
ctx.beginPath();
|
|
490
643
|
ctx.moveTo(proj1.x, proj1.y);
|
|
491
644
|
ctx.lineTo(proj2.x, proj2.y);
|
|
492
645
|
ctx.lineTo(proj3.x, proj3.y);
|
|
493
646
|
ctx.closePath();
|
|
494
|
-
ctx.fillStyle =
|
|
647
|
+
ctx.fillStyle = add_alpha(face_color, hull_face_opacity);
|
|
495
648
|
ctx.fill();
|
|
496
|
-
ctx.strokeStyle =
|
|
649
|
+
ctx.strokeStyle = add_alpha(face_color, Math.min(0.6, hull_face_opacity * 3));
|
|
497
650
|
ctx.lineWidth = 1;
|
|
498
651
|
ctx.stroke();
|
|
499
652
|
ctx.restore();
|
|
@@ -513,7 +666,7 @@ const e_form_color_scale_fn = $derived.by(() => {
|
|
|
513
666
|
// alpha 0 at 0 eV, goes to hull_face_opacity at most negative energy
|
|
514
667
|
const t = Math.max(0, Math.min(1, (value - min_fe) / denom));
|
|
515
668
|
const alpha = (1 - t) * hull_face_opacity;
|
|
516
|
-
return
|
|
669
|
+
return add_alpha(hull_face_color, alpha);
|
|
517
670
|
};
|
|
518
671
|
});
|
|
519
672
|
function draw_data_points() {
|
|
@@ -584,15 +737,7 @@ function draw_hull_labels() {
|
|
|
584
737
|
if (!can_label)
|
|
585
738
|
continue;
|
|
586
739
|
const projected = project_3d_point(entry.x, entry.y, entry.z);
|
|
587
|
-
|
|
588
|
-
if (!formula) {
|
|
589
|
-
formula = Object.entries(entry.composition)
|
|
590
|
-
.filter(([, amt]) => amt > 0)
|
|
591
|
-
.sort(([el1], [el2]) => elements.indexOf(el1) -
|
|
592
|
-
elements.indexOf(el2))
|
|
593
|
-
.map(([el, amt]) => Math.abs(amt - 1) < 1e-6 ? el : `${el}${format_num(amt, `.2~`)}`)
|
|
594
|
-
.join(``);
|
|
595
|
-
}
|
|
740
|
+
const formula = helpers.get_entry_label(entry, elements);
|
|
596
741
|
ctx.fillText(formula, projected.x, projected.y + 16 * canvas_dims.scale);
|
|
597
742
|
}
|
|
598
743
|
}
|
|
@@ -608,11 +753,13 @@ function render_frame() {
|
|
|
608
753
|
ctx.fillStyle = `transparent`;
|
|
609
754
|
ctx.fillRect(0, 0, display_width, display_height);
|
|
610
755
|
if (elements.length !== 3) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
756
|
+
if (elements.length > 0) {
|
|
757
|
+
ctx.fillStyle = text_color;
|
|
758
|
+
ctx.font = `16px Arial`;
|
|
759
|
+
ctx.textAlign = `center`;
|
|
760
|
+
ctx.textBaseline = `middle`;
|
|
761
|
+
ctx.fillText(`Ternary convex hull requires exactly 3 elements (got ${elements.length})`, display_width / 2, display_height / 2);
|
|
762
|
+
}
|
|
616
763
|
return;
|
|
617
764
|
}
|
|
618
765
|
draw_structure_outline();
|
|
@@ -625,33 +772,37 @@ function render_frame() {
|
|
|
625
772
|
function handle_mouse_down(event) {
|
|
626
773
|
is_dragging = true;
|
|
627
774
|
drag_started = false;
|
|
775
|
+
hover_data = null;
|
|
776
|
+
on_point_hover?.(null);
|
|
628
777
|
last_mouse = { x: event.clientX, y: event.clientY };
|
|
629
778
|
}
|
|
630
779
|
const handle_mouse_move = (event) => {
|
|
631
|
-
if (is_dragging)
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
last_mouse = { x: event.clientX, y: event.clientY };
|
|
780
|
+
if (!is_dragging)
|
|
781
|
+
return;
|
|
782
|
+
const [dx, dy] = [event.clientX - last_mouse.x, event.clientY - last_mouse.y];
|
|
783
|
+
// Mark as drag if any movement occurred
|
|
784
|
+
if (dx !== 0 || dy !== 0)
|
|
785
|
+
drag_started = true;
|
|
786
|
+
// With Cmd/Ctrl held: pan the view instead of rotating
|
|
787
|
+
if (event.metaKey || event.ctrlKey) {
|
|
788
|
+
camera.center_x += dx;
|
|
789
|
+
camera.center_y += dy;
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
// Horizontal drag -> azimuth rotation around z-axis
|
|
793
|
+
camera.azimuth += dx * 0.3; // Positive dx (drag right) rotates clockwise
|
|
794
|
+
// Vertical drag -> elevation angle (full range)
|
|
795
|
+
camera.elevation -= dy * 0.3; // Positive dy (drag down) tilts view down
|
|
648
796
|
}
|
|
797
|
+
last_mouse = { x: event.clientX, y: event.clientY };
|
|
649
798
|
};
|
|
650
799
|
const handle_wheel = (event) => {
|
|
651
800
|
event.preventDefault();
|
|
652
801
|
camera.zoom = Math.max(0.5, Math.min(10, camera.zoom * (event.deltaY > 0 ? 0.98 : 1.02)));
|
|
653
802
|
};
|
|
654
803
|
const handle_hover = (event) => {
|
|
804
|
+
if (is_dragging)
|
|
805
|
+
return;
|
|
655
806
|
const entry = find_entry_at_mouse(event);
|
|
656
807
|
hover_data = entry
|
|
657
808
|
? { entry, position: { x: event.clientX, y: event.clientY } }
|
|
@@ -717,15 +868,21 @@ function update_canvas_size() {
|
|
|
717
868
|
const container = canvas.parentElement;
|
|
718
869
|
const rect = container?.getBoundingClientRect();
|
|
719
870
|
const [w, h] = rect ? [rect.width, rect.height] : [400, 400];
|
|
720
|
-
canvas
|
|
721
|
-
canvas.height
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (ctx) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
ctx
|
|
871
|
+
// Only update canvas dimensions if they actually changed
|
|
872
|
+
// (assigning canvas.width/height clears the canvas even if values are the same)
|
|
873
|
+
const new_width = Math.max(0, Math.round(w * dpr));
|
|
874
|
+
const new_height = Math.max(0, Math.round(h * dpr));
|
|
875
|
+
if (!ctx || canvas.width !== new_width || canvas.height !== new_height) {
|
|
876
|
+
canvas.width = new_width;
|
|
877
|
+
canvas.height = new_height;
|
|
878
|
+
ctx = canvas.getContext(`2d`);
|
|
879
|
+
if (ctx) {
|
|
880
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
881
|
+
ctx.imageSmoothingEnabled = true;
|
|
882
|
+
ctx.imageSmoothingQuality = `high`;
|
|
883
|
+
}
|
|
728
884
|
}
|
|
885
|
+
canvas_dims = { width: w, height: h, scale: Math.min(w, h) / 600 };
|
|
729
886
|
render_once();
|
|
730
887
|
}
|
|
731
888
|
// Reactive dark mode detection for canvas text color
|
|
@@ -803,6 +960,8 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
|
|
|
803
960
|
class:dragover={drag_over}
|
|
804
961
|
style={`${style}; ${rest.style ?? ``}`}
|
|
805
962
|
data-has-selection={selected_entry !== null}
|
|
963
|
+
data-has-hover={hover_data !== null}
|
|
964
|
+
data-is-dragging={is_dragging}
|
|
806
965
|
bind:this={wrapper}
|
|
807
966
|
role="application"
|
|
808
967
|
tabindex="-1"
|
|
@@ -824,7 +983,7 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
|
|
|
824
983
|
highlighted_entries,
|
|
825
984
|
selected_entry,
|
|
826
985
|
})}
|
|
827
|
-
<h3 style="position: absolute; left: 1em; top: 1ex; margin: 0">
|
|
986
|
+
<h3 style="position: absolute; left: 1em; top: 1ex; margin: 0; font-weight: 500">
|
|
828
987
|
{@html merged_controls.title || phase_stats?.chemical_system || ``}
|
|
829
988
|
</h3>
|
|
830
989
|
<canvas
|
|
@@ -839,6 +998,13 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
|
|
|
839
998
|
onwheel={handle_wheel}
|
|
840
999
|
></canvas>
|
|
841
1000
|
|
|
1001
|
+
{#if entries.length === 0}
|
|
1002
|
+
<Spinner
|
|
1003
|
+
text="Loading data..."
|
|
1004
|
+
style="position: absolute; inset: 0; display: flex; align-items: center; justify-content: center"
|
|
1005
|
+
/>
|
|
1006
|
+
{/if}
|
|
1007
|
+
|
|
842
1008
|
<!-- Formation Energy Color Bar (bottom-left corner) -->
|
|
843
1009
|
{#if color_mode === `energy` && plot_entries.length > 0}
|
|
844
1010
|
{@const hull_distances = plot_entries
|
|
@@ -850,20 +1016,23 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
|
|
|
850
1016
|
title="Energy above hull (eV/atom)"
|
|
851
1017
|
range={[min_energy, max_energy]}
|
|
852
1018
|
{color_scale}
|
|
853
|
-
wrapper_style="position: absolute; bottom:
|
|
1019
|
+
wrapper_style="position: absolute; bottom: 16px; left: 1em; width: 200px;"
|
|
854
1020
|
bar_style="height: 12px;"
|
|
855
1021
|
title_style="margin-bottom: 4px;"
|
|
856
1022
|
/>
|
|
857
1023
|
{/if}
|
|
858
1024
|
|
|
859
1025
|
<!-- Formation Energy Faces Color Bar (bottom-right corner) -->
|
|
860
|
-
|
|
1026
|
+
<!-- Only show for uniform/formation_energy modes where face color relates to E_form -->
|
|
1027
|
+
{#if plot_entries.length > 0 && show_hull_faces &&
|
|
1028
|
+
(hull_face_color_mode === `uniform` ||
|
|
1029
|
+
hull_face_color_mode === `formation_energy`)}
|
|
861
1030
|
<ColorBar
|
|
862
1031
|
title="Formation energy (eV/atom)"
|
|
863
1032
|
color_scale_fn={e_form_color_scale_fn}
|
|
864
1033
|
color_scale_domain={e_form_range}
|
|
865
1034
|
range={e_form_range}
|
|
866
|
-
wrapper_style="position: absolute;
|
|
1035
|
+
wrapper_style="position: absolute; bottom: 16px; right: 1em; width: 200px;"
|
|
867
1036
|
bar_style="height: 12px;"
|
|
868
1037
|
title_style="margin-bottom: 4px;"
|
|
869
1038
|
/>
|
|
@@ -931,6 +1100,8 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
|
|
|
931
1100
|
on_hull_face_color_change={(value) => hull_face_color = value}
|
|
932
1101
|
{hull_face_opacity}
|
|
933
1102
|
on_hull_face_opacity_change={(value) => hull_face_opacity = value}
|
|
1103
|
+
{hull_face_color_mode}
|
|
1104
|
+
on_hull_face_color_mode_change={(value) => hull_face_color_mode = value}
|
|
934
1105
|
bind:energy_source_mode
|
|
935
1106
|
{has_precomputed_e_form}
|
|
936
1107
|
{can_compute_e_form}
|
|
@@ -941,16 +1112,55 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
|
|
|
941
1112
|
</section>
|
|
942
1113
|
{/if}
|
|
943
1114
|
|
|
944
|
-
|
|
945
|
-
|
|
1115
|
+
<!-- Orientation gizmo (configurable placement, default top-right) -->
|
|
1116
|
+
{#if gizmo && typeof WebGLRenderingContext !== `undefined`}
|
|
1117
|
+
<div class="gizmo-wrapper {controls_config.class}" data-placement={gizmo_placement}>
|
|
1118
|
+
<Canvas
|
|
1119
|
+
createRenderer={(cvs: HTMLCanvasElement) =>
|
|
1120
|
+
new WebGLRenderer({ canvas: cvs, alpha: true, antialias: true })}
|
|
1121
|
+
>
|
|
1122
|
+
<T.PerspectiveCamera
|
|
1123
|
+
makeDefault
|
|
1124
|
+
bind:ref={gizmo_cam_ref}
|
|
1125
|
+
position={gizmo_cam_state.position}
|
|
1126
|
+
up={gizmo_cam_state.up}
|
|
1127
|
+
fov={50}
|
|
1128
|
+
>
|
|
1129
|
+
<extras.OrbitControls
|
|
1130
|
+
bind:ref={gizmo_orbit_ref}
|
|
1131
|
+
enableRotate={false}
|
|
1132
|
+
enableZoom={false}
|
|
1133
|
+
enablePan={false}
|
|
1134
|
+
>
|
|
1135
|
+
<extras.Gizmo
|
|
1136
|
+
{...gizmo_props}
|
|
1137
|
+
onstart={() => (gizmo_active = true)}
|
|
1138
|
+
onchange={sync_gizmo_to_camera}
|
|
1139
|
+
onend={() => {
|
|
1140
|
+
sync_gizmo_to_camera()
|
|
1141
|
+
gizmo_active = false
|
|
1142
|
+
}}
|
|
1143
|
+
/>
|
|
1144
|
+
</extras.OrbitControls>
|
|
1145
|
+
</T.PerspectiveCamera>
|
|
1146
|
+
</Canvas>
|
|
1147
|
+
</div>
|
|
946
1148
|
{/if}
|
|
947
1149
|
|
|
948
|
-
{#if
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1150
|
+
{#if (has_temp_data && temperature !== undefined) ||
|
|
1151
|
+
(gas_analysis.has_gas_dependent_elements && merged_gas_config)}
|
|
1152
|
+
<div class="right-controls">
|
|
1153
|
+
{#if has_temp_data && temperature !== undefined}
|
|
1154
|
+
<TemperatureSlider {available_temperatures} bind:temperature />
|
|
1155
|
+
{/if}
|
|
1156
|
+
{#if gas_analysis.has_gas_dependent_elements && merged_gas_config}
|
|
1157
|
+
<GasPressureControls
|
|
1158
|
+
config={merged_gas_config}
|
|
1159
|
+
bind:pressures={gas_pressures}
|
|
1160
|
+
temperature={temperature ?? 300}
|
|
1161
|
+
/>
|
|
1162
|
+
{/if}
|
|
1163
|
+
</div>
|
|
954
1164
|
{/if}
|
|
955
1165
|
|
|
956
1166
|
<!-- Hover tooltip -->
|
|
@@ -1019,6 +1229,58 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
|
|
|
1019
1229
|
canvas:active {
|
|
1020
1230
|
cursor: grabbing;
|
|
1021
1231
|
}
|
|
1232
|
+
.right-controls {
|
|
1233
|
+
position: absolute;
|
|
1234
|
+
top: calc(1ex + 50px);
|
|
1235
|
+
right: 1ex;
|
|
1236
|
+
z-index: 2;
|
|
1237
|
+
pointer-events: auto;
|
|
1238
|
+
display: flex;
|
|
1239
|
+
flex-direction: column;
|
|
1240
|
+
align-items: flex-end;
|
|
1241
|
+
gap: 6px;
|
|
1242
|
+
}
|
|
1243
|
+
.right-controls :global(.temperature-slider),
|
|
1244
|
+
.right-controls :global(.pressure-controls) {
|
|
1245
|
+
position: static;
|
|
1246
|
+
}
|
|
1247
|
+
/* align both vertical range inputs at the same x position */
|
|
1248
|
+
.right-controls :global(.slider-wrapper) {
|
|
1249
|
+
justify-content: flex-end;
|
|
1250
|
+
}
|
|
1251
|
+
.gizmo-wrapper {
|
|
1252
|
+
position: absolute;
|
|
1253
|
+
width: clamp(80px, 18cqmin, 110px);
|
|
1254
|
+
height: clamp(80px, 18cqmin, 110px);
|
|
1255
|
+
pointer-events: auto;
|
|
1256
|
+
isolation: isolate; /* contain z-index: 1000 from three-viewport-gizmo overlay */
|
|
1257
|
+
transition: opacity 0.2s ease-in-out;
|
|
1258
|
+
}
|
|
1259
|
+
.gizmo-wrapper[data-placement='top-right'] {
|
|
1260
|
+
top: 1.8em;
|
|
1261
|
+
right: 1ex;
|
|
1262
|
+
}
|
|
1263
|
+
.gizmo-wrapper[data-placement='top-left'] {
|
|
1264
|
+
top: 1.8em;
|
|
1265
|
+
left: 1ex;
|
|
1266
|
+
}
|
|
1267
|
+
.gizmo-wrapper[data-placement='bottom-right'] {
|
|
1268
|
+
bottom: 2.5em;
|
|
1269
|
+
right: 1ex;
|
|
1270
|
+
}
|
|
1271
|
+
.gizmo-wrapper[data-placement='bottom-left'] {
|
|
1272
|
+
bottom: 2.5em;
|
|
1273
|
+
left: 1ex;
|
|
1274
|
+
}
|
|
1275
|
+
.gizmo-wrapper.hover-visible {
|
|
1276
|
+
opacity: 0;
|
|
1277
|
+
pointer-events: none;
|
|
1278
|
+
}
|
|
1279
|
+
.convex-hull-3d:hover .gizmo-wrapper.hover-visible,
|
|
1280
|
+
.convex-hull-3d:focus-within .gizmo-wrapper.hover-visible {
|
|
1281
|
+
opacity: 1;
|
|
1282
|
+
pointer-events: auto;
|
|
1283
|
+
}
|
|
1022
1284
|
.control-buttons {
|
|
1023
1285
|
position: absolute;
|
|
1024
1286
|
top: 1ex;
|
|
@@ -1040,7 +1302,7 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
|
|
|
1040
1302
|
opacity: 1;
|
|
1041
1303
|
pointer-events: auto;
|
|
1042
1304
|
}
|
|
1043
|
-
.control-buttons button {
|
|
1305
|
+
.control-buttons :global(button) {
|
|
1044
1306
|
background: transparent;
|
|
1045
1307
|
border: none;
|
|
1046
1308
|
padding: 4px;
|
|
@@ -1049,9 +1311,9 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
|
|
|
1049
1311
|
color: var(--text-color, currentColor);
|
|
1050
1312
|
transition: background-color 0.2s;
|
|
1051
1313
|
display: flex;
|
|
1052
|
-
font-size: clamp(0.85em, 2cqmin,
|
|
1314
|
+
font-size: clamp(0.85em, 2cqmin, 1.3em);
|
|
1053
1315
|
}
|
|
1054
|
-
.control-buttons button:hover {
|
|
1316
|
+
.control-buttons :global(button):hover {
|
|
1055
1317
|
background-color: color-mix(in srgb, currentColor 8%, transparent);
|
|
1056
1318
|
}
|
|
1057
1319
|
</style>
|