matterviz 0.4.2 → 0.4.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/FilePicker.svelte +40 -50
- package/dist/api/optimade.js +10 -7
- package/dist/app.css +15 -0
- package/dist/brillouin/BrillouinZone.svelte +18 -9
- package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
- package/dist/brillouin/compute.js +10 -16
- package/dist/brillouin/geometry.js +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
- package/dist/chempot-diagram/async-compute.svelte.js +12 -13
- package/dist/chempot-diagram/color.d.ts +21 -1
- package/dist/chempot-diagram/color.js +70 -1
- package/dist/chempot-diagram/compute.d.ts +7 -0
- package/dist/chempot-diagram/compute.js +85 -26
- package/dist/chempot-diagram/export.js +1 -6
- package/dist/chempot-diagram/temperature.js +9 -10
- package/dist/composition/BarChart.svelte +7 -9
- package/dist/composition/Composition.svelte +3 -8
- package/dist/composition/Formula.svelte +2 -2
- package/dist/composition/FormulaFilter.svelte +27 -93
- package/dist/composition/format.js +6 -8
- package/dist/composition/parse.js +50 -105
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +6 -17
- package/dist/convex-hull/ConvexHull2D.svelte +0 -2
- package/dist/convex-hull/ConvexHull3D.svelte +19 -47
- package/dist/convex-hull/ConvexHull4D.svelte +65 -95
- package/dist/convex-hull/StructurePopup.svelte +1 -1
- package/dist/convex-hull/gas-thermodynamics.js +21 -53
- package/dist/convex-hull/helpers.js +1 -7
- package/dist/convex-hull/thermodynamics.js +19 -27
- package/dist/coordination/CoordinationBarPlot.svelte +12 -21
- package/dist/element/BohrAtom.svelte +0 -1
- package/dist/element/ElementTile.svelte +26 -44
- package/dist/element/data.d.ts +2 -0
- package/dist/element/data.js +5 -1
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -1
- package/dist/fermi-surface/FermiSurface.svelte +22 -23
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
- package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
- package/dist/fermi-surface/compute.js +22 -55
- package/dist/fermi-surface/parse.js +38 -70
- package/dist/file-viewer/JsonBrowser.svelte +1043 -0
- package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
- package/dist/file-viewer/PlotPanel.svelte +350 -0
- package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
- package/dist/file-viewer/detect.d.ts +11 -0
- package/dist/file-viewer/detect.js +381 -0
- package/dist/file-viewer/eligibility.d.ts +4 -0
- package/dist/file-viewer/eligibility.js +53 -0
- package/dist/file-viewer/host-protocol.d.ts +50 -0
- package/dist/file-viewer/host-transfer.d.ts +25 -0
- package/dist/file-viewer/host-transfer.js +42 -0
- package/dist/file-viewer/index.d.ts +5 -0
- package/dist/file-viewer/index.js +5 -0
- package/dist/file-viewer/main.d.ts +44 -0
- package/dist/file-viewer/main.js +535 -0
- package/dist/file-viewer/parse.d.ts +9 -0
- package/dist/file-viewer/parse.js +170 -0
- package/dist/file-viewer/plot-utils.d.ts +24 -0
- package/dist/file-viewer/plot-utils.js +191 -0
- package/dist/file-viewer/types.d.ts +4 -0
- package/dist/file-viewer/types.js +5 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/io/ExportPane.svelte +1 -1
- package/dist/io/decompress.d.ts +2 -0
- package/dist/io/decompress.js +3 -2
- package/dist/io/export.d.ts +7 -4
- package/dist/io/export.js +70 -18
- package/dist/io/file-drop.js +60 -35
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +28 -16
- package/dist/isosurface/Isosurface.svelte +687 -196
- package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +564 -85
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
- package/dist/isosurface/VolumeSlice.svelte +224 -0
- package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
- package/dist/isosurface/coloring.d.ts +20 -0
- package/dist/isosurface/coloring.js +118 -0
- package/dist/isosurface/geometry-worker-types.d.ts +38 -0
- package/dist/isosurface/geometry.worker.d.ts +1 -0
- package/dist/isosurface/geometry.worker.js +59 -0
- package/dist/isosurface/grid.d.ts +10 -0
- package/dist/isosurface/grid.js +31 -0
- package/dist/isosurface/index.d.ts +4 -0
- package/dist/isosurface/index.js +4 -0
- package/dist/isosurface/profile.d.ts +12 -0
- package/dist/isosurface/profile.js +19 -0
- package/dist/isosurface/sampling.d.ts +34 -0
- package/dist/isosurface/sampling.js +408 -0
- package/dist/isosurface/slice-rendering.d.ts +13 -0
- package/dist/isosurface/slice-rendering.js +79 -0
- package/dist/isosurface/slice.d.ts +22 -2
- package/dist/isosurface/slice.js +200 -116
- package/dist/isosurface/types.d.ts +24 -1
- package/dist/isosurface/types.js +120 -8
- package/dist/json-path.d.ts +4 -0
- package/dist/json-path.js +89 -0
- package/dist/labels.js +1 -1
- package/dist/layout/InfoCard.svelte +1 -3
- package/dist/layout/SettingsSection.svelte +52 -61
- package/dist/layout/SettingsSection.svelte.d.ts +6 -7
- package/dist/layout/ViewerChrome.svelte +7 -1
- package/dist/layout/json-tree/JsonNode.svelte +14 -48
- package/dist/layout/json-tree/JsonTree.svelte +48 -95
- package/dist/layout/json-tree/JsonValue.svelte +3 -18
- package/dist/layout/json-tree/utils.d.ts +0 -3
- package/dist/layout/json-tree/utils.js +50 -179
- package/dist/marching-cubes.d.ts +8 -1
- package/dist/marching-cubes.js +115 -48
- package/dist/math.d.ts +3 -2
- package/dist/math.js +29 -38
- package/dist/overlays/ContextMenu.svelte +15 -32
- package/dist/overlays/DraggablePane.svelte +39 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
- package/dist/overlays/GlassChip.svelte +3 -1
- package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
- package/dist/phase-diagram/build-diagram.js +1 -6
- package/dist/phase-diagram/svg-to-diagram.js +9 -14
- package/dist/plot/bar/BarPlot.svelte +38 -42
- package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
- package/dist/plot/box/BoxPlot.svelte +27 -23
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +1 -1
- package/dist/plot/core/data-transform.d.ts +1 -2
- package/dist/plot/core/data-transform.js +0 -10
- package/dist/plot/core/layout.d.ts +12 -5
- package/dist/plot/core/layout.js +37 -27
- package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
- package/dist/plot/core/pan-zoom.svelte.js +34 -17
- package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -21
- package/dist/plot/core/svg.js +0 -1
- package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
- package/dist/plot/core/utils/hierarchy-chart.js +12 -0
- package/dist/plot/core/utils/hierarchy-labels.js +3 -2
- package/dist/plot/histogram/Histogram.svelte +21 -26
- package/dist/plot/sankey/sankey.js +13 -9
- package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
- package/dist/plot/scatter/ScatterPlot.svelte +67 -52
- package/dist/plot/scatter/adaptive-density.d.ts +1 -1
- package/dist/plot/scatter/adaptive-density.js +36 -20
- package/dist/plot/sunburst/Sunburst.svelte +20 -20
- package/dist/plot/treemap/Treemap.svelte +175 -60
- package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
- package/dist/plot/treemap/index.d.ts +1 -0
- package/dist/plot/treemap/labels.d.ts +38 -0
- package/dist/plot/treemap/labels.js +80 -0
- package/dist/plot/treemap/treemap.d.ts +4 -2
- package/dist/plot/treemap/treemap.js +5 -2
- package/dist/rdf/RdfPlot.svelte +17 -35
- package/dist/rdf/calc-rdf.js +59 -44
- package/dist/rdf/index.d.ts +0 -1
- package/dist/sanitize.js +84 -26
- package/dist/scene/SceneCamera.svelte +1 -1
- package/dist/scene/SceneCamera.svelte.d.ts +1 -1
- package/dist/scene/index.d.ts +1 -2
- package/dist/scene/index.js +1 -1
- package/dist/scene/{props.js → props.svelte.js} +15 -1
- package/dist/settings.js +159 -482
- package/dist/spectral/Bands.svelte +151 -158
- package/dist/spectral/BrillouinBandsDos.svelte +4 -19
- package/dist/spectral/Dos.svelte +26 -24
- package/dist/spectral/helpers.js +40 -105
- package/dist/structure/AtomLegend.svelte +8 -13
- package/dist/structure/AtomLegend.svelte.d.ts +2 -3
- package/dist/structure/CellSelect.svelte +35 -10
- package/dist/structure/Structure.svelte +177 -99
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureCarousel.svelte +10 -3
- package/dist/structure/StructureControls.svelte +8 -26
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +65 -76
- package/dist/structure/StructureScene.svelte +75 -109
- package/dist/structure/StructureScene.svelte.d.ts +3 -1
- package/dist/structure/StructureViewport.svelte +120 -55
- package/dist/structure/StructureViewport.svelte.d.ts +3 -1
- package/dist/structure/atom-properties.d.ts +1 -1
- package/dist/structure/atom-properties.js +4 -3
- package/dist/structure/bond-order-perception.js +3 -8
- package/dist/structure/bonding.d.ts +0 -1
- package/dist/structure/bonding.js +16 -23
- package/dist/structure/export.js +50 -74
- package/dist/structure/format-detect.js +5 -7
- package/dist/structure/index.js +6 -12
- package/dist/structure/parse.js +38 -100
- package/dist/structure/partial-occupancy.js +7 -4
- package/dist/structure/pbc.js +8 -10
- package/dist/structure/polyhedra.js +5 -4
- package/dist/structure/prop-groups.d.ts +85 -0
- package/dist/structure/prop-groups.js +18 -0
- package/dist/symmetry/SymmetryStats.svelte +0 -4
- package/dist/symmetry/WyckoffTable.svelte +5 -4
- package/dist/symmetry/cell-transform.js +5 -14
- package/dist/symmetry/index.js +6 -9
- package/dist/table/HeatmapTable.svelte +61 -103
- package/dist/table/ToggleMenu.svelte +166 -44
- package/dist/table/ToggleMenu.svelte.d.ts +1 -1
- package/dist/theme/embedded.d.ts +18 -0
- package/dist/theme/embedded.js +206 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +1 -2
- package/dist/theme/themes.mjs +31 -60
- package/dist/tooltip/TooltipContent.svelte +1 -1
- package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
- package/dist/tooltip/index.d.ts +8 -1
- package/dist/trajectory/Trajectory.svelte +140 -120
- package/dist/trajectory/TrajectoryError.svelte +1 -66
- package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
- package/dist/trajectory/extract.js +19 -29
- package/dist/trajectory/format-detect.d.ts +2 -0
- package/dist/trajectory/format-detect.js +7 -5
- package/dist/trajectory/frame-reader.js +3 -4
- package/dist/trajectory/helpers.js +16 -49
- package/dist/trajectory/index.js +2 -8
- package/dist/trajectory/parse/ase.js +1 -1
- package/dist/trajectory/parse/index.d.ts +4 -3
- package/dist/trajectory/parse/index.js +12 -17
- package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
- package/dist/trajectory/parse/vaspout-h5.js +1 -4
- package/dist/trajectory/parse/xyz.js +48 -2
- package/dist/trajectory/plotting.js +14 -37
- package/dist/xrd/XrdPlot.svelte +43 -68
- package/dist/xrd/calc-xrd.js +22 -58
- package/dist/xrd/parse.js +13 -22
- package/package.json +40 -8
- package/readme.md +6 -5
- package/dist/scene/types.d.ts +0 -26
- package/dist/tooltip/types.d.ts +0 -8
- package/dist/trajectory/constants.d.ts +0 -6
- package/dist/trajectory/constants.js +0 -11
- /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
- /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { get_d3_interpolator } from '../colors';
|
|
2
|
+
import { extract_formula_elements } from '../composition/parse';
|
|
3
|
+
import { group } from 'd3-array';
|
|
2
4
|
import { scaleSequential } from 'd3-scale';
|
|
5
|
+
import { best_form_energy_for_formula as best_form_energy, formula_key_from_composition, } from './compute';
|
|
6
|
+
// Categorical palette for arity mode (element count)
|
|
7
|
+
export const ARITY_COLORS = [`#3498db`, `#2ecc71`, `#e67e22`, `#9b59b6`];
|
|
8
|
+
// Exhaustively typed over the numeric color modes so adding a new mode
|
|
9
|
+
// fails compilation here instead of rendering an undefined colorbar title
|
|
10
|
+
const COLOR_MODE_LABELS = {
|
|
11
|
+
energy: `Energy per atom (eV)`,
|
|
12
|
+
formation_energy: `Formation energy (eV/atom)`,
|
|
13
|
+
entries: `Entry count`,
|
|
14
|
+
};
|
|
3
15
|
// Resolve D3 interpolator with optional reverse for chempot color scales.
|
|
4
16
|
function get_chempot_interpolator(name, reverse) {
|
|
5
17
|
const raw = get_d3_interpolator(name);
|
|
6
18
|
return reverse ? (frac) => raw(1 - frac) : raw;
|
|
7
19
|
}
|
|
8
20
|
// Build sequential color scale from values and D3 interpolator name.
|
|
9
|
-
|
|
21
|
+
function make_chempot_color_scale(values, interpolator_name, reverse) {
|
|
10
22
|
const finite_values = values.filter(Number.isFinite);
|
|
11
23
|
if (finite_values.length === 0)
|
|
12
24
|
return null;
|
|
@@ -30,3 +42,60 @@ export function get_chempot_color_bar_config(color_scale, reverse) {
|
|
|
30
42
|
color_scale_domain: [0, 1],
|
|
31
43
|
};
|
|
32
44
|
}
|
|
45
|
+
// Per-formula domain colors plus color-bar range for the active color mode.
|
|
46
|
+
// Shared by ChemPotDiagram2D and ChemPotDiagram3D.
|
|
47
|
+
export function get_domain_color_data(opts) {
|
|
48
|
+
const { formulas, color_mode, entries, el_refs, energy_stats } = opts;
|
|
49
|
+
const colors = new Map();
|
|
50
|
+
if (color_mode === `none`)
|
|
51
|
+
return { colors, color_range: null };
|
|
52
|
+
if (color_mode === `arity`) {
|
|
53
|
+
for (const formula of formulas) {
|
|
54
|
+
const n_elements = extract_formula_elements(formula).length;
|
|
55
|
+
const color_idx = Math.min(n_elements, ARITY_COLORS.length) - 1;
|
|
56
|
+
colors.set(formula, ARITY_COLORS[Math.max(0, color_idx)]);
|
|
57
|
+
}
|
|
58
|
+
return { colors, color_range: null };
|
|
59
|
+
}
|
|
60
|
+
const entries_by_formula = color_mode === `formation_energy`
|
|
61
|
+
? group(entries, (entry) => formula_key_from_composition(entry.composition))
|
|
62
|
+
: new Map();
|
|
63
|
+
const get_value = (formula) => {
|
|
64
|
+
if (color_mode === `energy`)
|
|
65
|
+
return energy_stats.get(formula)?.min_energy_per_atom ?? null;
|
|
66
|
+
if (color_mode === `formation_energy`) {
|
|
67
|
+
return best_form_energy(entries_by_formula.get(formula) ?? [], formula, el_refs) ?? null;
|
|
68
|
+
}
|
|
69
|
+
return energy_stats.get(formula)?.matching_entry_count ?? 0;
|
|
70
|
+
};
|
|
71
|
+
const value_by_formula = new Map();
|
|
72
|
+
for (const formula of formulas) {
|
|
73
|
+
const value = get_value(formula);
|
|
74
|
+
if (value == null || !Number.isFinite(value))
|
|
75
|
+
continue;
|
|
76
|
+
value_by_formula.set(formula, value);
|
|
77
|
+
}
|
|
78
|
+
const values = [...value_by_formula.values()];
|
|
79
|
+
const scale = make_chempot_color_scale(values, opts.color_scale, opts.reverse_color_scale);
|
|
80
|
+
for (const formula of formulas) {
|
|
81
|
+
const value = value_by_formula.get(formula);
|
|
82
|
+
colors.set(formula, value != null && scale ? scale(value) : `#999`);
|
|
83
|
+
}
|
|
84
|
+
if (values.length === 0)
|
|
85
|
+
return { colors, color_range: null };
|
|
86
|
+
let [min_val, max_val] = [values[0], values[0]];
|
|
87
|
+
for (const value of values) {
|
|
88
|
+
if (value < min_val)
|
|
89
|
+
min_val = value;
|
|
90
|
+
if (value > max_val)
|
|
91
|
+
max_val = value;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
colors,
|
|
95
|
+
color_range: {
|
|
96
|
+
min: min_val,
|
|
97
|
+
max: Math.max(max_val, min_val + 1e-6),
|
|
98
|
+
label: COLOR_MODE_LABELS[color_mode],
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -8,6 +8,12 @@ export declare function get_min_entries_and_el_refs(entries: PhaseData[]): {
|
|
|
8
8
|
el_refs: Record<string, PhaseData>;
|
|
9
9
|
};
|
|
10
10
|
export declare function best_form_energy_for_formula(entries: PhaseData[], formula: string, el_refs: Record<string, PhaseData>): number | undefined;
|
|
11
|
+
export interface FormulaEnergyStats {
|
|
12
|
+
matching_entry_count: number;
|
|
13
|
+
min_energy_per_atom: number | null;
|
|
14
|
+
max_energy_per_atom: number | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function get_energy_stats_by_formula(entries: PhaseData[]): Map<string, FormulaEnergyStats>;
|
|
11
17
|
export declare const renormalize_entries: (entries: PhaseData[], el_refs: Record<string, PhaseData>, elements: string[]) => PhaseData[];
|
|
12
18
|
export declare function build_hyperplanes(min_entries: PhaseData[], el_refs: Record<string, PhaseData>, elements: string[]): {
|
|
13
19
|
hyperplanes: number[][];
|
|
@@ -44,5 +50,6 @@ export interface VisibleDomainLabel {
|
|
|
44
50
|
}
|
|
45
51
|
export declare function get_visible_domain_labels(face_positions: ArrayLike<number>, face_domain_map: string[], label_font_size_by_formula: ReadonlyMap<string, number>, pinned_labels?: VisibleDomainLabel[]): VisibleDomainLabel[];
|
|
46
52
|
export declare function get_ternary_combinations(elements: string[]): string[][];
|
|
53
|
+
export declare function entry_fingerprint(entry: PhaseData): string;
|
|
47
54
|
export declare function make_nd_cache_key(entries: PhaseData[], formal_chempots: boolean, default_min_limit: number, limits: ChemPotDiagramConfig[`limits`]): string;
|
|
48
55
|
export declare function compute_chempot_diagram(entries: PhaseData[], config?: ChemPotDiagramConfig): ChemPotDiagramData;
|
|
@@ -34,16 +34,28 @@ const get_reduced_formula = (composition) => {
|
|
|
34
34
|
.map(([elem, amt]) => [elem, Math.round(amt * factor)]));
|
|
35
35
|
};
|
|
36
36
|
// === Entry Helpers ===
|
|
37
|
-
//
|
|
37
|
+
// Energy per atom; NaN for invalid/non-finite so UI can skip without throwing.
|
|
38
38
|
export function get_energy_per_atom(entry) {
|
|
39
|
-
if (typeof entry.energy_per_atom === `number`)
|
|
40
|
-
return entry.energy_per_atom;
|
|
41
|
-
const atoms = count_atoms_in_composition(entry.composition);
|
|
42
|
-
if (atoms <= 0) {
|
|
43
|
-
throw new Error(`Invalid composition with non-positive atom count: ${JSON.stringify(entry.composition)}`);
|
|
39
|
+
if (typeof entry.energy_per_atom === `number`) {
|
|
40
|
+
return Number.isFinite(entry.energy_per_atom) ? entry.energy_per_atom : Number.NaN;
|
|
44
41
|
}
|
|
42
|
+
const atoms = count_atoms_in_composition(entry.composition);
|
|
43
|
+
if (atoms <= 0 || !Number.isFinite(entry.energy))
|
|
44
|
+
return Number.NaN;
|
|
45
45
|
return entry.energy / atoms;
|
|
46
46
|
}
|
|
47
|
+
// Same-formula EPA total order (keep aligned with make_nd_cache_key / entry_fingerprint).
|
|
48
|
+
function prefer_min_entry(candidate, candidate_epa, existing, existing_epa) {
|
|
49
|
+
if (candidate_epa !== existing_epa)
|
|
50
|
+
return candidate_epa < existing_epa;
|
|
51
|
+
if (Boolean(candidate.exclude_from_hull) !== Boolean(existing.exclude_from_hull)) {
|
|
52
|
+
return !candidate.exclude_from_hull;
|
|
53
|
+
}
|
|
54
|
+
if ((candidate.is_stable === true) !== (existing.is_stable === true)) {
|
|
55
|
+
return candidate.is_stable === true;
|
|
56
|
+
}
|
|
57
|
+
return (candidate.e_above_hull ?? Infinity) < (existing.e_above_hull ?? Infinity);
|
|
58
|
+
}
|
|
47
59
|
// Cache for reduced formula strings -- avoids recomputing get_reduced_formula
|
|
48
60
|
// in hot loops. Key is object identity (WeakMap), value is the formula string.
|
|
49
61
|
const formula_cache = new WeakMap();
|
|
@@ -69,8 +81,10 @@ export function get_min_entries_and_el_refs(entries) {
|
|
|
69
81
|
for (const entry of entries) {
|
|
70
82
|
const key = formula_key_from_composition(entry.composition);
|
|
71
83
|
const epa = get_energy_per_atom(entry);
|
|
84
|
+
if (!Number.isFinite(epa))
|
|
85
|
+
continue;
|
|
72
86
|
const existing = by_formula.get(key);
|
|
73
|
-
if (!existing || epa
|
|
87
|
+
if (!existing || prefer_min_entry(entry, epa, existing.entry, existing.epa)) {
|
|
74
88
|
by_formula.set(key, { entry, epa });
|
|
75
89
|
}
|
|
76
90
|
}
|
|
@@ -89,14 +103,20 @@ export function get_min_entries_and_el_refs(entries) {
|
|
|
89
103
|
function compute_form_energy_per_atom(entry, el_refs) {
|
|
90
104
|
const atom_count = count_atoms_in_composition(entry.composition);
|
|
91
105
|
const energy_per_atom = get_energy_per_atom(entry);
|
|
106
|
+
if (!(atom_count > 0) || !Number.isFinite(energy_per_atom))
|
|
107
|
+
return Number.NaN;
|
|
92
108
|
let ref_energy = 0;
|
|
93
109
|
for (const [element, amount] of Object.entries(entry.composition)) {
|
|
94
110
|
if (amount <= 0)
|
|
95
111
|
continue;
|
|
96
112
|
const fraction = amount / atom_count;
|
|
97
113
|
const ref_entry = el_refs[element];
|
|
98
|
-
if (ref_entry)
|
|
99
|
-
|
|
114
|
+
if (ref_entry) {
|
|
115
|
+
const ref_epa = get_energy_per_atom(ref_entry);
|
|
116
|
+
if (!Number.isFinite(ref_epa))
|
|
117
|
+
return Number.NaN;
|
|
118
|
+
ref_energy += fraction * ref_epa;
|
|
119
|
+
}
|
|
100
120
|
}
|
|
101
121
|
return energy_per_atom - ref_energy;
|
|
102
122
|
}
|
|
@@ -107,23 +127,57 @@ export function best_form_energy_for_formula(entries, formula, el_refs) {
|
|
|
107
127
|
if (formula_key_from_composition(entry.composition) !== formula)
|
|
108
128
|
continue;
|
|
109
129
|
const e_form = entry.e_form_per_atom ?? compute_form_energy_per_atom(entry, el_refs);
|
|
130
|
+
if (!Number.isFinite(e_form))
|
|
131
|
+
continue;
|
|
110
132
|
if (best_value === undefined || e_form < best_value)
|
|
111
133
|
best_value = e_form;
|
|
112
134
|
}
|
|
113
135
|
return best_value;
|
|
114
136
|
}
|
|
137
|
+
// Aggregate per-formula entry count and min/max energy per atom (used for
|
|
138
|
+
// region coloring and hover tooltips in the 2D/3D diagram components).
|
|
139
|
+
export function get_energy_stats_by_formula(entries) {
|
|
140
|
+
const stats = new Map();
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
const energy_per_atom = get_energy_per_atom(entry);
|
|
143
|
+
if (!Number.isFinite(energy_per_atom))
|
|
144
|
+
continue;
|
|
145
|
+
const formula_key = formula_key_from_composition(entry.composition);
|
|
146
|
+
const existing = stats.get(formula_key);
|
|
147
|
+
if (existing) {
|
|
148
|
+
existing.matching_entry_count += 1;
|
|
149
|
+
existing.min_energy_per_atom = Math.min(existing.min_energy_per_atom ?? energy_per_atom, energy_per_atom);
|
|
150
|
+
existing.max_energy_per_atom = Math.max(existing.max_energy_per_atom ?? energy_per_atom, energy_per_atom);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
stats.set(formula_key, {
|
|
154
|
+
matching_entry_count: 1,
|
|
155
|
+
min_energy_per_atom: energy_per_atom,
|
|
156
|
+
max_energy_per_atom: energy_per_atom,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return stats;
|
|
161
|
+
}
|
|
115
162
|
// Renormalize entry energies to be relative to elemental references (formal chemical potentials).
|
|
116
163
|
// For each entry, subtracts sum(x_i * E_ref_i) from its energy per atom.
|
|
117
164
|
export const renormalize_entries = (entries, el_refs, elements) => entries.map((entry) => {
|
|
118
165
|
const atoms = count_atoms_in_composition(entry.composition);
|
|
166
|
+
const base_epa = get_energy_per_atom(entry);
|
|
167
|
+
if (!(atoms > 0) || !Number.isFinite(base_epa))
|
|
168
|
+
return entry;
|
|
119
169
|
let renorm_energy = 0;
|
|
120
170
|
for (const el of elements) {
|
|
121
|
-
const frac =
|
|
171
|
+
const frac = (entry.composition[el] ?? 0) / atoms;
|
|
122
172
|
const ref = el_refs[el];
|
|
123
|
-
if (ref)
|
|
124
|
-
|
|
173
|
+
if (!ref)
|
|
174
|
+
continue;
|
|
175
|
+
const ref_epa = get_energy_per_atom(ref);
|
|
176
|
+
if (!Number.isFinite(ref_epa))
|
|
177
|
+
return entry;
|
|
178
|
+
renorm_energy += frac * ref_epa;
|
|
125
179
|
}
|
|
126
|
-
const new_energy_per_atom =
|
|
180
|
+
const new_energy_per_atom = base_epa - renorm_energy;
|
|
127
181
|
return {
|
|
128
182
|
...entry,
|
|
129
183
|
energy: new_energy_per_atom * atoms,
|
|
@@ -137,7 +191,10 @@ export function build_hyperplanes(min_entries, el_refs, elements) {
|
|
|
137
191
|
const n_elems = elements.length;
|
|
138
192
|
const element_ref_energies = elements.map((element) => {
|
|
139
193
|
const ref_entry = el_refs[element];
|
|
140
|
-
|
|
194
|
+
if (!ref_entry)
|
|
195
|
+
return 0;
|
|
196
|
+
const epa = get_energy_per_atom(ref_entry);
|
|
197
|
+
return Number.isFinite(epa) ? epa : 0;
|
|
141
198
|
});
|
|
142
199
|
const always_include = new Set(Object.values(el_refs));
|
|
143
200
|
const tol = 1e-6; // PhaseDiagram.formation_energy_tol
|
|
@@ -148,6 +205,8 @@ export function build_hyperplanes(min_entries, el_refs, elements) {
|
|
|
148
205
|
const atom_count = count_atoms_in_composition(entry.composition);
|
|
149
206
|
const composition = entry.composition;
|
|
150
207
|
const energy_per_atom = get_energy_per_atom(entry);
|
|
208
|
+
if (!(atom_count > 0) || !Number.isFinite(energy_per_atom))
|
|
209
|
+
continue;
|
|
151
210
|
const row = Array(n_elems + 1).fill(0);
|
|
152
211
|
let ref_energy = 0;
|
|
153
212
|
for (let elem_idx = 0; elem_idx < n_elems; elem_idx++) {
|
|
@@ -670,19 +729,19 @@ export function get_ternary_combinations(elements) {
|
|
|
670
729
|
return combos;
|
|
671
730
|
}
|
|
672
731
|
let nd_cache = null;
|
|
673
|
-
//
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
.map((entry) => [
|
|
732
|
+
// Fingerprint for N-D / async-compute cache keys (computed EPA; NaN → null).
|
|
733
|
+
export function entry_fingerprint(entry) {
|
|
734
|
+
const epa = get_energy_per_atom(entry);
|
|
735
|
+
return JSON.stringify([
|
|
678
736
|
formula_key_from_composition(entry.composition),
|
|
679
|
-
|
|
680
|
-
entry.is_stable ??
|
|
681
|
-
entry.e_above_hull ??
|
|
682
|
-
entry.exclude_from_hull ??
|
|
683
|
-
]
|
|
684
|
-
|
|
685
|
-
|
|
737
|
+
Number.isFinite(epa) ? epa : null,
|
|
738
|
+
entry.is_stable ?? null,
|
|
739
|
+
entry.e_above_hull ?? null,
|
|
740
|
+
entry.exclude_from_hull ?? null,
|
|
741
|
+
]);
|
|
742
|
+
}
|
|
743
|
+
export function make_nd_cache_key(entries, formal_chempots, default_min_limit, limits) {
|
|
744
|
+
return `${entries.map(entry_fingerprint).sort().join(`,`)}|${formal_chempots}|${default_min_limit}|${JSON.stringify(limits ?? {})}`;
|
|
686
745
|
}
|
|
687
746
|
// === Main Pipeline ===
|
|
688
747
|
// Compute the full chemical potential diagram from entries and config.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Export helpers for chemical potential diagrams (shared between 2D and 3D views).
|
|
2
2
|
import { dpi_to_scale } from '../io/export';
|
|
3
3
|
import { download } from '../io/fetch';
|
|
4
|
+
import { escape_html as xml_escape } from '../utils';
|
|
4
5
|
import * as THREE from 'three';
|
|
5
6
|
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
|
|
6
7
|
export const get_json_string = (payload) => JSON.stringify(payload, null, 2);
|
|
@@ -77,12 +78,6 @@ export function export_png_file(wrapper, basename, png_dpi) {
|
|
|
77
78
|
download(blob, `${basename}.png`, `image/png`);
|
|
78
79
|
}, `image/png`);
|
|
79
80
|
}
|
|
80
|
-
const xml_escape = (text) => text
|
|
81
|
-
.replaceAll(`&`, `&`)
|
|
82
|
-
.replaceAll(`<`, `<`)
|
|
83
|
-
.replaceAll(`>`, `>`)
|
|
84
|
-
.replaceAll(`"`, `"`)
|
|
85
|
-
.replaceAll(`'`, `'`);
|
|
86
81
|
// SVG snapshot: rasterized canvas as embedded image + overlay labels as real text nodes
|
|
87
82
|
export function export_svg_file(wrapper, basename, view_settings) {
|
|
88
83
|
const gl_canvas = get_gl_canvas(wrapper);
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { analyze_temperature_data, filter_entries_at_temperature, } from '../convex-hull/helpers';
|
|
2
2
|
import { CHEMPOT_DEFAULTS } from './types';
|
|
3
3
|
export const get_projection_source_entries = (entries, temp_filtered_entries) => (temp_filtered_entries.length > 0 ? temp_filtered_entries : entries);
|
|
4
|
-
const resolve_temp_filter_options = (config, props) => ({
|
|
5
|
-
interpolate: config.interpolate_temperature ??
|
|
6
|
-
props.interpolate_temperature ??
|
|
7
|
-
CHEMPOT_DEFAULTS.interpolate_temperature,
|
|
8
|
-
max_interpolation_gap: config.max_interpolation_gap ??
|
|
9
|
-
props.max_interpolation_gap ??
|
|
10
|
-
CHEMPOT_DEFAULTS.max_interpolation_gap,
|
|
11
|
-
});
|
|
12
4
|
export function get_temp_filter_payload(entries, temperature, config, props) {
|
|
13
5
|
const { has_temp_data, available_temperatures } = analyze_temperature_data(entries);
|
|
14
6
|
if (!has_temp_data || temperature === undefined) {
|
|
15
7
|
return { has_temp_data, available_temperatures, temp_filtered_entries: entries };
|
|
16
8
|
}
|
|
17
|
-
|
|
18
|
-
const temp_filtered_entries = filter_entries_at_temperature(entries, temperature,
|
|
9
|
+
// Option resolution order: config beats props beats defaults
|
|
10
|
+
const temp_filtered_entries = filter_entries_at_temperature(entries, temperature, {
|
|
11
|
+
interpolate: config.interpolate_temperature ??
|
|
12
|
+
props.interpolate_temperature ??
|
|
13
|
+
CHEMPOT_DEFAULTS.interpolate_temperature,
|
|
14
|
+
max_interpolation_gap: config.max_interpolation_gap ??
|
|
15
|
+
props.max_interpolation_gap ??
|
|
16
|
+
CHEMPOT_DEFAULTS.max_interpolation_gap,
|
|
17
|
+
});
|
|
19
18
|
return { has_temp_data, available_temperatures, temp_filtered_entries };
|
|
20
19
|
}
|
|
21
20
|
export function get_valid_temperature(temperature, has_temp_data, available_temperatures) {
|
|
@@ -210,15 +210,13 @@
|
|
|
210
210
|
class:hovered={hovered_element === segment.element}
|
|
211
211
|
onmouseenter={() => interactive && (hovered_element = segment.element)}
|
|
212
212
|
onmouseleave={() => interactive && (hovered_element = null)}
|
|
213
|
-
{...interactive
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
: {}}
|
|
213
|
+
{...interactive && {
|
|
214
|
+
role: `button`,
|
|
215
|
+
tabindex: 0,
|
|
216
|
+
'aria-label': `${segment.element}: ${segment.amount} ${
|
|
217
|
+
segment.amount === 1 ? `atom` : `atoms`
|
|
218
|
+
} (${format_num(segment.fraction, `.1~%`)})`,
|
|
219
|
+
}}
|
|
222
220
|
>
|
|
223
221
|
<title>
|
|
224
222
|
{segment.element}: {segment.amount}
|
|
@@ -60,14 +60,9 @@
|
|
|
60
60
|
{ value: `bar`, icon: `Graph`, label: `Bar Chart` },
|
|
61
61
|
] as const
|
|
62
62
|
|
|
63
|
-
const color_scheme_options =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
{ value: `Alloy`, icon: `ColorPalette`, label: `Alloy` },
|
|
67
|
-
{ value: `Pastel`, icon: `ColorPalette`, label: `Pastel` },
|
|
68
|
-
{ value: `Muted`, icon: `ColorPalette`, label: `Muted` },
|
|
69
|
-
{ value: `Dark Mode`, icon: `ColorPalette`, label: `Dark Mode` },
|
|
70
|
-
] as const
|
|
63
|
+
const color_scheme_options = (
|
|
64
|
+
[`Vesta`, `Jmol`, `Alloy`, `Pastel`, `Muted`, `Dark Mode`] as const
|
|
65
|
+
).map((value) => ({ value, icon: `ColorPalette`, label: value }))
|
|
71
66
|
|
|
72
67
|
const export_options = [
|
|
73
68
|
{ value: `copy_formula`, icon: `Copy`, label: `Copy Formula` },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { ColorSchemeName } from '../colors'
|
|
3
3
|
import { ELEMENT_COLOR_SCHEMES, luminance } from '../colors'
|
|
4
4
|
import type { ElementSymbol } from '../element'
|
|
5
|
-
import {
|
|
5
|
+
import { element_by_symbol } from '../element'
|
|
6
6
|
import ElementTile from '../element/ElementTile.svelte'
|
|
7
7
|
import { format_num } from '../labels'
|
|
8
8
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
let tooltip_pos = $state({ x: 0, y: 0 })
|
|
66
66
|
|
|
67
67
|
const hovered_elem_data = $derived(
|
|
68
|
-
hovered_element ?
|
|
68
|
+
hovered_element ? (element_by_symbol.get(hovered_element) ?? null) : null,
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
// Format formula as plain text for clipboard: "Li2 O" or "Fe(+3)2 O(-2)3"
|
|
@@ -61,32 +61,12 @@
|
|
|
61
61
|
},
|
|
62
62
|
]
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
[
|
|
67
|
-
[
|
|
68
|
-
[
|
|
69
|
-
[
|
|
70
|
-
[`\u2085`]: `5`,
|
|
71
|
-
[`\u2086`]: `6`,
|
|
72
|
-
[`\u2087`]: `7`,
|
|
73
|
-
[`\u2088`]: `8`,
|
|
74
|
-
[`\u2089`]: `9`,
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const SUPERSCRIPT_TO_ASCII: Record<string, string> = {
|
|
78
|
-
[`\u2070`]: `0`,
|
|
79
|
-
[`\u00B9`]: `1`,
|
|
80
|
-
[`\u00B2`]: `2`,
|
|
81
|
-
[`\u00B3`]: `3`,
|
|
82
|
-
[`\u2074`]: `4`,
|
|
83
|
-
[`\u2075`]: `5`,
|
|
84
|
-
[`\u2076`]: `6`,
|
|
85
|
-
[`\u2077`]: `7`,
|
|
86
|
-
[`\u2078`]: `8`,
|
|
87
|
-
[`\u2079`]: `9`,
|
|
88
|
-
[`\u207A`]: `+`,
|
|
89
|
-
[`\u207B`]: `-`,
|
|
64
|
+
// Unicode subscript/superscript digits and signs -> ASCII equivalents
|
|
65
|
+
const UNICODE_TO_ASCII: Record<string, string> = {
|
|
66
|
+
...Object.fromEntries([...`₀₁₂₃₄₅₆₇₈₉`].map((char, digit) => [char, `${digit}`])),
|
|
67
|
+
...Object.fromEntries([...`⁰¹²³⁴⁵⁶⁷⁸⁹`].map((char, digit) => [char, `${digit}`])),
|
|
68
|
+
[`⁺`]: `+`,
|
|
69
|
+
[`⁻`]: `-`,
|
|
90
70
|
}
|
|
91
71
|
|
|
92
72
|
let {
|
|
@@ -152,51 +132,31 @@
|
|
|
152
132
|
const has_storage = typeof localStorage !== `undefined`
|
|
153
133
|
const history_pins_key = $derived(`${history_key}-pins`)
|
|
154
134
|
|
|
155
|
-
function
|
|
135
|
+
function load_entries(key: string): string[] {
|
|
156
136
|
if (max_history <= 0 || !has_storage) return []
|
|
157
137
|
try {
|
|
158
|
-
const
|
|
159
|
-
if (!raw) return []
|
|
160
|
-
const parsed: unknown = JSON.parse(raw)
|
|
138
|
+
const parsed: unknown = JSON.parse(localStorage.getItem(key) ?? `null`)
|
|
161
139
|
if (!Array.isArray(parsed)) return []
|
|
162
|
-
return parsed
|
|
163
|
-
.filter((item): item is string => typeof item === `string`)
|
|
164
|
-
.slice(0, max_history)
|
|
140
|
+
return parsed.filter((item): item is string => typeof item === `string`)
|
|
165
141
|
} catch {
|
|
166
142
|
return []
|
|
167
143
|
}
|
|
168
144
|
}
|
|
169
145
|
|
|
170
|
-
function
|
|
146
|
+
function save_entries(key: string, entries: string[]): void {
|
|
171
147
|
if (max_history <= 0 || !has_storage) return
|
|
172
148
|
try {
|
|
173
|
-
localStorage.setItem(
|
|
149
|
+
localStorage.setItem(key, JSON.stringify(entries))
|
|
174
150
|
} catch {
|
|
175
151
|
// localStorage may be unavailable (e.g. private browsing)
|
|
176
152
|
}
|
|
177
153
|
}
|
|
178
154
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const parsed: unknown = JSON.parse(raw)
|
|
185
|
-
if (!Array.isArray(parsed)) return []
|
|
186
|
-
return parsed.filter((item): item is string => typeof item === `string`)
|
|
187
|
-
} catch {
|
|
188
|
-
return []
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function save_pinned(entries: string[]): void {
|
|
193
|
-
if (max_history <= 0 || !has_storage) return
|
|
194
|
-
try {
|
|
195
|
-
localStorage.setItem(history_pins_key, JSON.stringify(entries))
|
|
196
|
-
} catch {
|
|
197
|
-
// localStorage may be unavailable
|
|
198
|
-
}
|
|
199
|
-
}
|
|
155
|
+
const load_history = () => load_entries(history_key).slice(0, max_history)
|
|
156
|
+
const load_pinned = () => load_entries(history_pins_key)
|
|
157
|
+
const save_history = (entries: string[]) =>
|
|
158
|
+
save_entries(history_key, entries.slice(0, max_history))
|
|
159
|
+
const save_pinned = (entries: string[]) => save_entries(history_pins_key, entries)
|
|
200
160
|
|
|
201
161
|
let history = $state<string[]>(load_history())
|
|
202
162
|
let pinned_history = $state<string[]>(load_pinned())
|
|
@@ -325,11 +285,8 @@
|
|
|
325
285
|
|
|
326
286
|
function normalize_unicode_formula(input: string): string {
|
|
327
287
|
let normalized = input
|
|
328
|
-
for (const [
|
|
329
|
-
normalized = normalized.replaceAll(
|
|
330
|
-
}
|
|
331
|
-
for (const [superscript, ascii] of Object.entries(SUPERSCRIPT_TO_ASCII)) {
|
|
332
|
-
normalized = normalized.replaceAll(superscript, ascii)
|
|
288
|
+
for (const [unicode_char, ascii] of Object.entries(UNICODE_TO_ASCII)) {
|
|
289
|
+
normalized = normalized.replaceAll(unicode_char, ascii)
|
|
333
290
|
}
|
|
334
291
|
return (
|
|
335
292
|
normalized
|
|
@@ -410,13 +367,10 @@
|
|
|
410
367
|
return `${prefix}${token.element}${suffix}`
|
|
411
368
|
}
|
|
412
369
|
|
|
413
|
-
|
|
370
|
+
// Chip labels show an explicit + prefix for include tokens; serialized values omit it
|
|
371
|
+
const token_chip_label = (
|
|
414
372
|
token: Pick<FormulaFilterToken, `operator` | `element` | `constraint`>,
|
|
415
|
-
): string
|
|
416
|
-
const prefix = token.operator === `exclude` ? `-` : `+`
|
|
417
|
-
const suffix = token.constraint ? `:${token.constraint}` : ``
|
|
418
|
-
return `${prefix}${token.element}${suffix}`
|
|
419
|
-
}
|
|
373
|
+
): string => (token.operator === `include` ? `+` : ``) + serialize_token(token)
|
|
420
374
|
|
|
421
375
|
function parse_token(raw_token: string): FormulaFilterToken {
|
|
422
376
|
const token = raw_token.trim()
|
|
@@ -571,15 +525,8 @@
|
|
|
571
525
|
// Otherwise parse as formula (already returns sorted by default)
|
|
572
526
|
// For formulas with wildcards, we can't parse them normally
|
|
573
527
|
if (has_wildcards(trimmed)) {
|
|
574
|
-
// Use shared utility and extract unique elements
|
|
575
528
|
const tokens = parse_formula_with_wildcards(trimmed)
|
|
576
|
-
const
|
|
577
|
-
for (const token of tokens) {
|
|
578
|
-
if (token.element !== null && !unique_elements.includes(token.element)) {
|
|
579
|
-
unique_elements.push(token.element)
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
const elements = unique_elements.sort()
|
|
529
|
+
const elements = [...new Set(tokens.flatMap((token) => token.element ?? []))].sort()
|
|
583
530
|
const wildcards = tokens.filter((token) => token.element === null).map(() => `*`)
|
|
584
531
|
return [...elements, ...wildcards]
|
|
585
532
|
}
|
|
@@ -1124,24 +1071,7 @@
|
|
|
1124
1071
|
font-size: 0.88em;
|
|
1125
1072
|
color: inherit;
|
|
1126
1073
|
}
|
|
1127
|
-
.history-remove
|
|
1128
|
-
min-width: 24px;
|
|
1129
|
-
min-height: 24px;
|
|
1130
|
-
display: flex;
|
|
1131
|
-
align-items: center;
|
|
1132
|
-
justify-content: center;
|
|
1133
|
-
background: none;
|
|
1134
|
-
border: none;
|
|
1135
|
-
cursor: pointer;
|
|
1136
|
-
padding: 3pt;
|
|
1137
|
-
border-radius: 50%;
|
|
1138
|
-
opacity: 0.3;
|
|
1139
|
-
color: inherit;
|
|
1140
|
-
&:hover {
|
|
1141
|
-
opacity: 0.8;
|
|
1142
|
-
background: rgba(128, 128, 128, 0.15);
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1074
|
+
.history-remove,
|
|
1145
1075
|
.history-pin {
|
|
1146
1076
|
display: flex;
|
|
1147
1077
|
align-items: center;
|
|
@@ -1158,6 +1088,10 @@
|
|
|
1158
1088
|
background: rgba(128, 128, 128, 0.15);
|
|
1159
1089
|
}
|
|
1160
1090
|
}
|
|
1091
|
+
.history-remove {
|
|
1092
|
+
min-width: 24px;
|
|
1093
|
+
min-height: 24px;
|
|
1094
|
+
}
|
|
1161
1095
|
.examples-wrapper {
|
|
1162
1096
|
position: relative;
|
|
1163
1097
|
}
|
|
@@ -3,17 +3,15 @@ import { is_elem_symbol } from '../element';
|
|
|
3
3
|
import { ELEMENT_ELECTRONEGATIVITY_MAP, parse_composition } from './parse';
|
|
4
4
|
// Extract composition from structure object
|
|
5
5
|
const structure_to_composition = (structure) => {
|
|
6
|
-
if (!
|
|
7
|
-
throw new
|
|
6
|
+
if (!Array.isArray(structure.sites)) {
|
|
7
|
+
throw new TypeError(`Invalid structure object`);
|
|
8
8
|
}
|
|
9
9
|
const composition = {};
|
|
10
10
|
for (const site of structure.sites) {
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
composition[element] = (composition[element] ?? 0) + occu;
|
|
16
|
-
}
|
|
11
|
+
if (!Array.isArray(site.species))
|
|
12
|
+
continue;
|
|
13
|
+
for (const species of site.species) {
|
|
14
|
+
composition[species.element] = (composition[species.element] ?? 0) + (species.occu ?? 1);
|
|
17
15
|
}
|
|
18
16
|
}
|
|
19
17
|
return composition;
|