matterviz 0.3.5 → 0.3.7
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/MillerIndexInput.svelte +5 -5
- package/dist/api/optimade.js +3 -3
- package/dist/brillouin/BrillouinZone.svelte +5 -2
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +1 -3
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +1 -1
- package/dist/brillouin/BrillouinZoneScene.svelte +5 -5
- package/dist/brillouin/compute.js +21 -21
- package/dist/brillouin/index.d.ts +1 -1
- package/dist/brillouin/index.js +0 -1
- package/dist/brillouin/types.d.ts +8 -13
- package/dist/chempot-diagram/ChemPotDiagram.svelte +3 -3
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +3 -4
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +33 -34
- package/dist/chempot-diagram/compute.js +1 -7
- package/dist/chempot-diagram/temperature.d.ts +1 -1
- package/dist/chempot-diagram/temperature.js +1 -3
- package/dist/chempot-diagram/types.d.ts +4 -9
- package/dist/colors/index.js +5 -5
- package/dist/composition/Composition.svelte +2 -1
- package/dist/composition/Formula.svelte +7 -4
- package/dist/composition/FormulaFilter.svelte +1 -3
- package/dist/composition/format.js +4 -4
- package/dist/composition/parse.d.ts +2 -1
- package/dist/composition/parse.js +61 -46
- package/dist/convex-hull/ConvexHull2D.svelte +62 -51
- package/dist/convex-hull/ConvexHull3D.svelte +101 -90
- package/dist/convex-hull/ConvexHull4D.svelte +70 -58
- package/dist/convex-hull/ConvexHullControls.svelte +24 -35
- package/dist/convex-hull/ConvexHullInfoPane.svelte +8 -5
- package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +2 -0
- package/dist/convex-hull/ConvexHullStats.svelte +9 -2
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +2 -0
- package/dist/convex-hull/GasPressureControls.svelte +7 -7
- package/dist/convex-hull/StructurePopup.svelte +65 -30
- package/dist/convex-hull/StructurePopup.svelte.d.ts +6 -6
- package/dist/convex-hull/TemperatureSlider.svelte +8 -5
- package/dist/convex-hull/barycentric-coords.d.ts +2 -2
- package/dist/convex-hull/barycentric-coords.js +2 -2
- package/dist/convex-hull/gas-thermodynamics.js +2 -4
- package/dist/convex-hull/helpers.d.ts +13 -2
- package/dist/convex-hull/helpers.js +37 -16
- package/dist/convex-hull/index.d.ts +1 -0
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +2 -1
- package/dist/convex-hull/thermodynamics.js +7 -7
- package/dist/convex-hull/types.d.ts +15 -15
- package/dist/effects.svelte.d.ts +12 -0
- package/dist/effects.svelte.js +37 -0
- package/dist/element/BohrAtom.svelte +4 -4
- package/dist/element/data.json.gz.d.ts +3 -1
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +0 -1
- package/dist/fermi-surface/FermiSurface.svelte +4 -4
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte +15 -19
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +8 -6
- package/dist/fermi-surface/compute.js +2 -2
- package/dist/fermi-surface/export.js +13 -26
- package/dist/fermi-surface/parse.js +8 -12
- package/dist/fermi-surface/types.d.ts +2 -5
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +21 -3
- package/dist/heatmap-matrix/index.js +6 -6
- package/dist/io/decompress.d.ts +2 -1
- package/dist/io/decompress.js +1 -1
- package/dist/io/export.js +1 -1
- package/dist/io/index.d.ts +1 -1
- package/dist/io/index.js +0 -1
- package/dist/io/url-drop.js +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +11 -25
- package/dist/isosurface/slice.js +1 -1
- package/dist/isosurface/types.js +12 -12
- package/dist/labels.d.ts +1 -1
- package/dist/labels.js +14 -11
- package/dist/layout/InfoTag.svelte +6 -4
- package/dist/layout/PropertyFilter.svelte +4 -2
- package/dist/layout/json-tree/JsonTree.svelte +22 -14
- package/dist/layout/json-tree/JsonValue.svelte +2 -2
- package/dist/layout/json-tree/types.d.ts +3 -2
- package/dist/layout/json-tree/types.js +0 -1
- package/dist/layout/json-tree/utils.d.ts +4 -4
- package/dist/layout/json-tree/utils.js +12 -20
- package/dist/marching-cubes.js +13 -15
- package/dist/math.d.ts +11 -1
- package/dist/math.js +15 -6
- package/dist/overlays/DragControlTab.svelte +98 -0
- package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
- package/dist/overlays/DraggablePane.svelte +7 -84
- package/dist/overlays/index.d.ts +1 -0
- package/dist/overlays/index.js +1 -0
- package/dist/periodic-table/PeriodicTable.svelte +11 -11
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +4 -2
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramControls.svelte +4 -9
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +2 -10
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +2 -3
- package/dist/phase-diagram/TdbInfoPanel.svelte +3 -3
- package/dist/phase-diagram/build-diagram.js +11 -18
- package/dist/phase-diagram/diagram-input.d.ts +5 -9
- package/dist/phase-diagram/index.d.ts +2 -2
- package/dist/phase-diagram/index.js +0 -2
- package/dist/phase-diagram/parse.d.ts +2 -2
- package/dist/phase-diagram/parse.js +6 -10
- package/dist/phase-diagram/svg-to-diagram.js +15 -15
- package/dist/phase-diagram/types.d.ts +5 -11
- package/dist/phase-diagram/utils.d.ts +2 -2
- package/dist/phase-diagram/utils.js +9 -11
- package/dist/plot/BarPlot.svelte +162 -314
- package/dist/plot/BarPlot.svelte.d.ts +5 -4
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/BinnedScatterPlot.svelte +1114 -0
- package/dist/plot/BinnedScatterPlot.svelte.d.ts +66 -0
- package/dist/plot/ColorBar.svelte +19 -17
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/FillArea.svelte +2 -4
- package/dist/plot/FillArea.svelte.d.ts +1 -1
- package/dist/plot/Histogram.svelte +167 -281
- package/dist/plot/Histogram.svelte.d.ts +1 -1
- package/dist/plot/HistogramControls.svelte.d.ts +1 -1
- package/dist/plot/InteractiveAxisLabel.svelte +5 -3
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/PlotAxis.svelte +169 -0
- package/dist/plot/PlotAxis.svelte.d.ts +24 -0
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/ReferenceLine3D.svelte +53 -51
- package/dist/plot/ReferencePlane.svelte +39 -42
- package/dist/plot/ScatterPlot.svelte +300 -367
- package/dist/plot/ScatterPlot.svelte.d.ts +8 -5
- package/dist/plot/ScatterPlot3D.svelte +33 -6
- package/dist/plot/ScatterPlot3D.svelte.d.ts +3 -2
- package/dist/plot/ScatterPlot3DControls.svelte +9 -9
- package/dist/plot/ScatterPlotControls.svelte +3 -4
- package/dist/plot/ScatterPoint.svelte +18 -27
- package/dist/plot/ScatterPoint.svelte.d.ts +4 -3
- package/dist/plot/Surface3D.svelte +4 -7
- package/dist/plot/ZeroLines.svelte +2 -1
- package/dist/plot/ZeroLines.svelte.d.ts +2 -1
- package/dist/plot/ZoomRect.svelte +2 -2
- package/dist/plot/ZoomRect.svelte.d.ts +3 -3
- package/dist/plot/adaptive-density.d.ts +69 -0
- package/dist/plot/adaptive-density.js +191 -0
- package/dist/plot/auto-place.d.ts +43 -0
- package/dist/plot/auto-place.js +122 -0
- package/dist/plot/axis-utils.js +3 -5
- package/dist/plot/binned-scatter-types.d.ts +59 -0
- package/dist/plot/binned-scatter-types.js +1 -0
- package/dist/plot/data-cleaning.js +1 -1
- package/dist/plot/data-transform.js +1 -1
- package/dist/plot/fill-utils.d.ts +4 -9
- package/dist/plot/fill-utils.js +29 -44
- package/dist/plot/index.d.ts +4 -0
- package/dist/plot/index.js +2 -0
- package/dist/plot/interactions.d.ts +4 -4
- package/dist/plot/interactions.js +4 -3
- package/dist/plot/layout.d.ts +20 -2
- package/dist/plot/layout.js +59 -16
- package/dist/plot/reference-line.d.ts +1 -1
- package/dist/plot/reference-line.js +9 -11
- package/dist/plot/scales.d.ts +1 -1
- package/dist/plot/scales.js +20 -23
- package/dist/plot/types.d.ts +30 -58
- package/dist/plot/types.js +2 -6
- package/dist/plot/utils/label-placement.d.ts +24 -3
- package/dist/plot/utils/label-placement.js +82 -12
- package/dist/plot/utils/series-visibility.d.ts +8 -2
- package/dist/plot/utils/series-visibility.js +23 -5
- package/dist/rdf/RdfPlot.svelte +5 -5
- package/dist/rdf/calc-rdf.js +3 -3
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.js +2 -0
- package/dist/spectral/Bands.svelte +1 -1
- package/dist/spectral/BandsAndDos.svelte +22 -16
- package/dist/spectral/BrillouinBandsDos.svelte +20 -16
- package/dist/spectral/Dos.svelte +1 -1
- package/dist/spectral/helpers.d.ts +4 -2
- package/dist/spectral/helpers.js +44 -35
- package/dist/spectral/index.d.ts +1 -1
- package/dist/spectral/index.js +0 -1
- package/dist/structure/AtomLegend.svelte +23 -6
- package/dist/structure/AtomLegend.svelte.d.ts +1 -0
- package/dist/structure/CanvasTooltip.svelte +9 -9
- package/dist/structure/CanvasTooltip.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +14 -16
- package/dist/structure/Structure.svelte +317 -68
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureControls.svelte +20 -45
- package/dist/structure/StructureExportPane.svelte +2 -1
- package/dist/structure/StructureInfoPane.svelte +10 -8
- package/dist/structure/StructureScene.svelte +527 -177
- package/dist/structure/StructureScene.svelte.d.ts +5 -2
- package/dist/structure/atom-properties.js +4 -4
- package/dist/structure/bond-order-perception.js +115 -98
- package/dist/structure/bonding.d.ts +27 -1
- package/dist/structure/bonding.js +187 -16
- package/dist/structure/export.js +1 -1
- package/dist/structure/index.d.ts +3 -2
- package/dist/structure/index.js +0 -2
- package/dist/structure/parse.js +88 -59
- package/dist/symmetry/WyckoffTable.svelte +7 -0
- package/dist/symmetry/index.js +13 -14
- package/dist/table/HeatmapTable.svelte +45 -66
- package/dist/table/HeatmapTable.svelte.d.ts +1 -1
- package/dist/table/ToggleMenu.svelte +19 -10
- package/dist/theme/themes.mjs +12 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +0 -1
- package/dist/trajectory/Trajectory.svelte +43 -15
- package/dist/trajectory/TrajectoryInfoPane.svelte +2 -2
- package/dist/trajectory/extract.js +1 -1
- package/dist/trajectory/frame-reader.js +4 -4
- package/dist/trajectory/helpers.d.ts +5 -4
- package/dist/trajectory/helpers.js +9 -17
- package/dist/trajectory/index.d.ts +2 -2
- package/dist/trajectory/index.js +2 -2
- package/dist/trajectory/parse/ase.js +4 -4
- package/dist/trajectory/parse/hdf5.js +1 -1
- package/dist/trajectory/parse/index.js +2 -3
- package/dist/trajectory/parse/lammps.js +1 -1
- package/dist/trajectory/parse/vasp.js +1 -1
- package/dist/trajectory/plotting.d.ts +1 -1
- package/dist/trajectory/plotting.js +38 -38
- package/dist/trajectory/types.d.ts +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +9 -0
- package/dist/xrd/calc-xrd.js +3 -4
- package/dist/xrd/parse.js +1 -1
- package/package.json +42 -22
|
@@ -89,11 +89,11 @@
|
|
|
89
89
|
.join(` `),
|
|
90
90
|
)
|
|
91
91
|
|
|
92
|
-
function handle_copy(event: ClipboardEvent) {
|
|
92
|
+
function handle_copy(event: ClipboardEvent & { currentTarget: HTMLElement }) {
|
|
93
93
|
const selection = window.getSelection()
|
|
94
94
|
// Only intercept if user selected text fully within this formula
|
|
95
95
|
if (!selection || selection.isCollapsed) return
|
|
96
|
-
const formula_el = event.currentTarget
|
|
96
|
+
const formula_el = event.currentTarget
|
|
97
97
|
if (
|
|
98
98
|
!formula_el.contains(selection.anchorNode) ||
|
|
99
99
|
!formula_el.contains(selection.focusNode)
|
|
@@ -102,8 +102,11 @@
|
|
|
102
102
|
event.clipboardData?.setData(`text/plain`, plain_text_formula)
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
function show_tooltip(
|
|
106
|
-
|
|
105
|
+
function show_tooltip(
|
|
106
|
+
element: ElementSymbol,
|
|
107
|
+
event: MouseEvent & { currentTarget: HTMLElement },
|
|
108
|
+
) {
|
|
109
|
+
const { left, width, top, bottom, right, height } = event.currentTarget
|
|
107
110
|
.getBoundingClientRect()
|
|
108
111
|
hovered_element = element
|
|
109
112
|
|
|
@@ -243,9 +243,7 @@
|
|
|
243
243
|
close_history()
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
return pinned_history.includes(entry)
|
|
248
|
-
}
|
|
246
|
+
const is_pinned = (entry: string): boolean => pinned_history.includes(entry)
|
|
249
247
|
|
|
250
248
|
// Filtered history: exclude current value to avoid redundant suggestion
|
|
251
249
|
let visible_history = $derived.by(() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { format_num } from '../labels';
|
|
2
|
-
import { ELEMENT_ELECTRONEGATIVITY_MAP, parse_composition } from './parse';
|
|
2
|
+
import { ELEMENT_ELECTRONEGATIVITY_MAP, is_valid_element, parse_composition } from './parse';
|
|
3
3
|
// Extract composition from structure object
|
|
4
4
|
const structure_to_composition = (structure) => {
|
|
5
5
|
if (!structure.sites || !Array.isArray(structure.sites)) {
|
|
@@ -17,9 +17,10 @@ const structure_to_composition = (structure) => {
|
|
|
17
17
|
}
|
|
18
18
|
return composition;
|
|
19
19
|
};
|
|
20
|
+
const is_structure_like = (input) => `sites` in input || `lattice` in input;
|
|
20
21
|
// Format composition into chemical formula string
|
|
21
22
|
export const format_composition_formula = (composition, sort_fn, plain_text = false, delim = ` `, amount_format = `.3~s`) => {
|
|
22
|
-
const symbols = Object.keys(composition);
|
|
23
|
+
const symbols = Object.keys(composition).filter(is_valid_element);
|
|
23
24
|
return sort_fn(symbols)
|
|
24
25
|
.filter((el) => composition[el] && composition[el] > 0)
|
|
25
26
|
.map((el) => {
|
|
@@ -37,9 +38,8 @@ const format_formula_generic = (input, sort_fn, plain_text = false, delim = ` `,
|
|
|
37
38
|
let composition;
|
|
38
39
|
if (typeof input === `string`)
|
|
39
40
|
composition = parse_composition(input);
|
|
40
|
-
else if (
|
|
41
|
+
else if (is_structure_like(input))
|
|
41
42
|
composition = structure_to_composition(input);
|
|
42
|
-
}
|
|
43
43
|
else
|
|
44
44
|
composition = input;
|
|
45
45
|
return format_composition_formula(composition, sort_fn, plain_text, delim, amount_format);
|
|
@@ -34,7 +34,8 @@ export declare function extract_formula_elements(formula: string, { unique, sort
|
|
|
34
34
|
sorted?: boolean;
|
|
35
35
|
}): ElementSymbol[];
|
|
36
36
|
export declare function generate_chem_sys_subspaces(input: string | CompositionType | ElementSymbol[]): string[];
|
|
37
|
-
export declare
|
|
37
|
+
export declare function normalize_element_symbols(csv: string): ElementSymbol[];
|
|
38
|
+
export declare function normalize_element_symbols<T extends string>(csv: string, all_symbols: T[]): T[];
|
|
38
39
|
export type WildcardFormulaToken = {
|
|
39
40
|
element: ElementSymbol | null;
|
|
40
41
|
count: number;
|
|
@@ -21,8 +21,19 @@ export const is_valid_element = (sym) => ELEM_SYMBOLS.includes(sym);
|
|
|
21
21
|
// Check if object has atomic numbers as keys (1-118)
|
|
22
22
|
const is_atomic_number_composition = (obj) => {
|
|
23
23
|
const keys = Object.keys(obj);
|
|
24
|
+
const atomic_nums = keys.map(Number);
|
|
24
25
|
return (keys.length > 0 &&
|
|
25
|
-
|
|
26
|
+
atomic_nums.every((atomic_num) => Number.isInteger(atomic_num) && Object.hasOwn(ATOMIC_NUMBER_TO_SYMBOL, atomic_num)));
|
|
27
|
+
};
|
|
28
|
+
const format_state = (state) => (state > 0 ? `+` : ``) + state;
|
|
29
|
+
const parse_count = (count) => (count ? parseFloat(count) : 1);
|
|
30
|
+
const format_count = (count) => {
|
|
31
|
+
if (!Number.isFinite(count))
|
|
32
|
+
return `${count}`;
|
|
33
|
+
return Number(count.toPrecision(12)).toLocaleString(`en-US`, {
|
|
34
|
+
maximumSignificantDigits: 12,
|
|
35
|
+
useGrouping: false,
|
|
36
|
+
});
|
|
26
37
|
};
|
|
27
38
|
// Convert atomic numbers to element symbols
|
|
28
39
|
export const atomic_num_to_symbols = (atomic_composition) => {
|
|
@@ -32,7 +43,7 @@ export const atomic_num_to_symbols = (atomic_composition) => {
|
|
|
32
43
|
if (!symbol)
|
|
33
44
|
throw new Error(`Invalid atomic number: ${atomic_num_str}`);
|
|
34
45
|
if (amount > 0)
|
|
35
|
-
composition[symbol] = (composition[symbol]
|
|
46
|
+
composition[symbol] = (composition[symbol] ?? 0) + amount;
|
|
36
47
|
}
|
|
37
48
|
return composition;
|
|
38
49
|
};
|
|
@@ -40,6 +51,8 @@ export const atomic_num_to_symbols = (atomic_composition) => {
|
|
|
40
51
|
export const atomic_symbol_to_num = (symbol_composition) => {
|
|
41
52
|
const atomic_composition = {};
|
|
42
53
|
for (const [symbol, amount] of Object.entries(symbol_composition)) {
|
|
54
|
+
if (!is_valid_element(symbol))
|
|
55
|
+
throw new Error(`Invalid element symbol: ${symbol}`);
|
|
43
56
|
const atomic_num = SYMBOL_TO_ATOMIC_NUMBER[symbol];
|
|
44
57
|
if (!atomic_num)
|
|
45
58
|
throw new Error(`Invalid element symbol: ${symbol}`);
|
|
@@ -52,11 +65,11 @@ export const atomic_symbol_to_num = (symbol_composition) => {
|
|
|
52
65
|
// Expand parentheses in chemical formulas
|
|
53
66
|
const expand_parentheses = (formula) => {
|
|
54
67
|
while (formula.includes(`(`)) {
|
|
55
|
-
formula = formula.replace(/\(([^()]+)\)(\d
|
|
56
|
-
const mult =
|
|
57
|
-
return group.replace(/([A-Z][a-z]?)(\d
|
|
58
|
-
const
|
|
59
|
-
return element + (
|
|
68
|
+
formula = formula.replace(/\(([^()]+)\)(\d+(?:\.\d+)?|\.\d+)?/g, (_match, group, multiplier) => {
|
|
69
|
+
const mult = parse_count(multiplier);
|
|
70
|
+
return group.replace(/([A-Z][a-z]?)(\d+(?:\.\d+)?|\.\d+)?/g, (_m, element, count) => {
|
|
71
|
+
const count_str = format_count(parse_count(count) * mult);
|
|
72
|
+
return element + (count_str === `1` ? `` : count_str);
|
|
60
73
|
});
|
|
61
74
|
});
|
|
62
75
|
}
|
|
@@ -66,12 +79,12 @@ const expand_parentheses = (formula) => {
|
|
|
66
79
|
export const parse_formula = (formula) => {
|
|
67
80
|
const composition = {};
|
|
68
81
|
const cleaned_formula = expand_parentheses(formula.replace(/\s/g, ``));
|
|
69
|
-
for (const match of cleaned_formula.matchAll(/([A-Z][a-z]?)(\d
|
|
82
|
+
for (const match of cleaned_formula.matchAll(/([A-Z][a-z]?)(\d+(?:\.\d+)?|\.\d+)?/g)) {
|
|
70
83
|
const element = match[1];
|
|
71
|
-
const count =
|
|
84
|
+
const count = parse_count(match[2]);
|
|
72
85
|
if (!is_valid_element(element))
|
|
73
86
|
throw new Error(`Invalid element symbol: ${element}`);
|
|
74
|
-
composition[element] = (composition[element]
|
|
87
|
+
composition[element] = (composition[element] ?? 0) + count;
|
|
75
88
|
}
|
|
76
89
|
return composition;
|
|
77
90
|
};
|
|
@@ -82,7 +95,7 @@ export const normalize_composition = (composition) => {
|
|
|
82
95
|
}
|
|
83
96
|
const normalized = {};
|
|
84
97
|
for (const [element, amount] of Object.entries(composition)) {
|
|
85
|
-
if (typeof amount === `number` && amount > 0) {
|
|
98
|
+
if (typeof amount === `number` && amount > 0 && is_valid_element(element)) {
|
|
86
99
|
normalized[element] = amount;
|
|
87
100
|
}
|
|
88
101
|
}
|
|
@@ -100,7 +113,7 @@ export const sanitize_composition_keys = (composition) => {
|
|
|
100
113
|
if (typeof amount !== `number` || amount <= 0)
|
|
101
114
|
continue;
|
|
102
115
|
// Extract first valid element symbol from key (e.g. "B0." -> "B", "Fe2+" -> "Fe")
|
|
103
|
-
const elem = (key.match(/[A-Z][a-z]?/g)
|
|
116
|
+
const elem = (key.match(/[A-Z][a-z]?/g) ?? []).find(is_valid_element);
|
|
104
117
|
if (elem)
|
|
105
118
|
sanitized[elem] = (sanitized[elem] || 0) + amount;
|
|
106
119
|
}
|
|
@@ -114,6 +127,8 @@ export const fractional_composition = (composition, by_weight = false) => {
|
|
|
114
127
|
const filtered = Object.fromEntries(Object.entries(composition).filter(([, amount]) => amount > 0));
|
|
115
128
|
if (by_weight) {
|
|
116
129
|
const element_weights = Object.fromEntries(Object.entries(filtered).map(([element, amount]) => {
|
|
130
|
+
if (!is_valid_element(element))
|
|
131
|
+
throw new Error(`Unknown element: ${element}`);
|
|
117
132
|
const atomic_mass = ATOMIC_WEIGHTS.get(element);
|
|
118
133
|
if (!atomic_mass)
|
|
119
134
|
throw new Error(`Unknown element: ${element}`);
|
|
@@ -162,11 +177,16 @@ export const get_reduced_formula = (composition) => {
|
|
|
162
177
|
const divisor = amounts.reduce((acc, amt) => gcd(acc, amt));
|
|
163
178
|
if (divisor <= 1)
|
|
164
179
|
return composition;
|
|
165
|
-
|
|
180
|
+
const reduced = {};
|
|
181
|
+
for (const [elem, amt] of Object.entries(composition)) {
|
|
182
|
+
if (is_valid_element(elem))
|
|
183
|
+
reduced[elem] = amt / divisor;
|
|
184
|
+
}
|
|
185
|
+
return reduced;
|
|
166
186
|
};
|
|
167
187
|
// Calculate molecular weight (sum of atomic masses * amounts)
|
|
168
188
|
export const get_molecular_weight = (composition) => Object.entries(composition).reduce((total, [elem, amount]) => {
|
|
169
|
-
const mass = ATOMIC_WEIGHTS.get(elem) ?? 0;
|
|
189
|
+
const mass = is_valid_element(elem) ? (ATOMIC_WEIGHTS.get(elem) ?? 0) : 0;
|
|
170
190
|
return total + mass * amount;
|
|
171
191
|
}, 0);
|
|
172
192
|
// Parse oxidation state string (e.g. "+2", "2+", "-", "[2-]") to number
|
|
@@ -194,12 +214,12 @@ export const parse_formula_with_oxidation = (formula, strict = false) => {
|
|
|
194
214
|
// Regex to match: Element, optional oxidation state and/or count in either order
|
|
195
215
|
// Pattern: ([A-Z][a-z]?) - element symbol
|
|
196
216
|
// Followed by one of:
|
|
197
|
-
// - oxidation then optional count: (?:\^([+-]?\d+[+-]?|[+-])|\[([+-]?\d+[+-]?|[+-])\])(
|
|
198
|
-
// - count then optional oxidation: (
|
|
217
|
+
// - oxidation then optional count: (?:\^([+-]?\d+[+-]?|[+-])|\[([+-]?\d+[+-]?|[+-])\])(count?)
|
|
218
|
+
// - count then optional oxidation: count(?:\^([+-]?\d+[+-]?|[+-])|\[([+-]?\d+[+-]?|[+-])\])?
|
|
199
219
|
// - just oxidation: (?:\^([+-]?\d+[+-]?|[+-])|\[([+-]?\d+[+-]?|[+-])\])
|
|
200
|
-
// - just count:
|
|
220
|
+
// - just count: count
|
|
201
221
|
// - neither
|
|
202
|
-
const regex = /([A-Z][a-z]?)(?:(?:\^([+-]?\d+[+-]?|[+-])|\[([+-]?\d+[+-]?|[+-])\])(
|
|
222
|
+
const regex = /([A-Z][a-z]?)(?:(?:\^([+-]?\d+[+-]?|[+-])|\[([+-]?\d+[+-]?|[+-])\])((?:\d+(?:\.\d+)?|\.\d+)?)|((?:\d+(?:\.\d+)?|\.\d+))(?:\^([+-]?\d+[+-]?|[+-])|\[([+-]?\d+[+-]?|[+-])\])?)?/g;
|
|
203
223
|
let match;
|
|
204
224
|
let orig_idx = 0;
|
|
205
225
|
while ((match = regex.exec(cleaned_formula)) !== null) {
|
|
@@ -208,7 +228,7 @@ export const parse_formula_with_oxidation = (formula, strict = false) => {
|
|
|
208
228
|
// Count can be in group 4 (after oxidation) or 5 (before oxidation)
|
|
209
229
|
const oxidation_str = match[2] || match[3] || match[6] || match[7];
|
|
210
230
|
const count_str = match[4] || match[5];
|
|
211
|
-
const count =
|
|
231
|
+
const count = parse_count(count_str);
|
|
212
232
|
if (!is_valid_element(element))
|
|
213
233
|
throw new Error(`Invalid element symbol: ${element}`);
|
|
214
234
|
const oxidation_state = oxidation_str ? parse_oxidation_state(oxidation_str) : undefined;
|
|
@@ -226,7 +246,6 @@ export const parse_formula_with_oxidation = (formula, strict = false) => {
|
|
|
226
246
|
}
|
|
227
247
|
else if (strict && existing.oxidation_state !== oxidation_state) {
|
|
228
248
|
// In strict mode, throw on conflicting oxidation states
|
|
229
|
-
const format_state = (state) => (state > 0 ? `+` : ``) + state;
|
|
230
249
|
throw new Error(`Conflicting oxidation states for ${element}: ${format_state(existing.oxidation_state)} and ${format_state(oxidation_state)}`);
|
|
231
250
|
}
|
|
232
251
|
}
|
|
@@ -238,12 +257,7 @@ export const parse_formula_with_oxidation = (formula, strict = false) => {
|
|
|
238
257
|
};
|
|
239
258
|
// Convert OxiComposition to ElementWithOxidation array
|
|
240
259
|
// Does not preserve original order since objects don't have a defined order
|
|
241
|
-
export const oxi_composition_to_elements = (composition) => Object.entries(composition).
|
|
242
|
-
element: element,
|
|
243
|
-
amount: data.amount,
|
|
244
|
-
oxidation_state: data.oxidation_state,
|
|
245
|
-
orig_idx: idx,
|
|
246
|
-
}));
|
|
260
|
+
export const oxi_composition_to_elements = (composition) => Object.entries(composition).flatMap(([element, { amount, oxidation_state }], idx) => is_valid_element(element) ? [{ element, amount, oxidation_state, orig_idx: idx }] : []);
|
|
247
261
|
// Extract element symbols from a chemical formula.
|
|
248
262
|
// Default (unique=true, sorted=true): "NbZr2Nb" -> ["Nb", "Zr"]
|
|
249
263
|
// unique=false: Fast token extraction preserving order without parentheses expansion
|
|
@@ -254,10 +268,15 @@ export const oxi_composition_to_elements = (composition) => Object.entries(compo
|
|
|
254
268
|
export function extract_formula_elements(formula, { unique = true, sorted = true } = {}) {
|
|
255
269
|
if (!unique) {
|
|
256
270
|
// Fast path: regex token extraction without parentheses expansion
|
|
257
|
-
const matches = formula.match(/[A-Z][a-z]?/g)
|
|
258
|
-
|
|
271
|
+
const matches = formula.match(/[A-Z][a-z]?/g) ?? [];
|
|
272
|
+
const elements = [];
|
|
273
|
+
for (const match of matches) {
|
|
274
|
+
if (is_valid_element(match))
|
|
275
|
+
elements.push(match);
|
|
276
|
+
}
|
|
277
|
+
return elements;
|
|
259
278
|
}
|
|
260
|
-
const symbols = Object.keys(parse_formula(formula));
|
|
279
|
+
const symbols = Object.keys(parse_formula(formula)).filter(is_valid_element);
|
|
261
280
|
return sorted ? symbols.sort() : symbols;
|
|
262
281
|
}
|
|
263
282
|
// Generate all non-empty subsets of a chemical system as hyphenated strings.
|
|
@@ -277,12 +296,12 @@ export function generate_chem_sys_subspaces(input) {
|
|
|
277
296
|
elements = uniq;
|
|
278
297
|
}
|
|
279
298
|
else {
|
|
280
|
-
|
|
281
|
-
for (const elem of keys) {
|
|
299
|
+
elements = [];
|
|
300
|
+
for (const elem of Object.keys(input)) {
|
|
282
301
|
if (!is_valid_element(elem))
|
|
283
302
|
throw new Error(`Invalid element symbol: ${elem}`);
|
|
303
|
+
elements.push(elem);
|
|
284
304
|
}
|
|
285
|
-
elements = keys;
|
|
286
305
|
}
|
|
287
306
|
const sorted = [...elements].sort();
|
|
288
307
|
const subspaces = [];
|
|
@@ -297,18 +316,14 @@ export function generate_chem_sys_subspaces(input) {
|
|
|
297
316
|
}
|
|
298
317
|
return subspaces;
|
|
299
318
|
}
|
|
300
|
-
|
|
301
|
-
// Filters invalid symbols, removes duplicates, trims whitespace.
|
|
302
|
-
// Example: "Zr, Nb, InvalidElement, H" -> ["H", "Nb", "Zr"]
|
|
303
|
-
// Note: Matching is case-sensitive. Use all_symbols to filter against a subset.
|
|
304
|
-
export const normalize_element_symbols = (csv, all_symbols) => {
|
|
319
|
+
export function normalize_element_symbols(csv, all_symbols) {
|
|
305
320
|
const input_set = new Set(csv
|
|
306
321
|
.split(`,`)
|
|
307
322
|
.map((sym) => sym.trim())
|
|
308
323
|
.filter(Boolean));
|
|
309
|
-
|
|
310
|
-
return
|
|
311
|
-
}
|
|
324
|
+
const symbols = all_symbols ?? ELEM_SYMBOLS;
|
|
325
|
+
return symbols.filter((sym) => input_set.has(sym));
|
|
326
|
+
}
|
|
312
327
|
// Check if input contains wildcard elements (*).
|
|
313
328
|
// Works for both chemsys format (Li-Fe-*-*) and exact formula format (LiFe*2*).
|
|
314
329
|
export const has_wildcards = (input) => input.includes(`*`);
|
|
@@ -360,20 +375,20 @@ export function parse_formula_with_wildcards(formula) {
|
|
|
360
375
|
// Restore wildcards
|
|
361
376
|
cleaned = cleaned.replace(ELEM_WILDCARD.from_placeholder, `*`);
|
|
362
377
|
// Regex to match either:
|
|
363
|
-
// 1. Standard element symbol with optional count
|
|
364
|
-
// 2. Wildcard with optional count
|
|
365
|
-
const regex = /([A-Z][a-z]?)(
|
|
378
|
+
// 1. Standard element symbol with optional decimal count
|
|
379
|
+
// 2. Wildcard with optional decimal count
|
|
380
|
+
const regex = /([A-Z][a-z]?)((?:\d+(?:\.\d+)?|\.\d+)?)|(\*)((?:\d+(?:\.\d+)?|\.\d+)?)/g;
|
|
366
381
|
let match;
|
|
367
382
|
while ((match = regex.exec(cleaned)) !== null) {
|
|
368
383
|
if (match[3] === `*`) {
|
|
369
384
|
// Wildcard match
|
|
370
|
-
const count =
|
|
385
|
+
const count = parse_count(match[4]);
|
|
371
386
|
tokens.push({ element: null, count });
|
|
372
387
|
}
|
|
373
388
|
else if (match[1]) {
|
|
374
389
|
// Element symbol match
|
|
375
390
|
const element = match[1];
|
|
376
|
-
const count =
|
|
391
|
+
const count = parse_count(match[2]);
|
|
377
392
|
if (!is_valid_element(element)) {
|
|
378
393
|
throw new Error(`Invalid element symbol: ${element}`);
|
|
379
394
|
}
|
|
@@ -395,7 +410,7 @@ export function matches_chemsys_wildcard(formula, explicit_elements, wildcard_co
|
|
|
395
410
|
// Must contain all explicit elements
|
|
396
411
|
const formula_set = new Set(formula_elements);
|
|
397
412
|
for (const elem of explicit_elements) {
|
|
398
|
-
if (!formula_set.has(elem))
|
|
413
|
+
if (!is_valid_element(elem) || !formula_set.has(elem))
|
|
399
414
|
return false;
|
|
400
415
|
}
|
|
401
416
|
return true;
|
|
@@ -206,39 +206,26 @@
|
|
|
206
206
|
// Require formation energy per atom to place along y
|
|
207
207
|
const e_form = entry.e_form_per_atom
|
|
208
208
|
if (typeof e_form !== `number`) continue
|
|
209
|
-
const total = Object.values(entry.composition).reduce((
|
|
209
|
+
const total = Object.values(entry.composition).reduce((sum, amount) => sum + amount, 0)
|
|
210
210
|
if (total <= 0) continue
|
|
211
211
|
const frac_b = (entry.composition[el2] || 0) / total
|
|
212
212
|
const is_element = is_unary_entry(entry)
|
|
213
|
-
coords.push({ ...entry, x: frac_b, y: e_form, z: 0, is_element
|
|
213
|
+
coords.push({ ...entry, x: frac_b, y: e_form, z: 0, is_element })
|
|
214
214
|
}
|
|
215
215
|
// Ensure elemental references at x=0 and x=1 with y=0 to close the hull
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
)
|
|
222
|
-
if (!el_a) {
|
|
216
|
+
for (const [element, x_coord] of [
|
|
217
|
+
[el1, 0],
|
|
218
|
+
[el2, 1],
|
|
219
|
+
] as const) {
|
|
220
|
+
if (coords.some((entry) => entry.is_element && entry.x === x_coord)) continue
|
|
223
221
|
coords.push({
|
|
224
|
-
composition: { [
|
|
222
|
+
composition: { [element]: 1 } as CompositionType,
|
|
225
223
|
energy: 0,
|
|
226
|
-
|
|
224
|
+
entry_id: `synthetic-element:${element}`,
|
|
225
|
+
x: x_coord,
|
|
227
226
|
y: 0,
|
|
228
227
|
z: 0,
|
|
229
228
|
is_element: true,
|
|
230
|
-
visible: true,
|
|
231
|
-
})
|
|
232
|
-
}
|
|
233
|
-
if (!el_b) {
|
|
234
|
-
coords.push({
|
|
235
|
-
composition: { [el2]: 1 } as CompositionType,
|
|
236
|
-
energy: 0,
|
|
237
|
-
x: 1,
|
|
238
|
-
y: 0,
|
|
239
|
-
z: 0,
|
|
240
|
-
is_element: true,
|
|
241
|
-
visible: true,
|
|
242
229
|
})
|
|
243
230
|
}
|
|
244
231
|
return coords
|
|
@@ -280,9 +267,7 @@
|
|
|
280
267
|
const enriched_entries = coords_entries.map((entry) => {
|
|
281
268
|
const y_hull = thermo.interpolate_hull_2d(computed_hull_points, entry.x)
|
|
282
269
|
const raw_dist = y_hull == null ? 0 : entry.y - y_hull
|
|
283
|
-
return {
|
|
284
|
-
...entry, ...compute_hull_stability(raw_dist, entry.exclude_from_hull), visible: true,
|
|
285
|
-
}
|
|
270
|
+
return { ...entry, ...compute_hull_stability(raw_dist, entry.exclude_from_hull) }
|
|
286
271
|
})
|
|
287
272
|
return { all_enriched_entries: enriched_entries, hull_points: computed_hull_points }
|
|
288
273
|
})
|
|
@@ -297,25 +282,23 @@
|
|
|
297
282
|
DEFAULTS.convex_hull.binary.max_hull_dist_show_phases,
|
|
298
283
|
))
|
|
299
284
|
|
|
300
|
-
|
|
301
|
-
|
|
285
|
+
const next_auto_threshold = helpers.auto_threshold_reset(
|
|
286
|
+
DEFAULTS.convex_hull.binary.max_hull_dist_show_phases,
|
|
287
|
+
)
|
|
302
288
|
$effect(() => {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
max_hull_dist_show_phases
|
|
306
|
-
|
|
289
|
+
max_hull_dist_show_phases = next_auto_threshold(
|
|
290
|
+
entries,
|
|
291
|
+
max_hull_dist_show_phases,
|
|
292
|
+
auto_default_threshold,
|
|
293
|
+
) ?? max_hull_dist_show_phases
|
|
307
294
|
})
|
|
308
295
|
|
|
309
|
-
// Filter by threshold
|
|
296
|
+
// Filter by threshold; visibility is a view predicate, not entry state.
|
|
310
297
|
const plot_entries = $derived(
|
|
311
|
-
all_enriched_entries
|
|
312
|
-
.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
.map((e) => ({
|
|
316
|
-
...e,
|
|
317
|
-
visible: (e.is_stable && show_stable) || (!e.is_stable && show_unstable),
|
|
318
|
-
})),
|
|
298
|
+
all_enriched_entries.filter((entry) =>
|
|
299
|
+
helpers.entry_is_stable(entry) ||
|
|
300
|
+
(entry.e_above_hull ?? 0) <= max_hull_dist_show_phases
|
|
301
|
+
),
|
|
319
302
|
)
|
|
320
303
|
|
|
321
304
|
// Update bindable entries arrays when plot_entries change (single pass)
|
|
@@ -323,7 +306,7 @@
|
|
|
323
306
|
const stable: ConvexHullEntry[] = []
|
|
324
307
|
const unstable: ConvexHullEntry[] = []
|
|
325
308
|
for (const entry of plot_entries) {
|
|
326
|
-
if (entry
|
|
309
|
+
if (helpers.entry_is_stable(entry)) stable.push(entry)
|
|
327
310
|
else unstable.push(entry)
|
|
328
311
|
}
|
|
329
312
|
stable_entries = stable
|
|
@@ -371,7 +354,11 @@
|
|
|
371
354
|
}
|
|
372
355
|
|
|
373
356
|
// Pre-compute visible entries to avoid redundant filtering
|
|
374
|
-
const visible_entries = $derived(
|
|
357
|
+
const visible_entries = $derived(helpers.visible_entries(
|
|
358
|
+
plot_entries,
|
|
359
|
+
show_stable,
|
|
360
|
+
show_unstable,
|
|
361
|
+
))
|
|
375
362
|
|
|
376
363
|
const scatter_points_series = $derived.by(() => {
|
|
377
364
|
const is_energy_mode = color_mode === `energy`
|
|
@@ -389,7 +376,7 @@
|
|
|
389
376
|
y_vals[idx] = entry.y
|
|
390
377
|
if (is_energy_mode) color_values[idx] = entry.e_above_hull ?? 0
|
|
391
378
|
|
|
392
|
-
const is_stable =
|
|
379
|
+
const is_stable = helpers.entry_is_stable(entry)
|
|
393
380
|
const base_radius = entry.size || (is_stable ? 6 : 4)
|
|
394
381
|
const hl = is_highlighted(entry) ? merged_highlight_style : null
|
|
395
382
|
|
|
@@ -444,10 +431,8 @@
|
|
|
444
431
|
|
|
445
432
|
const scatter_series = $derived([scatter_points_series, ...hull_segments_series])
|
|
446
433
|
|
|
447
|
-
// Map selected_entry to ScatterPlot point index (series_idx: 0 = points series)
|
|
448
|
-
//
|
|
449
|
-
// because synthetic elemental entries lack entry_id, and undefined === undefined
|
|
450
|
-
// would incorrectly match the first entry with undefined entry_id
|
|
434
|
+
// Map selected_entry to ScatterPlot point index (series_idx: 0 = points series).
|
|
435
|
+
// current_entry() keeps selections pointing at the current plot entry object.
|
|
451
436
|
const selected_scatter_point = $derived.by(() => {
|
|
452
437
|
const entry = selected_entry
|
|
453
438
|
if (!entry) return null
|
|
@@ -505,9 +490,33 @@
|
|
|
505
490
|
|
|
506
491
|
// Custom hover tooltip state used with ScatterPlot events
|
|
507
492
|
let hover_data = $state<HoverData3D<ConvexHullEntry> | null>(null)
|
|
493
|
+
$effect(() => {
|
|
494
|
+
const current_selection = helpers.current_entry(selected_entry, plot_entries)
|
|
495
|
+
if (selected_entry && !current_selection) selected_entry = null
|
|
496
|
+
else if (current_selection && current_selection !== selected_entry) {
|
|
497
|
+
selected_entry = current_selection
|
|
498
|
+
}
|
|
499
|
+
const current_hover = helpers.current_entry(hover_data?.entry, plot_entries)
|
|
500
|
+
if (hover_data?.entry && !current_hover) {
|
|
501
|
+
hover_data = null
|
|
502
|
+
on_point_hover?.(null)
|
|
503
|
+
} else if (hover_data && current_hover && current_hover !== hover_data.entry) {
|
|
504
|
+
hover_data = { ...hover_data, entry: current_hover }
|
|
505
|
+
}
|
|
506
|
+
const current_popup = helpers.current_entry(structure_popup.entry, plot_entries)
|
|
507
|
+
if (structure_popup.open) {
|
|
508
|
+
const structure = current_popup && extract_structure_from_entry(current_popup)
|
|
509
|
+
if (!structure) structure_popup = { open: false, structure: null, entry: null, place_right: true }
|
|
510
|
+
else if (
|
|
511
|
+
current_popup !== structure_popup.entry ||
|
|
512
|
+
structure !== structure_popup.structure
|
|
513
|
+
) structure_popup = { ...structure_popup, entry: current_popup, structure }
|
|
514
|
+
}
|
|
515
|
+
})
|
|
508
516
|
|
|
509
517
|
const handle_keydown = (event: KeyboardEvent) => {
|
|
510
|
-
|
|
518
|
+
const target = event.target
|
|
519
|
+
if (target instanceof HTMLElement && target.tagName.match(/INPUT|TEXTAREA/)) return
|
|
511
520
|
const actions: Record<string, () => void> = {
|
|
512
521
|
b: () => color_mode = color_mode === `stability` ? `energy` : `stability`,
|
|
513
522
|
s: () => show_stable = !show_stable,
|
|
@@ -637,6 +646,8 @@
|
|
|
637
646
|
{phase_stats}
|
|
638
647
|
{stable_entries}
|
|
639
648
|
{unstable_entries}
|
|
649
|
+
{show_stable}
|
|
650
|
+
{show_unstable}
|
|
640
651
|
{max_hull_dist_show_phases}
|
|
641
652
|
{max_hull_dist_show_labels}
|
|
642
653
|
{label_threshold}
|
|
@@ -695,11 +706,11 @@
|
|
|
695
706
|
tabindex={-1}
|
|
696
707
|
onkeydown={handle_keydown}
|
|
697
708
|
ondrop={handle_file_drop}
|
|
698
|
-
ondragover={(event) => {
|
|
709
|
+
ondragover={(event: DragEvent) => {
|
|
699
710
|
event.preventDefault()
|
|
700
711
|
drag_over = true
|
|
701
712
|
}}
|
|
702
|
-
ondragleave={(event) => {
|
|
713
|
+
ondragleave={(event: DragEvent) => {
|
|
703
714
|
event.preventDefault()
|
|
704
715
|
drag_over = false
|
|
705
716
|
}}
|