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,409 +1,868 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PLOT_COLORS } from '../colors'
|
|
3
|
+
import EmptyState from '../EmptyState.svelte'
|
|
4
|
+
import { format_num } from '../labels'
|
|
5
|
+
import { sanitize_html } from '../sanitize'
|
|
6
|
+
import { SettingsSection } from '../layout'
|
|
7
|
+
import type { Vec2 } from '../math'
|
|
8
|
+
import ScatterPlot from '../plot/ScatterPlot.svelte'
|
|
9
|
+
import type { AxisConfig, DataSeries, FillRegion } from '../plot/types'
|
|
10
|
+
import * as helpers from './helpers'
|
|
11
|
+
import type {
|
|
12
|
+
BandsSpinMode,
|
|
13
|
+
BandStructureType,
|
|
14
|
+
BaseBandStructure,
|
|
15
|
+
FrequencyUnit,
|
|
16
|
+
LineKwargs,
|
|
17
|
+
PathMode,
|
|
18
|
+
RibbonConfig,
|
|
19
|
+
} from './types'
|
|
20
|
+
import type { ComponentProps } from 'svelte'
|
|
21
|
+
import { SvelteMap } from 'svelte/reactivity'
|
|
22
|
+
|
|
23
|
+
type Dom_attr_value = string | number | boolean
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
band_structs,
|
|
27
|
+
line_kwargs = {},
|
|
28
|
+
path_mode = `strict`,
|
|
29
|
+
band_type = undefined,
|
|
30
|
+
show_legend = true,
|
|
31
|
+
x_axis = {},
|
|
32
|
+
y_axis = $bindable({}),
|
|
33
|
+
x_positions = $bindable(),
|
|
34
|
+
reference_frequency = null,
|
|
35
|
+
ribbon_config = {},
|
|
36
|
+
fermi_level = undefined,
|
|
37
|
+
units = $bindable(`THz`),
|
|
38
|
+
band_spin_mode = $bindable(`overlay`),
|
|
39
|
+
highlight_regions = [],
|
|
40
|
+
shade_imaginary_modes = true,
|
|
41
|
+
show_gap_annotation = true,
|
|
42
|
+
show_controls = true,
|
|
43
|
+
show_path_mode_control = true,
|
|
44
|
+
show_units_control = true,
|
|
45
|
+
show_spin_control = true,
|
|
46
|
+
show_annotation_controls = true,
|
|
47
|
+
id = undefined,
|
|
48
|
+
class: class_name = undefined,
|
|
49
|
+
style = undefined,
|
|
50
|
+
'data-testid': data_testid = undefined,
|
|
51
|
+
...rest
|
|
52
|
+
}: ComponentProps<typeof ScatterPlot> & {
|
|
53
|
+
band_structs: BaseBandStructure | Record<string, BaseBandStructure>
|
|
54
|
+
x_axis?: AxisConfig
|
|
55
|
+
y_axis?: AxisConfig
|
|
56
|
+
line_kwargs?: LineKwargs
|
|
57
|
+
path_mode?: PathMode
|
|
58
|
+
band_type?: BandStructureType
|
|
59
|
+
show_legend?: boolean
|
|
60
|
+
x_positions?: Record<string, [number, number]>
|
|
61
|
+
reference_frequency?: number | null
|
|
62
|
+
ribbon_config?: RibbonConfig | Record<string, RibbonConfig>
|
|
63
|
+
fermi_level?: number // Fermi level for electronic bands (auto-detected if not provided)
|
|
64
|
+
units?: FrequencyUnit // Phonon frequency display units (electronic always eV)
|
|
65
|
+
band_spin_mode?: BandsSpinMode // Electronic spin display: overlay (default), up_only, down_only
|
|
66
|
+
highlight_regions?: {
|
|
67
|
+
y_min: number
|
|
68
|
+
y_max: number
|
|
69
|
+
color?: string
|
|
70
|
+
opacity?: number
|
|
71
|
+
label?: string
|
|
72
|
+
}[]
|
|
73
|
+
shade_imaginary_modes?: boolean // Shade y<0 region for phonon plots with imaginary modes
|
|
74
|
+
show_gap_annotation?: boolean // Annotate electronic VBM/CBM and gap when available
|
|
75
|
+
show_controls?: boolean
|
|
76
|
+
show_path_mode_control?: boolean
|
|
77
|
+
show_units_control?: boolean
|
|
78
|
+
show_spin_control?: boolean
|
|
79
|
+
show_annotation_controls?: boolean
|
|
80
|
+
id?: string
|
|
81
|
+
class?: string
|
|
82
|
+
style?: string
|
|
83
|
+
'data-testid'?: string
|
|
84
|
+
} = $props()
|
|
85
|
+
|
|
86
|
+
const is_dom_attr_value = (attr_value: unknown): attr_value is Dom_attr_value =>
|
|
87
|
+
typeof attr_value === `string` ||
|
|
88
|
+
typeof attr_value === `number` ||
|
|
89
|
+
typeof attr_value === `boolean`
|
|
90
|
+
|
|
91
|
+
// Helper function to get line styling for a band
|
|
92
|
+
function get_line_style(
|
|
93
|
+
color: string,
|
|
94
|
+
is_acoustic: boolean,
|
|
95
|
+
frequencies: number[],
|
|
96
|
+
band_idx: number,
|
|
97
|
+
): { stroke: string; stroke_width: number } {
|
|
98
|
+
const defaults = { stroke: color, stroke_width: is_acoustic ? 1.5 : 1 }
|
|
99
|
+
|
|
9
100
|
if (typeof line_kwargs === `function`) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
101
|
+
const custom = line_kwargs(frequencies, band_idx)
|
|
102
|
+
return {
|
|
103
|
+
stroke: (custom.stroke as string) ?? defaults.stroke,
|
|
104
|
+
stroke_width: (custom.stroke_width as number) ?? defaults.stroke_width,
|
|
105
|
+
}
|
|
15
106
|
}
|
|
107
|
+
|
|
16
108
|
if (typeof line_kwargs === `object` && line_kwargs !== null) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
109
|
+
const mode_key = is_acoustic ? `acoustic` : `optical`
|
|
110
|
+
const mode_kwargs = (line_kwargs as Record<string, unknown>)[mode_key] as
|
|
111
|
+
| Record<string, unknown>
|
|
112
|
+
| undefined
|
|
113
|
+
const source = (mode_kwargs ?? line_kwargs) as Record<string, unknown>
|
|
114
|
+
return {
|
|
115
|
+
stroke: (source.stroke as string) ?? defaults.stroke,
|
|
116
|
+
stroke_width: (source.stroke_width as number) ?? defaults.stroke_width,
|
|
117
|
+
}
|
|
23
118
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
119
|
+
|
|
120
|
+
return defaults
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Ribbon data structure for rendering
|
|
124
|
+
interface RibbonData {
|
|
125
|
+
x_values: number[]
|
|
126
|
+
y_values: number[]
|
|
127
|
+
width_values: number[]
|
|
128
|
+
color: string
|
|
129
|
+
opacity: number
|
|
130
|
+
max_width: number
|
|
131
|
+
scale: number
|
|
132
|
+
band_idx: number
|
|
133
|
+
structure_label: string
|
|
134
|
+
segment_key: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Normalize input to dict format
|
|
138
|
+
// Supports multiple formats:
|
|
139
|
+
// - matterviz format: qpoints + branches arrays
|
|
140
|
+
// - pymatgen phonon: qpoints + bands (or frequencies_cm) arrays
|
|
141
|
+
// - pymatgen electronic: kpoints + bands arrays
|
|
142
|
+
let band_structs_dict = $derived.by(() => {
|
|
143
|
+
if (!band_structs) return {}
|
|
144
|
+
|
|
34
145
|
// Detect single band structure by checking for characteristic fields
|
|
35
146
|
// - pymatgen format: has @class or @module markers (may also have branches)
|
|
36
147
|
// - matterviz format: has qpoints + branches (no pymatgen markers)
|
|
37
148
|
const has_qpoints = `qpoints` in band_structs &&
|
|
38
|
-
|
|
39
|
-
|
|
149
|
+
Array.isArray(band_structs.qpoints) &&
|
|
150
|
+
band_structs.qpoints.length > 0
|
|
40
151
|
const has_kpoints = `kpoints` in band_structs &&
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const has_bands = `bands` in band_structs
|
|
152
|
+
Array.isArray(band_structs.kpoints) &&
|
|
153
|
+
band_structs.kpoints.length > 0
|
|
154
|
+
const has_bands = `bands` in band_structs
|
|
44
155
|
const has_frequencies_cm = `frequencies_cm` in band_structs &&
|
|
45
|
-
|
|
46
|
-
const has_branches = `branches` in band_structs
|
|
156
|
+
Array.isArray(band_structs.frequencies_cm)
|
|
157
|
+
const has_branches = `branches` in band_structs
|
|
47
158
|
// Pymatgen structures have explicit class/module markers
|
|
48
|
-
const is_pymatgen = `@class` in band_structs || `@module` in band_structs
|
|
159
|
+
const is_pymatgen = `@class` in band_structs || `@module` in band_structs
|
|
160
|
+
|
|
49
161
|
// Pymatgen single: has markers and point/band data (may have branches too)
|
|
50
162
|
const is_pymatgen_single = is_pymatgen &&
|
|
51
|
-
|
|
52
|
-
|
|
163
|
+
(has_qpoints || has_kpoints) &&
|
|
164
|
+
(has_bands || has_frequencies_cm)
|
|
53
165
|
// Matterviz single: has qpoints + branches but NO pymatgen markers
|
|
54
|
-
const is_matterviz_single = !is_pymatgen && has_qpoints && has_branches
|
|
55
|
-
const is_single = is_matterviz_single || is_pymatgen_single
|
|
56
|
-
|
|
166
|
+
const is_matterviz_single = !is_pymatgen && has_qpoints && has_branches
|
|
167
|
+
const is_single = is_matterviz_single || is_pymatgen_single
|
|
168
|
+
|
|
169
|
+
const result: Record<string, BaseBandStructure> = {}
|
|
170
|
+
|
|
57
171
|
if (is_single) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
172
|
+
const normalized = helpers.normalize_band_structure(band_structs)
|
|
173
|
+
if (normalized) result.default = normalized
|
|
174
|
+
} else {
|
|
175
|
+
for (const [key, bs] of Object.entries(band_structs)) {
|
|
176
|
+
const normalized = helpers.normalize_band_structure(bs)
|
|
177
|
+
if (normalized) result[key] = normalized
|
|
178
|
+
}
|
|
61
179
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
});
|
|
71
|
-
// Auto-detect band type if not explicitly set
|
|
72
|
-
let detected_band_type = $derived.by(() => {
|
|
73
|
-
if (band_type)
|
|
74
|
-
return band_type;
|
|
75
|
-
if (!band_structs)
|
|
76
|
-
return `phonon`;
|
|
180
|
+
return result
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// Auto-detect band type if not explicitly set
|
|
184
|
+
let detected_band_type = $derived.by((): BandStructureType => {
|
|
185
|
+
if (band_type) return band_type
|
|
186
|
+
if (!band_structs) return `phonon`
|
|
187
|
+
|
|
77
188
|
// Single structure has marker fields; dict of structures has label keys
|
|
78
189
|
const is_single = `@class` in band_structs || `@module` in band_structs ||
|
|
79
|
-
|
|
80
|
-
const source = (is_single ? band_structs : Object.values(band_structs)[0])
|
|
81
|
-
|
|
82
|
-
|
|
190
|
+
`kpoints` in band_structs || `qpoints` in band_structs
|
|
191
|
+
const source = (is_single ? band_structs : Object.values(band_structs)[0]) as
|
|
192
|
+
| Record<string, unknown>
|
|
193
|
+
| undefined
|
|
194
|
+
if (!source) return `phonon`
|
|
195
|
+
|
|
83
196
|
// Electronic: has kpoints, BandStructure* class (not Phonon*), or electronic_structure module
|
|
84
|
-
const py_class_name = String(source[`@class`] ?? ``)
|
|
85
|
-
if (
|
|
197
|
+
const py_class_name = String(source[`@class`] ?? ``)
|
|
198
|
+
if (
|
|
199
|
+
(`kpoints` in source && Array.isArray(source.kpoints) &&
|
|
86
200
|
source.kpoints.length > 0) ||
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
|
|
201
|
+
(py_class_name.startsWith(`BandStructure`) &&
|
|
202
|
+
!py_class_name.startsWith(`Phonon`)) ||
|
|
203
|
+
String(source[`@module`] ?? ``).includes(`electronic_structure`)
|
|
204
|
+
) return `electronic`
|
|
205
|
+
|
|
206
|
+
return `phonon`
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// Auto-detect Fermi level from electronic band structure data if not explicitly provided
|
|
210
|
+
let effective_fermi_level = $derived.by((): number | undefined => {
|
|
211
|
+
if (fermi_level !== undefined) return fermi_level
|
|
212
|
+
if (detected_band_type !== `electronic`) return undefined
|
|
213
|
+
|
|
99
214
|
// Check raw input for efermi field
|
|
100
|
-
const source = `efermi` in band_structs
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const efermi = source?.efermi
|
|
104
|
-
return typeof efermi === `number` ? efermi : undefined
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
215
|
+
const source = `efermi` in (band_structs as object)
|
|
216
|
+
? band_structs
|
|
217
|
+
: Object.values(band_structs)[0]
|
|
218
|
+
const efermi = (source as Record<string, unknown>)?.efermi
|
|
219
|
+
return typeof efermi === `number` ? efermi : undefined
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
let effective_spin_mode = $derived.by((): BandsSpinMode => {
|
|
223
|
+
if (detected_band_type !== `electronic`) return null
|
|
224
|
+
return (band_spin_mode === `up_only` || band_spin_mode === `down_only`)
|
|
225
|
+
? band_spin_mode
|
|
226
|
+
: `overlay`
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const convert_band_values = (values: number[]): number[] => {
|
|
230
|
+
if (detected_band_type !== `phonon`) return values
|
|
231
|
+
if (units === `THz`) return values
|
|
232
|
+
return helpers.convert_frequencies(values, units)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Collect all path segments across structures once (shared by strict checks and plotting)
|
|
236
|
+
let all_segments = $derived.by(() => {
|
|
237
|
+
const all_segments: Record<string, [string, BaseBandStructure][]> = {}
|
|
110
238
|
for (const [label, bs] of Object.entries(band_structs_dict)) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
239
|
+
for (const branch of bs.branches) {
|
|
240
|
+
const start_label = bs.qpoints[branch.start_index]?.label ?? undefined
|
|
241
|
+
const end_label = bs.qpoints[branch.end_index]?.label ?? undefined
|
|
242
|
+
const segment_key = helpers.get_segment_key(start_label, end_label)
|
|
243
|
+
all_segments[segment_key] ??= []
|
|
244
|
+
all_segments[segment_key].push([label, bs])
|
|
245
|
+
}
|
|
118
246
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
247
|
+
return all_segments
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
let num_structures = $derived(Object.keys(band_structs_dict).length)
|
|
251
|
+
let all_segment_keys = $derived(Object.keys(all_segments))
|
|
252
|
+
let common_segment_keys = $derived.by(() =>
|
|
253
|
+
all_segment_keys.filter(
|
|
254
|
+
(segment_key) => all_segments[segment_key].length === num_structures,
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
let empty_state_attrs = $derived.by(() => {
|
|
258
|
+
const attrs: Record<string, Dom_attr_value> = {}
|
|
259
|
+
for (const [attr_name, attr_value] of Object.entries(rest)) {
|
|
260
|
+
if (
|
|
261
|
+
(attr_name === `role` || attr_name.startsWith(`aria-`)) &&
|
|
262
|
+
is_dom_attr_value(attr_value)
|
|
263
|
+
) {
|
|
264
|
+
attrs[attr_name] = attr_value
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return attrs
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
// Compute path mismatch details for strict mode handling
|
|
271
|
+
let strict_path_error = $derived.by((): string | null => {
|
|
272
|
+
if (path_mode !== `strict`) return null
|
|
273
|
+
return common_segment_keys.length === all_segment_keys.length
|
|
274
|
+
? null
|
|
275
|
+
: `Band structures have different q-point paths. Switch to path_mode="union" or "intersection" to compare non-identical paths.`
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
// Determine which segments to plot based on path_mode
|
|
279
|
+
let segments_to_plot = $derived.by(() => {
|
|
280
|
+
if (path_mode === `union`) return new Set(all_segment_keys)
|
|
281
|
+
return new Set(common_segment_keys)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
// Map segments to x-axis positions
|
|
285
|
+
$effect(() => {
|
|
286
|
+
if (Object.keys(band_structs_dict).length === 0 || segments_to_plot.size === 0) {
|
|
287
|
+
x_positions = {}
|
|
288
|
+
return
|
|
130
289
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Map segments to x-axis positions
|
|
135
|
-
$effect(() => {
|
|
136
|
-
const positions = {};
|
|
137
|
-
let current_x = 0;
|
|
290
|
+
const positions: Record<string, [number, number]> = {}
|
|
291
|
+
let current_x = 0
|
|
292
|
+
|
|
138
293
|
// Preserve physical path order using the first available structure
|
|
139
|
-
const canonical = Object.values(band_structs_dict)[0]
|
|
140
|
-
|
|
294
|
+
const canonical = Object.values(band_structs_dict)[0]
|
|
295
|
+
if (!canonical) {
|
|
296
|
+
x_positions = {}
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
const ordered_segments = helpers.get_ordered_segments(canonical, segments_to_plot)
|
|
300
|
+
|
|
141
301
|
for (let seg_idx = 0; seg_idx < ordered_segments.length; seg_idx++) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
302
|
+
const segment_key = ordered_segments[seg_idx]
|
|
303
|
+
if (positions[segment_key]) continue
|
|
304
|
+
|
|
305
|
+
const [start_label, end_label] = segment_key.split(`_`)
|
|
306
|
+
|
|
307
|
+
// Find the first band structure that has this segment
|
|
308
|
+
for (const bs of Object.values(band_structs_dict)) {
|
|
309
|
+
const matching_branch = bs.branches.find((branch) => {
|
|
310
|
+
const branch_start = bs.qpoints[branch.start_index]?.label || `null`
|
|
311
|
+
const branch_end = bs.qpoints[branch.end_index]?.label || `null`
|
|
312
|
+
return branch_start === start_label && branch_end === end_label
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
if (matching_branch) {
|
|
316
|
+
// Check if this is a discontinuity: consecutive indices mean no path between points
|
|
317
|
+
const is_discontinuity =
|
|
318
|
+
matching_branch.end_index - matching_branch.start_index === 1
|
|
319
|
+
|
|
320
|
+
if (is_discontinuity) {
|
|
321
|
+
// Place at same x position as current, no advancement
|
|
322
|
+
positions[segment_key] = [current_x, current_x]
|
|
323
|
+
} else {
|
|
324
|
+
const segment_len = bs.distance[matching_branch.end_index] -
|
|
325
|
+
bs.distance[matching_branch.start_index]
|
|
326
|
+
positions[segment_key] = [current_x, current_x + segment_len]
|
|
327
|
+
current_x += segment_len
|
|
328
|
+
}
|
|
329
|
+
break
|
|
168
330
|
}
|
|
331
|
+
}
|
|
169
332
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
333
|
+
|
|
334
|
+
x_positions = positions
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
// Convert band structures to scatter plot series + track max slope in one pass
|
|
338
|
+
let { series_data, max_abs_slope } = $derived.by((): {
|
|
339
|
+
series_data: DataSeries[]
|
|
340
|
+
max_abs_slope: number
|
|
341
|
+
} => {
|
|
174
342
|
if (Object.keys(band_structs_dict).length === 0 || segments_to_plot.size === 0) {
|
|
175
|
-
|
|
343
|
+
return { series_data: [], max_abs_slope: 1 }
|
|
176
344
|
}
|
|
177
|
-
|
|
345
|
+
|
|
346
|
+
const all_series: DataSeries[] = []
|
|
347
|
+
let max_slope = 0
|
|
348
|
+
|
|
178
349
|
for (const [bs_idx, [label, bs]] of Object.entries(band_structs_dict).entries()) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
350
|
+
const color = PLOT_COLORS[bs_idx % PLOT_COLORS.length]
|
|
351
|
+
const structure_label = label || `Structure ${bs_idx + 1}`
|
|
352
|
+
const gamma_indices = detected_band_type === `phonon`
|
|
353
|
+
? helpers.find_gamma_indices(bs)
|
|
354
|
+
: []
|
|
355
|
+
|
|
356
|
+
for (const branch of bs.branches) {
|
|
357
|
+
const start_idx = branch.start_index
|
|
358
|
+
const end_idx = branch.end_index + 1
|
|
359
|
+
const start_label = bs.qpoints[start_idx]?.label ?? undefined
|
|
360
|
+
const end_label = bs.qpoints[end_idx - 1]?.label ?? undefined
|
|
361
|
+
const segment_key = helpers.get_segment_key(start_label, end_label)
|
|
362
|
+
|
|
363
|
+
if (!segments_to_plot.has(segment_key)) continue
|
|
364
|
+
|
|
365
|
+
// Skip discontinuous segments (consecutive labeled points)
|
|
366
|
+
const is_discontinuity = branch.end_index - branch.start_index === 1
|
|
367
|
+
if (is_discontinuity) continue
|
|
368
|
+
|
|
369
|
+
const [x_start, x_end] = x_positions?.[segment_key] || [0, 1]
|
|
370
|
+
|
|
371
|
+
// Scale distances for this segment
|
|
372
|
+
const segment_distances = bs.distance.slice(start_idx, end_idx)
|
|
373
|
+
const scaled_distances = helpers.scale_segment_distances(
|
|
374
|
+
segment_distances,
|
|
375
|
+
x_start,
|
|
376
|
+
x_end,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
// Create series for each band (and spin channel for electronic structures)
|
|
380
|
+
for (let band_idx = 0; band_idx < bs.nb_bands; band_idx++) {
|
|
381
|
+
const frequencies = convert_band_values(
|
|
382
|
+
bs.bands[band_idx].slice(start_idx, end_idx),
|
|
383
|
+
)
|
|
384
|
+
const is_acoustic = helpers.classify_acoustic(bs, band_idx, gamma_indices)
|
|
385
|
+
|
|
386
|
+
const line_style_up = get_line_style(
|
|
387
|
+
color,
|
|
388
|
+
is_acoustic === true,
|
|
389
|
+
frequencies,
|
|
390
|
+
band_idx,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
const spin_down_band = bs.spin_down_bands?.[band_idx]
|
|
394
|
+
const has_spin_down_channel = detected_band_type === `electronic` &&
|
|
395
|
+
Array.isArray(spin_down_band) &&
|
|
396
|
+
spin_down_band.length >= end_idx
|
|
397
|
+
|
|
398
|
+
const track_max_slope = (meta: helpers.BandPointMeta[]) => {
|
|
399
|
+
for (const pt of meta) {
|
|
400
|
+
if (typeof pt.slope === `number` && Number.isFinite(pt.slope)) {
|
|
401
|
+
max_slope = Math.max(max_slope, Math.abs(pt.slope))
|
|
402
|
+
}
|
|
212
403
|
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (effective_spin_mode !== `down_only`) {
|
|
407
|
+
const meta = helpers.build_point_metadata({
|
|
408
|
+
x_vals: scaled_distances,
|
|
409
|
+
y_vals: frequencies,
|
|
410
|
+
band_idx,
|
|
411
|
+
spin: `up`,
|
|
412
|
+
is_acoustic,
|
|
413
|
+
bs,
|
|
414
|
+
start_idx,
|
|
415
|
+
})
|
|
416
|
+
track_max_slope(meta)
|
|
417
|
+
all_series.push({
|
|
418
|
+
x: scaled_distances,
|
|
419
|
+
y: frequencies,
|
|
420
|
+
markers: `line`,
|
|
421
|
+
label: has_spin_down_channel
|
|
422
|
+
? `${structure_label} (↑)`
|
|
423
|
+
: structure_label,
|
|
424
|
+
line_style: line_style_up,
|
|
425
|
+
metadata: meta,
|
|
426
|
+
})
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (has_spin_down_channel && effective_spin_mode !== `up_only`) {
|
|
430
|
+
const spin_down_frequencies = convert_band_values(
|
|
431
|
+
spin_down_band.slice(start_idx, end_idx),
|
|
432
|
+
)
|
|
433
|
+
const meta = helpers.build_point_metadata({
|
|
434
|
+
x_vals: scaled_distances,
|
|
435
|
+
y_vals: spin_down_frequencies,
|
|
436
|
+
band_idx,
|
|
437
|
+
spin: `down`,
|
|
438
|
+
is_acoustic,
|
|
439
|
+
bs,
|
|
440
|
+
start_idx,
|
|
441
|
+
})
|
|
442
|
+
track_max_slope(meta)
|
|
443
|
+
all_series.push({
|
|
444
|
+
x: scaled_distances,
|
|
445
|
+
y: spin_down_frequencies,
|
|
446
|
+
markers: `line`,
|
|
447
|
+
label: `${structure_label} (↓)`,
|
|
448
|
+
line_style: {
|
|
449
|
+
...line_style_up,
|
|
450
|
+
line_dash: `4,2`,
|
|
451
|
+
stroke_width: Math.max(1, line_style_up.stroke_width - 0.1),
|
|
452
|
+
},
|
|
453
|
+
metadata: meta,
|
|
454
|
+
})
|
|
455
|
+
}
|
|
213
456
|
}
|
|
457
|
+
}
|
|
214
458
|
}
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
459
|
+
|
|
460
|
+
return { series_data: all_series, max_abs_slope: max_slope || 1 }
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
// Compute ribbon data for bands with width information
|
|
464
|
+
let ribbon_data = $derived.by((): RibbonData[] => {
|
|
219
465
|
if (Object.keys(band_structs_dict).length === 0 || segments_to_plot.size === 0) {
|
|
220
|
-
|
|
466
|
+
return []
|
|
221
467
|
}
|
|
222
|
-
|
|
468
|
+
|
|
469
|
+
const all_ribbons: RibbonData[] = []
|
|
470
|
+
|
|
223
471
|
for (const [bs_idx, [label, bs]] of Object.entries(band_structs_dict).entries()) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
472
|
+
// Skip if this band structure has no width data
|
|
473
|
+
if (!bs.band_widths || bs.band_widths.length === 0) continue
|
|
474
|
+
|
|
475
|
+
const color = PLOT_COLORS[bs_idx % PLOT_COLORS.length]
|
|
476
|
+
const structure_label = label || `Structure ${bs_idx + 1}`
|
|
477
|
+
const config = helpers.get_ribbon_config(ribbon_config, label)
|
|
478
|
+
|
|
479
|
+
for (const branch of bs.branches) {
|
|
480
|
+
const start_idx = branch.start_index
|
|
481
|
+
const end_idx = branch.end_index + 1
|
|
482
|
+
const start_label = bs.qpoints[start_idx]?.label ?? undefined
|
|
483
|
+
const end_label = bs.qpoints[end_idx - 1]?.label ?? undefined
|
|
484
|
+
const segment_key = helpers.get_segment_key(start_label, end_label)
|
|
485
|
+
|
|
486
|
+
if (!segments_to_plot.has(segment_key)) continue
|
|
487
|
+
|
|
488
|
+
// Skip discontinuous segments
|
|
489
|
+
const is_discontinuity = branch.end_index - branch.start_index === 1
|
|
490
|
+
if (is_discontinuity) continue
|
|
491
|
+
|
|
492
|
+
const [x_start, x_end] = x_positions?.[segment_key] || [0, 1]
|
|
493
|
+
|
|
494
|
+
// Scale distances for this segment
|
|
495
|
+
const segment_distances = bs.distance.slice(start_idx, end_idx)
|
|
496
|
+
const scaled_distances = helpers.scale_segment_distances(
|
|
497
|
+
segment_distances,
|
|
498
|
+
x_start,
|
|
499
|
+
x_end,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
// Create ribbon data for each band that has width data
|
|
503
|
+
for (let band_idx = 0; band_idx < bs.nb_bands; band_idx++) {
|
|
504
|
+
const band_widths = bs.band_widths[band_idx]
|
|
505
|
+
if (!band_widths) continue
|
|
506
|
+
|
|
507
|
+
const width_values = band_widths.slice(start_idx, end_idx)
|
|
508
|
+
// Skip if all widths are zero or missing
|
|
509
|
+
if (width_values.every((wv) => !wv || wv <= 0)) continue
|
|
510
|
+
|
|
511
|
+
const y_values = convert_band_values(
|
|
512
|
+
bs.bands[band_idx].slice(start_idx, end_idx),
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
all_ribbons.push({
|
|
516
|
+
x_values: scaled_distances,
|
|
517
|
+
y_values,
|
|
518
|
+
width_values,
|
|
519
|
+
color: config.color ?? color,
|
|
520
|
+
opacity: config.opacity ?? 0.3,
|
|
521
|
+
max_width: config.max_width ?? 6,
|
|
522
|
+
scale: config.scale ?? 1,
|
|
523
|
+
band_idx,
|
|
524
|
+
structure_label,
|
|
525
|
+
segment_key,
|
|
526
|
+
})
|
|
269
527
|
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return all_ribbons
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
// Get x-axis tick positions with custom labels for symmetry points
|
|
535
|
+
let x_axis_ticks = $derived.by(() => {
|
|
536
|
+
const tick_map = new SvelteMap<number, string[]>()
|
|
537
|
+
const add_label = (pos: number, label: string) => {
|
|
538
|
+
let labels = tick_map.get(pos)
|
|
539
|
+
if (!labels) {
|
|
540
|
+
labels = []
|
|
541
|
+
tick_map.set(pos, labels)
|
|
542
|
+
}
|
|
543
|
+
if (!labels.includes(label)) labels.push(label)
|
|
270
544
|
}
|
|
271
|
-
|
|
272
|
-
});
|
|
273
|
-
// Get x-axis tick positions with custom labels for symmetry points
|
|
274
|
-
let x_axis_ticks = $derived.by(() => {
|
|
275
|
-
const tick_map = new SvelteMap();
|
|
545
|
+
|
|
276
546
|
Object.entries(x_positions ?? {})
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const [start_lbl, end_lbl] = segment_key.split(`_`)
|
|
547
|
+
.sort(([, [a]], [, [b]]) => a - b)
|
|
548
|
+
.forEach(([segment_key, [x_start, x_end]]) => {
|
|
549
|
+
const [start_lbl, end_lbl] = segment_key.split(`_`)
|
|
280
550
|
const pretty_start = start_lbl !== `null`
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const pretty_end = end_lbl !== `null` ? helpers.pretty_sym_point(end_lbl) :
|
|
551
|
+
? helpers.pretty_sym_point(start_lbl)
|
|
552
|
+
: ``
|
|
553
|
+
const pretty_end = end_lbl !== `null` ? helpers.pretty_sym_point(end_lbl) : ``
|
|
554
|
+
|
|
284
555
|
// Check if this is a discontinuity (zero-length segment)
|
|
285
|
-
const is_discontinuity = Math.abs(x_end - x_start) < 1e-6
|
|
556
|
+
const is_discontinuity = Math.abs(x_end - x_start) < 1e-6
|
|
557
|
+
|
|
286
558
|
if (is_discontinuity && pretty_start && pretty_end) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
labels.push(pretty_end);
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
// Normal segment with distinct start/end
|
|
298
|
-
if (pretty_start) {
|
|
299
|
-
if (!tick_map.has(x_start))
|
|
300
|
-
tick_map.set(x_start, []);
|
|
301
|
-
const labels = tick_map.get(x_start);
|
|
302
|
-
if (!labels.includes(pretty_start))
|
|
303
|
-
labels.push(pretty_start);
|
|
304
|
-
}
|
|
305
|
-
if (pretty_end) {
|
|
306
|
-
if (!tick_map.has(x_end))
|
|
307
|
-
tick_map.set(x_end, []);
|
|
308
|
-
const labels = tick_map.get(x_end);
|
|
309
|
-
if (!labels.includes(pretty_end))
|
|
310
|
-
labels.push(pretty_end);
|
|
311
|
-
}
|
|
559
|
+
// Combine labels at discontinuity points
|
|
560
|
+
add_label(x_start, pretty_start)
|
|
561
|
+
add_label(x_start, pretty_end)
|
|
562
|
+
} else {
|
|
563
|
+
// Normal segment with distinct start/end
|
|
564
|
+
if (pretty_start) add_label(x_start, pretty_start)
|
|
565
|
+
if (pretty_end) add_label(x_end, pretty_end)
|
|
312
566
|
}
|
|
313
|
-
|
|
567
|
+
})
|
|
568
|
+
|
|
314
569
|
// Merge labels at same position with pipe separator
|
|
315
|
-
return Object.fromEntries(
|
|
570
|
+
return Object.fromEntries(
|
|
571
|
+
Array.from(tick_map.entries()).map(([pos, labels]) => [
|
|
316
572
|
pos,
|
|
317
573
|
labels.join(` | `),
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
574
|
+
]),
|
|
575
|
+
)
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
let x_range = $derived.by((): Vec2 => {
|
|
579
|
+
const flat = Object.values(x_positions ?? {}).flat()
|
|
580
|
+
return [flat[0] ?? 0, flat.at(-1) ?? 1]
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
// Calculate y-range, enforcing 0 minimum for phonon bands without imaginary modes
|
|
584
|
+
let y_range = $derived.by((): Vec2 | undefined => {
|
|
585
|
+
const all_freqs = Object.values(band_structs_dict).flatMap((bs) => [
|
|
586
|
+
...bs.bands.flat(),
|
|
587
|
+
...(bs.spin_down_bands?.flat() ?? []),
|
|
588
|
+
])
|
|
589
|
+
// Keep electronic y-range independent of phonon unit conversion options.
|
|
590
|
+
const display_values = detected_band_type === `phonon`
|
|
591
|
+
? convert_band_values(all_freqs)
|
|
592
|
+
: all_freqs
|
|
593
|
+
if (!display_values.length) return undefined
|
|
594
|
+
const finite = display_values.filter(Number.isFinite)
|
|
595
|
+
if (!finite.length) return undefined
|
|
596
|
+
let min_val = Math.min(...finite), max_val = Math.max(...finite)
|
|
331
597
|
if (
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
598
|
+
// clamp phonon min to 0 if negatives are noise
|
|
599
|
+
detected_band_type === `phonon` && min_val < 0 &&
|
|
600
|
+
helpers.negative_fraction(finite) < helpers.IMAGINARY_MODE_NOISE_THRESHOLD
|
|
601
|
+
) {
|
|
602
|
+
min_val = 0
|
|
336
603
|
}
|
|
337
|
-
const padding = (max_val - min_val) * 0.02
|
|
338
|
-
return [min_val === 0 ? 0 : min_val - padding, max_val + padding]
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
604
|
+
const padding = (max_val - min_val) * 0.02
|
|
605
|
+
return [min_val === 0 ? 0 : min_val - padding, max_val + padding]
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
// Internal y_axis that ScatterPlot binds to - syncs zoom changes back to parent
|
|
609
|
+
let internal_y_axis = $derived({
|
|
610
|
+
label: detected_band_type === `phonon` ? `Frequency (${units})` : `Energy (eV)`,
|
|
343
611
|
format: `.2f`,
|
|
344
612
|
label_shift: { y: 15 },
|
|
345
613
|
range: y_range,
|
|
346
614
|
...y_axis,
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
// Sync zoom changes from ScatterPlot back to parent via bindable y_axis
|
|
618
|
+
// Also clears parent range when internal range becomes invalid (auto-range reset)
|
|
619
|
+
$effect(() => {
|
|
620
|
+
const range = internal_y_axis.range
|
|
352
621
|
if (helpers.is_valid_range(range)) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
622
|
+
if (y_axis.range?.[0] !== range[0] || y_axis.range?.[1] !== range[1]) {
|
|
623
|
+
y_axis = { ...y_axis, range }
|
|
624
|
+
}
|
|
625
|
+
return
|
|
357
626
|
}
|
|
358
627
|
// Range became invalid - clear parent's range to propagate reset
|
|
359
628
|
if (`range` in y_axis) {
|
|
360
|
-
|
|
361
|
-
|
|
629
|
+
const { range: _omit, ...rest } = y_axis
|
|
630
|
+
y_axis = rest
|
|
362
631
|
}
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
let has_series = $derived(series_data.length > 0)
|
|
635
|
+
let is_strict_path_error = $derived(path_mode === `strict` && !!strict_path_error)
|
|
636
|
+
|
|
637
|
+
let imaginary_mode_region = $derived.by((): FillRegion[] => {
|
|
638
|
+
if (
|
|
639
|
+
detected_band_type !== `phonon` ||
|
|
640
|
+
!shade_imaginary_modes ||
|
|
641
|
+
!y_range ||
|
|
642
|
+
y_range[0] >= 0
|
|
643
|
+
) return []
|
|
644
|
+
return [{
|
|
645
|
+
lower: y_range[0],
|
|
646
|
+
upper: 0,
|
|
647
|
+
fill: `var(--bands-imaginary-region-color, light-dark(#f8d7da, #5a1a1f))`,
|
|
648
|
+
fill_opacity: 0.2,
|
|
649
|
+
label: `Imaginary modes`,
|
|
650
|
+
show_in_legend: false,
|
|
651
|
+
z_index: `below-lines`,
|
|
652
|
+
}]
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
let custom_highlight_regions = $derived.by((): FillRegion[] =>
|
|
656
|
+
(highlight_regions ?? [])
|
|
657
|
+
.filter((region) =>
|
|
658
|
+
Number.isFinite(region.y_min) && Number.isFinite(region.y_max)
|
|
659
|
+
)
|
|
660
|
+
.map((region) => ({
|
|
661
|
+
lower: Math.min(region.y_min, region.y_max),
|
|
662
|
+
upper: Math.max(region.y_min, region.y_max),
|
|
663
|
+
fill: region.color ??
|
|
664
|
+
`var(--bands-highlight-region-color, light-dark(#f6e8c3, #4d3f20))`,
|
|
665
|
+
fill_opacity: region.opacity ?? 0.2,
|
|
666
|
+
label: region.label,
|
|
667
|
+
show_in_legend: Boolean(region.label),
|
|
668
|
+
z_index: `below-lines` as const,
|
|
669
|
+
}))
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
let fill_regions = $derived([
|
|
673
|
+
...imaginary_mode_region,
|
|
674
|
+
...custom_highlight_regions,
|
|
675
|
+
])
|
|
676
|
+
|
|
677
|
+
let electronic_gap_annotation = $derived.by(() => {
|
|
678
|
+
if (
|
|
679
|
+
!show_gap_annotation ||
|
|
680
|
+
detected_band_type !== `electronic` ||
|
|
681
|
+
effective_fermi_level === undefined
|
|
682
|
+
) return null
|
|
683
|
+
const all_energies = series_data.flatMap((series_item) =>
|
|
684
|
+
series_item.y.filter(Number.isFinite)
|
|
685
|
+
)
|
|
686
|
+
const occupied = all_energies.filter((energy) => energy <= effective_fermi_level)
|
|
687
|
+
const unoccupied = all_energies.filter((energy) => energy > effective_fermi_level)
|
|
688
|
+
if (!occupied.length || !unoccupied.length) return null
|
|
689
|
+
const vbm = Math.max(...occupied)
|
|
690
|
+
const cbm = Math.min(...unoccupied)
|
|
691
|
+
const gap = cbm - vbm
|
|
692
|
+
if (!(gap > 0)) return null
|
|
693
|
+
return { vbm, cbm, gap }
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
let empty_state_message = $derived.by(() => {
|
|
697
|
+
if (is_strict_path_error) {
|
|
698
|
+
return strict_path_error ?? `Path mismatch in strict mode.`
|
|
699
|
+
}
|
|
700
|
+
if (!band_structs || Object.keys(band_structs_dict).length === 0) {
|
|
701
|
+
return `No valid band structure data to display.`
|
|
702
|
+
}
|
|
703
|
+
if (!has_series) {
|
|
704
|
+
return `No plottable band segments were found in the provided data.`
|
|
705
|
+
}
|
|
706
|
+
return `No valid band structure data to display.`
|
|
707
|
+
})
|
|
366
708
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
{
|
|
709
|
+
let display = $state({ x_grid: false, y_grid: true, y_zero_line: true })
|
|
710
|
+
</script>
|
|
711
|
+
{#if has_series && !is_strict_path_error}
|
|
712
|
+
<ScatterPlot
|
|
713
|
+
{id}
|
|
714
|
+
class={class_name}
|
|
715
|
+
{style}
|
|
716
|
+
data-testid={data_testid}
|
|
717
|
+
series={series_data}
|
|
718
|
+
{fill_regions}
|
|
719
|
+
x_axis={{
|
|
720
|
+
label: `Wave Vector`,
|
|
721
|
+
ticks: Object.keys(x_axis_ticks).length > 0 ? x_axis_ticks : undefined,
|
|
722
|
+
format: ``,
|
|
723
|
+
range: x_range,
|
|
724
|
+
...x_axis,
|
|
725
|
+
}}
|
|
726
|
+
bind:y_axis={internal_y_axis}
|
|
727
|
+
bind:display
|
|
728
|
+
legend={show_legend && Object.keys(band_structs_dict).length > 1 ? {} : null}
|
|
729
|
+
hover_config={{ threshold_px: 50 }}
|
|
730
|
+
controls={{ show: show_controls }}
|
|
731
|
+
{...rest}
|
|
732
|
+
>
|
|
733
|
+
{#snippet tooltip({ x, y, y_formatted, label, metadata })}
|
|
734
|
+
{@const y_label_full = internal_y_axis.label ?? ``}
|
|
735
|
+
{@const [, y_label, y_unit] = y_label_full.match(/^(.+?)\s*\(([^)]+)\)$/) ??
|
|
385
736
|
[, y_label_full, ``]}
|
|
386
|
-
|
|
737
|
+
{@const segment = Object.entries(x_positions ?? {}).find(([, [start, end]]) =>
|
|
387
738
|
x >= start && x <= end
|
|
388
739
|
)}
|
|
389
|
-
|
|
740
|
+
{@const path = segment?.[0].split(`_`).map((lbl) =>
|
|
390
741
|
lbl !== `null` ? helpers.pretty_sym_point(lbl) : ``
|
|
391
742
|
).filter(Boolean).join(` → `) || null}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
(
|
|
405
|
-
|
|
406
|
-
{
|
|
743
|
+
{@const {
|
|
744
|
+
band_idx,
|
|
745
|
+
spin,
|
|
746
|
+
is_acoustic,
|
|
747
|
+
nb_bands,
|
|
748
|
+
frac_coords,
|
|
749
|
+
qpoint_label,
|
|
750
|
+
band_width,
|
|
751
|
+
slope,
|
|
752
|
+
} = (metadata ?? {}) as Partial<helpers.BandPointMeta>}
|
|
753
|
+
{@const num_structs = Object.keys(band_structs_dict).length}
|
|
754
|
+
{#if num_structs > 1 && label}<strong>{label}</strong><br />{/if}
|
|
755
|
+
{@html sanitize_html(y_label || `Value`)}: {y_formatted}{y_unit ? ` ${y_unit}` : ``}<br />
|
|
756
|
+
{#if path}Path: {path}<br />{/if}
|
|
757
|
+
{#if typeof band_idx === `number`}
|
|
758
|
+
Band: {band_idx + 1}{#if typeof nb_bands === `number`} / {
|
|
759
|
+
nb_bands
|
|
760
|
+
}{/if}
|
|
761
|
+
{#if typeof is_acoustic === `boolean`}
|
|
762
|
+
({is_acoustic ? `acoustic` : `optical`})
|
|
763
|
+
{:else if detected_band_type === `electronic` && effective_fermi_level !== undefined}
|
|
764
|
+
({y <= effective_fermi_level ? `valence` : `conduction`})
|
|
765
|
+
{/if}
|
|
766
|
+
{#if spin === `up` || spin === `down`}
|
|
767
|
+
{spin === `up` ? `↑` : `↓`}
|
|
768
|
+
{/if}
|
|
769
|
+
{/if}
|
|
770
|
+
{#if typeof qpoint_label === `string` && qpoint_label}
|
|
771
|
+
<br />At: {helpers.pretty_sym_point(qpoint_label)}
|
|
772
|
+
{/if}
|
|
773
|
+
{#if Array.isArray(frac_coords)}
|
|
774
|
+
<br />{detected_band_type === `electronic` ? `k` : `q`}: [{
|
|
775
|
+
frac_coords.map((coord: number) => format_num(coord, `.3f`)).join(`, `)
|
|
776
|
+
}]
|
|
777
|
+
{/if}
|
|
778
|
+
{#if typeof band_width === `number` && band_width > 0}
|
|
779
|
+
<br />Projection: {format_num(band_width, `.3~g`)}
|
|
780
|
+
{/if}
|
|
781
|
+
{#if typeof slope === `number` && Number.isFinite(slope)}
|
|
782
|
+
{@const rel = Math.abs(slope) / max_abs_slope}
|
|
783
|
+
<br />Dispersion: {rel < 0.15 ? `flat` : rel < 0.5 ? `moderate` : `steep`}
|
|
784
|
+
{/if}
|
|
785
|
+
{/snippet}
|
|
786
|
+
|
|
787
|
+
{#snippet controls_extra()}
|
|
788
|
+
{#if show_path_mode_control}
|
|
789
|
+
<SettingsSection
|
|
790
|
+
title="Path Mode"
|
|
791
|
+
current_values={{ path_mode }}
|
|
792
|
+
on_reset={() => (path_mode = `strict`)}
|
|
793
|
+
>
|
|
794
|
+
<div class="pane-row">
|
|
795
|
+
<label for="bands-path-mode">Mode:</label>
|
|
796
|
+
<select id="bands-path-mode" bind:value={path_mode}>
|
|
797
|
+
<option value="strict">strict</option>
|
|
798
|
+
<option value="intersection">intersection</option>
|
|
799
|
+
<option value="union">union</option>
|
|
800
|
+
</select>
|
|
801
|
+
</div>
|
|
802
|
+
</SettingsSection>
|
|
803
|
+
{/if}
|
|
804
|
+
|
|
805
|
+
{#if show_units_control && detected_band_type === `phonon`}
|
|
806
|
+
<SettingsSection
|
|
807
|
+
title="Units"
|
|
808
|
+
current_values={{ units }}
|
|
809
|
+
on_reset={() => (units = `THz`)}
|
|
810
|
+
>
|
|
811
|
+
<div class="pane-row">
|
|
812
|
+
<label for="bands-units">Frequency:</label>
|
|
813
|
+
<select id="bands-units" bind:value={units}>
|
|
814
|
+
<option value="THz">THz</option>
|
|
815
|
+
<option value="eV">eV</option>
|
|
816
|
+
<option value="meV">meV</option>
|
|
817
|
+
<option value="cm-1">cm-1</option>
|
|
818
|
+
<option value="Ha">Ha</option>
|
|
819
|
+
</select>
|
|
820
|
+
</div>
|
|
821
|
+
</SettingsSection>
|
|
822
|
+
{/if}
|
|
823
|
+
|
|
824
|
+
{#if show_spin_control && detected_band_type === `electronic`}
|
|
825
|
+
<SettingsSection
|
|
826
|
+
title="Spin Display"
|
|
827
|
+
current_values={{ band_spin_mode }}
|
|
828
|
+
on_reset={() => (band_spin_mode = `overlay`)}
|
|
829
|
+
>
|
|
830
|
+
<div class="pane-row">
|
|
831
|
+
<label for="bands-spin-mode">Mode:</label>
|
|
832
|
+
<select id="bands-spin-mode" bind:value={band_spin_mode}>
|
|
833
|
+
<option value="overlay">overlay</option>
|
|
834
|
+
<option value="up_only">up only</option>
|
|
835
|
+
<option value="down_only">down only</option>
|
|
836
|
+
</select>
|
|
837
|
+
</div>
|
|
838
|
+
</SettingsSection>
|
|
839
|
+
{/if}
|
|
840
|
+
|
|
841
|
+
{#if show_annotation_controls && detected_band_type === `electronic`}
|
|
842
|
+
<SettingsSection
|
|
843
|
+
title="Annotations"
|
|
844
|
+
current_values={{ show_gap_annotation }}
|
|
845
|
+
on_reset={() => (show_gap_annotation = true)}
|
|
846
|
+
>
|
|
847
|
+
<div class="pane-row pane-checkbox">
|
|
848
|
+
<input
|
|
849
|
+
id="bands-gap-annotation"
|
|
850
|
+
type="checkbox"
|
|
851
|
+
bind:checked={show_gap_annotation}
|
|
852
|
+
/>
|
|
853
|
+
<label for="bands-gap-annotation">Show band gap annotation</label>
|
|
854
|
+
</div>
|
|
855
|
+
</SettingsSection>
|
|
856
|
+
{/if}
|
|
857
|
+
{/snippet}
|
|
858
|
+
|
|
859
|
+
{#snippet user_content({ height, x_scale_fn, y_scale_fn, pad })}
|
|
860
|
+
<!-- Fat band ribbons (rendered behind band lines) -->
|
|
861
|
+
{#each ribbon_data as
|
|
862
|
+
ribbon
|
|
863
|
+
(`${ribbon.structure_label}-${ribbon.segment_key}-${ribbon.band_idx}`)
|
|
864
|
+
}
|
|
865
|
+
{@const path_d = helpers.generate_ribbon_path(
|
|
407
866
|
ribbon.x_values,
|
|
408
867
|
ribbon.y_values,
|
|
409
868
|
ribbon.width_values,
|
|
@@ -412,78 +871,170 @@ let display = $state({ x_grid: false, y_grid: true, y_zero_line: true });
|
|
|
412
871
|
ribbon.max_width,
|
|
413
872
|
ribbon.scale,
|
|
414
873
|
)}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
874
|
+
{#if path_d}
|
|
875
|
+
<path
|
|
876
|
+
d={path_d}
|
|
877
|
+
fill={ribbon.color}
|
|
878
|
+
opacity={ribbon.opacity}
|
|
879
|
+
stroke="none"
|
|
880
|
+
class="fat-band-ribbon"
|
|
881
|
+
/>
|
|
882
|
+
{/if}
|
|
883
|
+
{/each}
|
|
425
884
|
|
|
426
|
-
|
|
427
|
-
|
|
885
|
+
<!-- Symmetry point vertical lines (filter NaN from scale) -->
|
|
886
|
+
{#each Object.keys(x_axis_ticks).map(Number).map((x) => x_scale_fn(x)).filter(
|
|
428
887
|
Number.isFinite,
|
|
429
888
|
) as
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
889
|
+
scaled_x
|
|
890
|
+
(scaled_x)
|
|
891
|
+
}
|
|
892
|
+
<line
|
|
893
|
+
x1={scaled_x}
|
|
894
|
+
x2={scaled_x}
|
|
895
|
+
y1={pad.t}
|
|
896
|
+
y2={height - pad.b}
|
|
897
|
+
stroke="var(--bands-symmetry-line-color, light-dark(black, white))"
|
|
898
|
+
stroke-width="var(--bands-symmetry-line-width, 1)"
|
|
899
|
+
opacity="var(--bands-symmetry-line-opacity, 0.5)"
|
|
900
|
+
/>
|
|
901
|
+
{/each}
|
|
902
|
+
|
|
903
|
+
<!-- Shared geometry for Fermi level and gap annotations -->
|
|
904
|
+
{@const fermi_y = effective_fermi_level !== undefined
|
|
446
905
|
? y_scale_fn(effective_fermi_level)
|
|
447
906
|
: NaN}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
907
|
+
{@const bands_x_end = x_scale_fn(Object.values(x_positions ?? {}).flat().at(-1) ?? 1)}
|
|
908
|
+
{@const gap_data = electronic_gap_annotation}
|
|
909
|
+
{@const vbm_y = gap_data ? y_scale_fn(gap_data.vbm) : NaN}
|
|
910
|
+
{@const cbm_y = gap_data ? y_scale_fn(gap_data.cbm) : NaN}
|
|
911
|
+
{@const gap_mid_y = (vbm_y + cbm_y) / 2}
|
|
912
|
+
{@const ef_needs_offset = Number.isFinite(gap_mid_y) &&
|
|
913
|
+
Math.abs(fermi_y - gap_mid_y) < 16}
|
|
914
|
+
{@const ef_label_y = ef_needs_offset
|
|
915
|
+
? gap_mid_y + (fermi_y >= gap_mid_y ? 16 : -16)
|
|
916
|
+
: fermi_y}
|
|
917
|
+
|
|
918
|
+
<!-- Fermi level line for electronic bands -->
|
|
919
|
+
{#if Number.isFinite(fermi_y) && Number.isFinite(bands_x_end)}
|
|
920
|
+
<line
|
|
921
|
+
class="fermi-level-line"
|
|
922
|
+
x1={pad.l}
|
|
923
|
+
x2={bands_x_end}
|
|
924
|
+
y1={fermi_y}
|
|
925
|
+
y2={fermi_y}
|
|
926
|
+
stroke="var(--bands-fermi-line-color, light-dark(#e74c3c, #ff6b6b))"
|
|
927
|
+
stroke-width="var(--bands-fermi-line-width, 1.5)"
|
|
928
|
+
stroke-dasharray="var(--bands-fermi-line-dash, 6,3)"
|
|
929
|
+
opacity="var(--bands-fermi-line-opacity, 0.8)"
|
|
930
|
+
/>
|
|
931
|
+
{#if ef_needs_offset}
|
|
932
|
+
<line
|
|
933
|
+
x1={bands_x_end}
|
|
934
|
+
y1={fermi_y}
|
|
935
|
+
x2={bands_x_end + 3}
|
|
936
|
+
y2={ef_label_y}
|
|
937
|
+
stroke="var(--bands-fermi-line-color, light-dark(#e74c3c, #ff6b6b))"
|
|
938
|
+
stroke-width="0.7"
|
|
939
|
+
opacity="0.5"
|
|
940
|
+
/>
|
|
941
|
+
{/if}
|
|
942
|
+
<text
|
|
943
|
+
class="fermi-level-label"
|
|
944
|
+
x={bands_x_end + 4}
|
|
945
|
+
y={ef_label_y}
|
|
946
|
+
dy="0.35em"
|
|
947
|
+
font-size="10"
|
|
948
|
+
fill="var(--bands-fermi-line-color, light-dark(#e74c3c, #ff6b6b))"
|
|
949
|
+
opacity="0.9"
|
|
950
|
+
>
|
|
951
|
+
E<tspan dy="2" font-size="8">F</tspan>
|
|
952
|
+
</text>
|
|
953
|
+
{/if}
|
|
954
|
+
|
|
955
|
+
<!-- Reference frequency horizontal line -->
|
|
956
|
+
{@const ref_freq = reference_frequency != null
|
|
957
|
+
? convert_band_values([reference_frequency])[0]
|
|
958
|
+
: NaN}
|
|
959
|
+
{@const ref_y = Number.isFinite(ref_freq) ? y_scale_fn(ref_freq) : NaN}
|
|
960
|
+
{#if Number.isFinite(ref_y) && Number.isFinite(bands_x_end)}
|
|
961
|
+
<line
|
|
962
|
+
x1={pad.l}
|
|
963
|
+
x2={bands_x_end}
|
|
964
|
+
y1={ref_y}
|
|
965
|
+
y2={ref_y}
|
|
966
|
+
stroke="var(--bands-reference-line-color, light-dark(#d48860, #c47850))"
|
|
967
|
+
stroke-width="var(--bands-reference-line-width, 1)"
|
|
968
|
+
stroke-dasharray="var(--bands-reference-line-dash, 4,3)"
|
|
969
|
+
opacity="var(--bands-reference-line-opacity, 0.5)"
|
|
970
|
+
/>
|
|
971
|
+
{/if}
|
|
972
|
+
|
|
973
|
+
<!-- Electronic band edge and gap annotation -->
|
|
974
|
+
{#if gap_data && Number.isFinite(vbm_y) && Number.isFinite(cbm_y) &&
|
|
975
|
+
Number.isFinite(bands_x_end)}
|
|
976
|
+
{#each [
|
|
977
|
+
[vbm_y, `var(--bands-gap-vbm-color, light-dark(#1f77b4, #7db7ff))`],
|
|
978
|
+
[cbm_y, `var(--bands-gap-cbm-color, light-dark(#2ca02c, #7ddc7d))`],
|
|
979
|
+
] as [number, string][] as
|
|
980
|
+
[edge_y, color]
|
|
981
|
+
(edge_y)
|
|
982
|
+
}
|
|
983
|
+
<line
|
|
984
|
+
x1={pad.l}
|
|
985
|
+
x2={bands_x_end + 3}
|
|
986
|
+
y1={edge_y}
|
|
987
|
+
y2={edge_y}
|
|
988
|
+
stroke={color}
|
|
989
|
+
stroke-width="var(--bands-gap-line-width, 1)"
|
|
990
|
+
stroke-dasharray="var(--bands-gap-line-dash, 2,2)"
|
|
991
|
+
opacity="0.7"
|
|
992
|
+
/>
|
|
993
|
+
{/each}
|
|
994
|
+
<text
|
|
995
|
+
x={bands_x_end + 4}
|
|
996
|
+
y={gap_mid_y}
|
|
997
|
+
dy="0.35em"
|
|
998
|
+
font-size="10"
|
|
999
|
+
fill="var(--text-color)"
|
|
1000
|
+
>
|
|
1001
|
+
E<tspan dy="2" font-size="8">g:</tspan>
|
|
1002
|
+
<tspan dy="-2">{Number(gap_data.gap.toPrecision(4))} eV</tspan>
|
|
1003
|
+
</text>
|
|
1004
|
+
{/if}
|
|
1005
|
+
{/snippet}
|
|
1006
|
+
</ScatterPlot>
|
|
1007
|
+
{:else}
|
|
1008
|
+
<EmptyState
|
|
1009
|
+
{id}
|
|
1010
|
+
class={class_name}
|
|
1011
|
+
{style}
|
|
1012
|
+
data-testid={data_testid}
|
|
1013
|
+
{...empty_state_attrs}
|
|
1014
|
+
message={empty_state_message}
|
|
1015
|
+
/>
|
|
1016
|
+
{/if}
|
|
1017
|
+
|
|
1018
|
+
<style>
|
|
1019
|
+
.pane-row {
|
|
1020
|
+
display: flex;
|
|
1021
|
+
align-items: center;
|
|
1022
|
+
gap: 0.5em;
|
|
1023
|
+
margin: 0.3em 0;
|
|
1024
|
+
font-size: 0.9em;
|
|
1025
|
+
}
|
|
1026
|
+
.pane-row label {
|
|
1027
|
+
min-width: 4.5em;
|
|
1028
|
+
flex-shrink: 0;
|
|
1029
|
+
}
|
|
1030
|
+
.pane-row select {
|
|
1031
|
+
flex: 1;
|
|
1032
|
+
min-width: 0;
|
|
1033
|
+
}
|
|
1034
|
+
.pane-checkbox {
|
|
1035
|
+
gap: 0.4em;
|
|
1036
|
+
}
|
|
1037
|
+
.pane-checkbox label {
|
|
1038
|
+
min-width: 0;
|
|
1039
|
+
}
|
|
1040
|
+
</style>
|