matterviz 0.1.4 → 0.1.6
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/DraggablePanel.svelte +52 -17
- package/dist/app.css +3 -3
- package/dist/colors/index.js +3 -12
- package/dist/composition/BarChart.svelte +1 -1
- package/dist/composition/BubbleChart.svelte +1 -1
- package/dist/composition/PieChart.svelte +1 -1
- package/dist/composition/parse.d.ts +9 -9
- package/dist/composition/parse.js +119 -214
- package/dist/element/ElementTile.svelte.d.ts +1 -1
- package/dist/element/Nucleus.svelte +0 -9
- package/dist/icons.d.ts +3 -3
- package/dist/icons.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/io/decompress.d.ts +6 -2
- package/dist/io/decompress.js +27 -27
- package/dist/io/export.d.ts +4 -0
- package/dist/io/export.js +210 -12
- package/dist/io/fetch.d.ts +4 -0
- package/dist/io/fetch.js +34 -0
- package/dist/io/index.js +51 -33
- package/dist/io/parse.d.ts +24 -1
- package/dist/io/parse.js +321 -206
- package/dist/math.d.ts +3 -1
- package/dist/math.js +18 -1
- package/dist/mp-api.d.ts +6 -6
- package/dist/mp-api.js +18 -36
- package/dist/plot/Histogram.svelte +231 -146
- package/dist/plot/Histogram.svelte.d.ts +13 -1
- package/dist/plot/HistogramControls.svelte +214 -225
- package/dist/plot/HistogramControls.svelte.d.ts +6 -1
- package/dist/plot/ScatterPlot.svelte.d.ts +1 -1
- package/dist/plot/ScatterPlotControls.svelte +239 -363
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/settings.d.ts +113 -0
- package/dist/settings.js +489 -0
- package/dist/structure/Bond.svelte +3 -7
- package/dist/structure/Bond.svelte.d.ts +3 -2
- package/dist/structure/Lattice.svelte +2 -2
- package/dist/structure/Structure.svelte +112 -40
- package/dist/structure/Structure.svelte.d.ts +15 -2
- package/dist/structure/StructureControls.svelte +352 -285
- package/dist/structure/StructureControls.svelte.d.ts +1 -8
- package/dist/structure/StructureInfoPanel.svelte +2 -75
- package/dist/structure/StructureScene.svelte +220 -152
- package/dist/structure/StructureScene.svelte.d.ts +8 -2
- package/dist/structure/Vector.svelte +2 -2
- package/dist/structure/index.d.ts +17 -24
- package/dist/structure/index.js +0 -22
- package/dist/theme/index.d.ts +5 -9
- package/dist/theme/index.js +6 -2
- package/dist/trajectory/Trajectory.svelte +402 -199
- package/dist/trajectory/Trajectory.svelte.d.ts +22 -6
- package/dist/trajectory/TrajectoryInfoPanel.svelte +150 -153
- package/dist/trajectory/TrajectoryInfoPanel.svelte.d.ts +2 -2
- package/dist/trajectory/index.d.ts +51 -8
- package/dist/trajectory/index.js +103 -32
- package/dist/trajectory/parse.d.ts +35 -7
- package/dist/trajectory/parse.js +757 -516
- package/dist/trajectory/plotting.d.ts +15 -10
- package/dist/trajectory/plotting.js +316 -280
- package/package.json +7 -7
- package/readme.md +5 -5
|
@@ -87,6 +87,25 @@ function handle_button_click(event, action) {
|
|
|
87
87
|
event.stopPropagation();
|
|
88
88
|
action();
|
|
89
89
|
}
|
|
90
|
+
// Debounced resize handler for better performance
|
|
91
|
+
let resize_timeout = $state(undefined);
|
|
92
|
+
function handle_resize() {
|
|
93
|
+
if (!show || has_been_dragged || currently_dragging)
|
|
94
|
+
return;
|
|
95
|
+
if (resize_timeout)
|
|
96
|
+
clearTimeout(resize_timeout);
|
|
97
|
+
const current_timeout = setTimeout(() => {
|
|
98
|
+
if (resize_timeout !== current_timeout)
|
|
99
|
+
return;
|
|
100
|
+
if (show && toggle_panel_btn && !has_been_dragged && panel_div) {
|
|
101
|
+
const pos = calculate_position();
|
|
102
|
+
initial_position = pos;
|
|
103
|
+
panel_div.style.left = pos.left;
|
|
104
|
+
panel_div.style.top = pos.top;
|
|
105
|
+
}
|
|
106
|
+
}, 50); // Debounce resize events
|
|
107
|
+
resize_timeout = current_timeout;
|
|
108
|
+
}
|
|
90
109
|
// Position panel when shown
|
|
91
110
|
$effect(() => {
|
|
92
111
|
if (show && toggle_panel_btn && !has_been_dragged) {
|
|
@@ -104,7 +123,7 @@ $effect(() => {
|
|
|
104
123
|
});
|
|
105
124
|
</script>
|
|
106
125
|
|
|
107
|
-
<svelte:window onkeydown={on_keydown} />
|
|
126
|
+
<svelte:window onkeydown={on_keydown} onresize={handle_resize} />
|
|
108
127
|
<svelte:document onclick={handle_click_outside} />
|
|
109
128
|
|
|
110
129
|
{#if show_panel}
|
|
@@ -174,10 +193,11 @@ $effect(() => {
|
|
|
174
193
|
box-sizing: border-box;
|
|
175
194
|
display: flex;
|
|
176
195
|
place-items: center;
|
|
177
|
-
padding:
|
|
196
|
+
padding: var(--panel-toggle-padding, 2pt);
|
|
178
197
|
border-radius: var(--panel-toggle-border-radius, 3pt);
|
|
179
198
|
background-color: transparent;
|
|
180
199
|
transition: background-color 0.2s;
|
|
200
|
+
font-size: clamp(1em, 2cqw, 1.6em);
|
|
181
201
|
}
|
|
182
202
|
button.panel-toggle:hover {
|
|
183
203
|
background-color: color-mix(in srgb, currentColor 8%, transparent);
|
|
@@ -197,7 +217,8 @@ $effect(() => {
|
|
|
197
217
|
transition: opacity 0.3s, background-color 0.3s, border-color 0.3s, box-shadow 0.3s;
|
|
198
218
|
width: 28em;
|
|
199
219
|
max-width: 90cqw;
|
|
200
|
-
overflow:
|
|
220
|
+
overflow-x: hidden;
|
|
221
|
+
overflow-y: auto;
|
|
201
222
|
max-height: calc(100vh - 3em);
|
|
202
223
|
pointer-events: auto;
|
|
203
224
|
}
|
|
@@ -208,24 +229,41 @@ $effect(() => {
|
|
|
208
229
|
left: auto !important;
|
|
209
230
|
}
|
|
210
231
|
/* Panel content styling */
|
|
232
|
+
.draggable-panel :global(h4) {
|
|
233
|
+
margin: 2pt 0;
|
|
234
|
+
font-size: 0.95em;
|
|
235
|
+
}
|
|
211
236
|
.draggable-panel :global(hr) {
|
|
212
237
|
border: none;
|
|
213
238
|
background: var(--panel-hr-bg, rgba(255, 255, 255, 0.1));
|
|
214
|
-
margin: 0;
|
|
215
|
-
height:
|
|
239
|
+
margin: 4pt 0;
|
|
240
|
+
height: 1px;
|
|
216
241
|
}
|
|
217
242
|
.draggable-panel :global(label) {
|
|
218
243
|
display: flex;
|
|
219
244
|
align-items: center;
|
|
220
245
|
gap: 2pt;
|
|
221
246
|
}
|
|
247
|
+
.draggable-panel :global(input[type='text']) {
|
|
248
|
+
flex: 1;
|
|
249
|
+
padding: 4px 6px;
|
|
250
|
+
}
|
|
251
|
+
.draggable-panel :global(input[type='text'].invalid) {
|
|
252
|
+
border-color: var(--error-color, #ff6b6b);
|
|
253
|
+
background: rgba(255, 107, 107, 0.1);
|
|
254
|
+
}
|
|
255
|
+
.draggable-panel :global(input[type='text'].invalid):focus {
|
|
256
|
+
outline-color: var(--error-color, #ff6b6b);
|
|
257
|
+
box-shadow: 0 0 0 2px rgba(255, 107, 107, 0.2);
|
|
258
|
+
}
|
|
222
259
|
.draggable-panel :global(input[type='range']) {
|
|
223
|
-
margin-left:
|
|
260
|
+
margin-left: 4pt;
|
|
224
261
|
width: 100px;
|
|
225
262
|
flex-shrink: 0;
|
|
226
263
|
}
|
|
227
|
-
.draggable-panel :global(
|
|
228
|
-
|
|
264
|
+
.draggable-panel :global(input[type='range']) {
|
|
265
|
+
flex: 1;
|
|
266
|
+
min-width: 60px;
|
|
229
267
|
}
|
|
230
268
|
.draggable-panel :global(input[type='number']) {
|
|
231
269
|
box-sizing: border-box;
|
|
@@ -247,6 +285,10 @@ $effect(() => {
|
|
|
247
285
|
}
|
|
248
286
|
.draggable-panel :global(select) {
|
|
249
287
|
margin: 0 0 0 5pt;
|
|
288
|
+
flex: 1;
|
|
289
|
+
border-radius: 3px;
|
|
290
|
+
padding: 2px 4px;
|
|
291
|
+
font-size: 0.8em;
|
|
250
292
|
}
|
|
251
293
|
.draggable-panel :global(input[type='color']) {
|
|
252
294
|
width: 40px;
|
|
@@ -257,15 +299,8 @@ $effect(() => {
|
|
|
257
299
|
}
|
|
258
300
|
.draggable-panel :global(.panel-row) {
|
|
259
301
|
display: flex;
|
|
260
|
-
gap:
|
|
261
|
-
align-items:
|
|
262
|
-
}
|
|
263
|
-
.draggable-panel :global(.panel-row label) {
|
|
264
|
-
min-width: 0;
|
|
265
|
-
}
|
|
266
|
-
.draggable-panel :global(.panel-row label.compact) {
|
|
267
|
-
flex: 0 0 auto;
|
|
268
|
-
margin-right: 8pt;
|
|
302
|
+
gap: 8pt;
|
|
303
|
+
align-items: center;
|
|
269
304
|
}
|
|
270
305
|
.draggable-panel :global(.panel-row label.slider-control) {
|
|
271
306
|
flex: 1;
|
package/dist/app.css
CHANGED
|
@@ -73,9 +73,9 @@ pre code {
|
|
|
73
73
|
pre {
|
|
74
74
|
position: relative;
|
|
75
75
|
border-radius: 4pt;
|
|
76
|
-
font-size:
|
|
76
|
+
font-size: 0.95em;
|
|
77
77
|
background-color: var(--pre-bg);
|
|
78
|
-
padding: 1em;
|
|
78
|
+
padding: 1ex 1em;
|
|
79
79
|
overflow-x: auto;
|
|
80
80
|
}
|
|
81
81
|
blockquote {
|
|
@@ -118,7 +118,7 @@ input {
|
|
|
118
118
|
border-radius: 3pt;
|
|
119
119
|
}
|
|
120
120
|
input[type='number'] {
|
|
121
|
-
min-width:
|
|
121
|
+
min-width: 40px;
|
|
122
122
|
width: auto;
|
|
123
123
|
}
|
|
124
124
|
input[type='color'] {
|
package/dist/colors/index.js
CHANGED
|
@@ -21,18 +21,9 @@ export const default_category_colors = {
|
|
|
21
21
|
lanthanide: `#58748e`,
|
|
22
22
|
actinide: `#6495ed`, // cornflowerblue
|
|
23
23
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (Object.hasOwn(obj, key)) {
|
|
28
|
-
const val = obj[key];
|
|
29
|
-
if (val.length >= 3) {
|
|
30
|
-
result[key] = rgb(val[0], val[1], val[2]).formatHex();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
24
|
+
const rgb_scheme_to_hex = (obj) => Object.fromEntries(Object.entries(obj)
|
|
25
|
+
.filter(([, val]) => val.length >= 3)
|
|
26
|
+
.map(([key, val]) => [key, rgb(val[0], val[1], val[2]).formatHex()]));
|
|
36
27
|
export const vesta_hex = rgb_scheme_to_hex(vesta_colors);
|
|
37
28
|
export const jmol_hex = rgb_scheme_to_hex(jmol_colors);
|
|
38
29
|
export const alloy_hex = rgb_scheme_to_hex(alloy_colors);
|
|
@@ -11,7 +11,7 @@ let bubbles = $derived.by(() => {
|
|
|
11
11
|
// Create hierarchy data structure for D3 pack
|
|
12
12
|
const hierarchy_data = {
|
|
13
13
|
children: element_entries.map(([element, amount]) => ({
|
|
14
|
-
element
|
|
14
|
+
element,
|
|
15
15
|
amount: amount,
|
|
16
16
|
color: element_colors[element] || `#cccccc`,
|
|
17
17
|
})),
|
|
@@ -78,7 +78,7 @@ let segments = $derived.by(() => {
|
|
|
78
78
|
const font_scale = get_chart_font_scale(base_scale, label_text, available_space);
|
|
79
79
|
const color = element_colors[element] || `#cccccc`;
|
|
80
80
|
return {
|
|
81
|
-
element
|
|
81
|
+
element,
|
|
82
82
|
amount: amount,
|
|
83
83
|
percentage,
|
|
84
84
|
color,
|
|
@@ -3,13 +3,13 @@ export declare const atomic_number_to_symbol: Record<number, ElementSymbol>;
|
|
|
3
3
|
export declare const symbol_to_atomic_number: Partial<Record<ElementSymbol, number>>;
|
|
4
4
|
export declare const atomic_weights: Map<"K" | "H" | "He" | "Li" | "Be" | "B" | "C" | "N" | "O" | "F" | "Ne" | "Na" | "Mg" | "Al" | "Si" | "P" | "S" | "Cl" | "Ar" | "Ca" | "Sc" | "Ti" | "V" | "Cr" | "Mn" | "Fe" | "Co" | "Ni" | "Cu" | "Zn" | "Ga" | "Ge" | "As" | "Se" | "Br" | "Kr" | "Rb" | "Sr" | "Y" | "Zr" | "Nb" | "Mo" | "Tc" | "Ru" | "Rh" | "Pd" | "Ag" | "Cd" | "In" | "Sn" | "Sb" | "Te" | "I" | "Xe" | "Cs" | "Ba" | "La" | "Ce" | "Pr" | "Nd" | "Pm" | "Sm" | "Eu" | "Gd" | "Tb" | "Dy" | "Ho" | "Er" | "Tm" | "Yb" | "Lu" | "Hf" | "Ta" | "W" | "Re" | "Os" | "Ir" | "Pt" | "Au" | "Hg" | "Tl" | "Pb" | "Bi" | "Po" | "At" | "Rn" | "Fr" | "Ra" | "Ac" | "Th" | "Pa" | "U" | "Np" | "Pu" | "Am" | "Cm" | "Bk" | "Cf" | "Es" | "Fm" | "Md" | "No" | "Lr" | "Rf" | "Db" | "Sg" | "Bh" | "Hs" | "Mt" | "Ds" | "Rg" | "Cn" | "Nh" | "Fl" | "Mc" | "Lv" | "Ts" | "Og", number>;
|
|
5
5
|
export declare const element_electronegativity_map: Map<"K" | "H" | "He" | "Li" | "Be" | "B" | "C" | "N" | "O" | "F" | "Ne" | "Na" | "Mg" | "Al" | "Si" | "P" | "S" | "Cl" | "Ar" | "Ca" | "Sc" | "Ti" | "V" | "Cr" | "Mn" | "Fe" | "Co" | "Ni" | "Cu" | "Zn" | "Ga" | "Ge" | "As" | "Se" | "Br" | "Kr" | "Rb" | "Sr" | "Y" | "Zr" | "Nb" | "Mo" | "Tc" | "Ru" | "Rh" | "Pd" | "Ag" | "Cd" | "In" | "Sn" | "Sb" | "Te" | "I" | "Xe" | "Cs" | "Ba" | "La" | "Ce" | "Pr" | "Nd" | "Pm" | "Sm" | "Eu" | "Gd" | "Tb" | "Dy" | "Ho" | "Er" | "Tm" | "Yb" | "Lu" | "Hf" | "Ta" | "W" | "Re" | "Os" | "Ir" | "Pt" | "Au" | "Hg" | "Tl" | "Pb" | "Bi" | "Po" | "At" | "Rn" | "Fr" | "Ra" | "Ac" | "Th" | "Pa" | "U" | "Np" | "Pu" | "Am" | "Cm" | "Bk" | "Cf" | "Es" | "Fm" | "Md" | "No" | "Lr" | "Rf" | "Db" | "Sg" | "Bh" | "Hs" | "Mt" | "Ds" | "Rg" | "Cn" | "Nh" | "Fl" | "Mc" | "Lv" | "Ts" | "Og", number>;
|
|
6
|
-
export declare
|
|
7
|
-
export declare
|
|
8
|
-
export declare
|
|
9
|
-
export declare
|
|
10
|
-
export declare
|
|
6
|
+
export declare const atomic_num_to_symbols: (atomic_composition: Record<number, number>) => CompositionType;
|
|
7
|
+
export declare const atomic_symbol_to_num: (symbol_composition: CompositionType) => Record<number, number>;
|
|
8
|
+
export declare const parse_formula: (formula: string) => CompositionType;
|
|
9
|
+
export declare const normalize_composition: (composition: CompositionType | Record<number, number> | Record<string | number, number>) => CompositionType;
|
|
10
|
+
export declare const composition_to_percentages: (composition: CompositionType, by_weight?: boolean) => CompositionType;
|
|
11
11
|
export declare const get_total_atoms: (composition: CompositionType) => number;
|
|
12
|
-
export declare
|
|
13
|
-
export declare
|
|
14
|
-
export declare
|
|
15
|
-
export declare
|
|
12
|
+
export declare const parse_composition: (input: string | CompositionType | Record<number, number> | Record<string | number, number>) => CompositionType;
|
|
13
|
+
export declare const format_composition_formula: (composition: CompositionType, sort_fn: (symbols: ElementSymbol[]) => ElementSymbol[], plain_text?: boolean, delim?: string, amount_format?: string) => string;
|
|
14
|
+
export declare const get_alphabetical_formula: (input: string | CompositionType | AnyStructure, plain_text?: boolean, delim?: string, amount_format?: string) => string;
|
|
15
|
+
export declare const get_electro_neg_formula: (input: string | CompositionType | AnyStructure, plain_text?: boolean, delim?: string, amount_format?: string) => string;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { elem_symbols } from '..';
|
|
1
|
+
import { elem_symbols, format_num } from '..';
|
|
2
2
|
import element_data from '../element/data';
|
|
3
|
-
// Create
|
|
3
|
+
// Create symbol/number/mass/electronegativity lookup maps for O(1) access
|
|
4
4
|
export const atomic_number_to_symbol = {};
|
|
5
5
|
export const symbol_to_atomic_number = {};
|
|
6
|
-
// Create mass/electronegativity maps for O(1) lookups in loops below
|
|
7
6
|
export const atomic_weights = new Map();
|
|
8
7
|
export const element_electronegativity_map = new Map();
|
|
9
8
|
// Populate maps at module load time
|
|
@@ -13,93 +12,68 @@ for (const element of element_data) {
|
|
|
13
12
|
atomic_weights.set(element.symbol, element.atomic_mass);
|
|
14
13
|
element_electronegativity_map.set(element.symbol, element.electronegativity ?? 0);
|
|
15
14
|
}
|
|
16
|
-
// Check if
|
|
17
|
-
|
|
18
|
-
function is_atomic_number_composition(obj) {
|
|
15
|
+
// Check if object has atomic numbers as keys (1-118)
|
|
16
|
+
const is_atomic_number_composition = (obj) => {
|
|
19
17
|
const keys = Object.keys(obj);
|
|
20
|
-
return
|
|
21
|
-
keys.map(Number).every((num) => Number.isInteger(num) && num >= 1 && num <= 118)
|
|
22
|
-
}
|
|
23
|
-
// Convert
|
|
24
|
-
|
|
25
|
-
export function convert_atomic_numbers_to_symbols(atomic_composition) {
|
|
18
|
+
return keys.length > 0 &&
|
|
19
|
+
keys.map(Number).every((num) => Number.isInteger(num) && num >= 1 && num <= 118);
|
|
20
|
+
};
|
|
21
|
+
// Convert atomic numbers to element symbols
|
|
22
|
+
export const atomic_num_to_symbols = (atomic_composition) => {
|
|
26
23
|
const composition = {};
|
|
27
|
-
for (const [
|
|
28
|
-
const
|
|
29
|
-
const symbol = atomic_number_to_symbol[atomic_number] || null;
|
|
24
|
+
for (const [atomic_num_str, amount] of Object.entries(atomic_composition)) {
|
|
25
|
+
const symbol = atomic_number_to_symbol[Number(atomic_num_str)];
|
|
30
26
|
if (!symbol)
|
|
31
|
-
throw new Error(`Invalid atomic number: ${
|
|
32
|
-
if (
|
|
27
|
+
throw new Error(`Invalid atomic number: ${atomic_num_str}`);
|
|
28
|
+
if (amount > 0)
|
|
33
29
|
composition[symbol] = (composition[symbol] || 0) + amount;
|
|
34
|
-
}
|
|
35
30
|
}
|
|
36
31
|
return composition;
|
|
37
|
-
}
|
|
38
|
-
// Convert
|
|
39
|
-
|
|
40
|
-
export function convert_symbols_to_atomic_numbers(symbol_composition) {
|
|
32
|
+
};
|
|
33
|
+
// Convert element symbols to atomic numbers
|
|
34
|
+
export const atomic_symbol_to_num = (symbol_composition) => {
|
|
41
35
|
const atomic_composition = {};
|
|
42
36
|
for (const [symbol, amount] of Object.entries(symbol_composition)) {
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
37
|
+
const atomic_num = symbol_to_atomic_number[symbol];
|
|
38
|
+
if (!atomic_num)
|
|
45
39
|
throw new Error(`Invalid element symbol: ${symbol}`);
|
|
46
|
-
if (
|
|
47
|
-
atomic_composition[
|
|
48
|
-
amount;
|
|
40
|
+
if (amount > 0) {
|
|
41
|
+
atomic_composition[atomic_num] = (atomic_composition[atomic_num] || 0) + amount;
|
|
49
42
|
}
|
|
50
43
|
}
|
|
51
44
|
return atomic_composition;
|
|
52
|
-
}
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
};
|
|
46
|
+
// Expand parentheses in chemical formulas
|
|
47
|
+
const expand_parentheses = (formula) => {
|
|
48
|
+
while (formula.includes(`(`)) {
|
|
49
|
+
formula = formula.replace(/\(([^()]+)\)(\d*)/g, (_match, group, multiplier) => {
|
|
50
|
+
const mult = multiplier ? parseInt(multiplier, 10) : 1;
|
|
51
|
+
return group.replace(/([A-Z][a-z]?)(\d*)/g, (_m, element, count) => {
|
|
52
|
+
const num = (count ? parseInt(count, 10) : 1) * mult;
|
|
53
|
+
return element + (num > 1 ? num : ``);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return formula;
|
|
58
|
+
};
|
|
59
|
+
// Parse chemical formula string into composition object
|
|
60
|
+
export const parse_formula = (formula) => {
|
|
56
61
|
const composition = {};
|
|
57
|
-
// Remove whitespace and handle parentheses by expanding them
|
|
58
62
|
const cleaned_formula = expand_parentheses(formula.replace(/\s/g, ``));
|
|
59
|
-
|
|
60
|
-
// This regex matches: Capital letter, optional lowercase letter, optional number
|
|
61
|
-
const pattern = /([A-Z][a-z]?)(\d*)/g;
|
|
62
|
-
let match;
|
|
63
|
-
while ((match = pattern.exec(cleaned_formula)) !== null) {
|
|
63
|
+
for (const match of cleaned_formula.matchAll(/([A-Z][a-z]?)(\d*)/g)) {
|
|
64
64
|
const element = match[1];
|
|
65
65
|
const count = match[2] ? parseInt(match[2], 10) : 1;
|
|
66
|
-
// Validate element symbol
|
|
67
66
|
if (!elem_symbols.includes(element)) {
|
|
68
67
|
throw new Error(`Invalid element symbol: ${element}`);
|
|
69
68
|
}
|
|
70
69
|
composition[element] = (composition[element] || 0) + count;
|
|
71
70
|
}
|
|
72
71
|
return composition;
|
|
73
|
-
}
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
function expand_parentheses(formula) {
|
|
77
|
-
// Handle nested parentheses by expanding from innermost to outermost
|
|
78
|
-
while (formula.includes(`(`)) {
|
|
79
|
-
formula = formula.replace(/\(([^()]+)\)(\d*)/g, (_match, group, multiplier) => {
|
|
80
|
-
const mult = multiplier ? parseInt(multiplier, 10) : 1;
|
|
81
|
-
let expanded = ``;
|
|
82
|
-
// Parse elements within parentheses
|
|
83
|
-
const inner_pattern = /([A-Z][a-z]?)(\d*)/g;
|
|
84
|
-
let inner_match;
|
|
85
|
-
while ((inner_match = inner_pattern.exec(group)) !== null) {
|
|
86
|
-
const element = inner_match[1];
|
|
87
|
-
const count = inner_match[2] ? parseInt(inner_match[2], 10) : 1;
|
|
88
|
-
expanded += element + (count * mult > 1 ? count * mult : ``);
|
|
89
|
-
}
|
|
90
|
-
return expanded;
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return formula;
|
|
94
|
-
}
|
|
95
|
-
// Normalize a composition by ensuring all values are positive numbers
|
|
96
|
-
// Handles both element symbol and atomic number compositions
|
|
97
|
-
export function normalize_composition(composition) {
|
|
98
|
-
// If it's an atomic number composition, convert to symbols first
|
|
72
|
+
};
|
|
73
|
+
// Normalize composition to positive numbers only
|
|
74
|
+
export const normalize_composition = (composition) => {
|
|
99
75
|
if (is_atomic_number_composition(composition)) {
|
|
100
|
-
|
|
101
|
-
const symbol_comp = convert_atomic_numbers_to_symbols(atomic_comp);
|
|
102
|
-
return normalize_composition(symbol_comp);
|
|
76
|
+
return normalize_composition(atomic_num_to_symbols(composition));
|
|
103
77
|
}
|
|
104
78
|
const normalized = {};
|
|
105
79
|
for (const [element, amount] of Object.entries(composition)) {
|
|
@@ -108,178 +82,109 @@ export function normalize_composition(composition) {
|
|
|
108
82
|
}
|
|
109
83
|
}
|
|
110
84
|
return normalized;
|
|
111
|
-
}
|
|
112
|
-
// Convert composition to percentages
|
|
113
|
-
export
|
|
85
|
+
};
|
|
86
|
+
// Convert composition to percentages
|
|
87
|
+
export const composition_to_percentages = (composition, by_weight = false) => {
|
|
114
88
|
if (by_weight) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const weight = amount * atomic_mass;
|
|
125
|
-
element_weights[element] = weight;
|
|
126
|
-
total_weight += weight;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
89
|
+
const element_weights = Object.fromEntries(Object.entries(composition)
|
|
90
|
+
.filter(([, amount]) => amount > 0)
|
|
91
|
+
.map(([element, amount]) => {
|
|
92
|
+
const atomic_mass = atomic_weights.get(element);
|
|
93
|
+
if (!atomic_mass)
|
|
94
|
+
throw new Error(`Unknown element: ${element}`);
|
|
95
|
+
return [element, amount * atomic_mass];
|
|
96
|
+
}));
|
|
97
|
+
const total_weight = Object.values(element_weights).reduce((sum, weight) => sum + weight, 0);
|
|
129
98
|
if (total_weight === 0)
|
|
130
99
|
return {};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
return percentages;
|
|
100
|
+
return Object.fromEntries(Object.entries(element_weights).map(([element, weight]) => [
|
|
101
|
+
element,
|
|
102
|
+
(weight / total_weight) * 100,
|
|
103
|
+
]));
|
|
137
104
|
}
|
|
138
|
-
const total =
|
|
105
|
+
const total = Object.values(composition).reduce((sum, count) => sum + (count ?? 0), 0);
|
|
139
106
|
if (total === 0)
|
|
140
107
|
return {};
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return percentages;
|
|
148
|
-
}
|
|
149
|
-
// Get the total number of atoms in a composition
|
|
108
|
+
return Object.fromEntries(Object.entries(composition).map(([element, amount]) => [
|
|
109
|
+
element,
|
|
110
|
+
((amount ?? 0) / total) * 100,
|
|
111
|
+
]));
|
|
112
|
+
};
|
|
113
|
+
// Get total number of atoms
|
|
150
114
|
export const get_total_atoms = (composition) => Object.values(composition).reduce((sum, count) => sum + (count ?? 0), 0);
|
|
151
|
-
//
|
|
152
|
-
export
|
|
115
|
+
// Parse composition from various input types
|
|
116
|
+
export const parse_composition = (input) => {
|
|
153
117
|
if (typeof input === `string`) {
|
|
154
|
-
// First try to parse as JSON (for composition objects like {"Fe": 70, "Cr": 18})
|
|
155
118
|
if (input.trim().startsWith(`{`) && input.trim().endsWith(`}`)) {
|
|
156
119
|
try {
|
|
157
|
-
|
|
158
|
-
return normalize_composition(parsed_json);
|
|
120
|
+
return normalize_composition(JSON.parse(input));
|
|
159
121
|
}
|
|
160
122
|
catch {
|
|
161
|
-
//
|
|
123
|
+
// Fall through to formula parsing
|
|
162
124
|
}
|
|
163
125
|
}
|
|
164
|
-
// If not JSON or JSON parsing failed, treat as chemical formula
|
|
165
126
|
return normalize_composition(parse_formula(input));
|
|
166
127
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// @param composition - Composition object like {Fe: 2, O: 3}
|
|
172
|
-
// @param sort_fn - Function to sort element symbols
|
|
173
|
-
// @param plain_text - If true, output plain text without HTML tags
|
|
174
|
-
// @param delim - Delimiter between elements (default: space)
|
|
175
|
-
// @returns Formatted chemical formula with or without subscripts
|
|
176
|
-
export function format_composition_formula(composition, sort_fn, plain_text = false, delim = ` `) {
|
|
177
|
-
const formula = [];
|
|
178
|
-
const symbols = Object.keys(composition);
|
|
179
|
-
for (const el of sort_fn(symbols)) {
|
|
180
|
-
const amount = composition[el];
|
|
181
|
-
if (amount && amount > 0) {
|
|
182
|
-
if (amount === 1)
|
|
183
|
-
formula.push(el);
|
|
184
|
-
else
|
|
185
|
-
formula.push(plain_text ? `${el}${amount}` : `${el}<sub>${amount}</sub>`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return formula.join(delim);
|
|
189
|
-
}
|
|
190
|
-
// Versatile function to create an alphabetical formula from any input type
|
|
191
|
-
// @param input - String formula, composition object, or structure
|
|
192
|
-
// @param plain_text - If true, output plain text without HTML tags
|
|
193
|
-
// @param delim - Delimiter between elements (default: space)
|
|
194
|
-
// @returns Alphabetically sorted chemical formula
|
|
195
|
-
export function get_alphabetical_formula(input, plain_text = false, delim = ` `) {
|
|
196
|
-
if (typeof input === `string`) {
|
|
197
|
-
// If it's already a string, parse it and reformat alphabetically
|
|
198
|
-
try {
|
|
199
|
-
const composition = parse_composition(input);
|
|
200
|
-
return format_composition_formula(composition, (symbols) => symbols.sort(), plain_text, delim);
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
// If parsing fails, return the original string
|
|
204
|
-
return input;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
else if (`sites` in input || `lattice` in input) {
|
|
208
|
-
// It's a structure object - need to extract composition
|
|
209
|
-
try {
|
|
210
|
-
const composition = structure_to_composition(input);
|
|
211
|
-
return format_composition_formula(composition, (symbols) => symbols.sort(), plain_text, delim);
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
return `Unknown`;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else { // It's a composition object
|
|
218
|
-
return format_composition_formula(input, (symbols) => symbols.sort(), plain_text, delim);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// Versatile function to create an electronegativity-sorted formula from any input type
|
|
222
|
-
// @param input - String formula, composition object, or structure
|
|
223
|
-
// @param plain_text - If true, output plain text without HTML tags
|
|
224
|
-
// @param delim - Delimiter between elements (default: space)
|
|
225
|
-
// @returns Electronegativity-sorted chemical formula
|
|
226
|
-
export function get_electro_neg_formula(input, plain_text = false, delim = ` `) {
|
|
227
|
-
const sort_by_electronegativity = (symbols) => {
|
|
228
|
-
return symbols.sort((el1, el2) => {
|
|
229
|
-
const elec_neg1 = element_electronegativity_map.get(el1) ?? 0;
|
|
230
|
-
const elec_neg2 = element_electronegativity_map.get(el2) ?? 0;
|
|
231
|
-
// Sort by electronegativity (ascending), then alphabetically for ties
|
|
232
|
-
if (elec_neg1 !== elec_neg2)
|
|
233
|
-
return elec_neg1 - elec_neg2;
|
|
234
|
-
return el1.localeCompare(el2);
|
|
235
|
-
});
|
|
236
|
-
};
|
|
237
|
-
if (typeof input === `string`) {
|
|
238
|
-
// If it's already a string, parse it and reformat by electronegativity
|
|
239
|
-
try {
|
|
240
|
-
const composition = parse_composition(input);
|
|
241
|
-
return format_composition_formula(composition, sort_by_electronegativity, plain_text, delim);
|
|
242
|
-
}
|
|
243
|
-
catch {
|
|
244
|
-
return input; // If parsing fails, return the original string
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
else if (`sites` in input || `lattice` in input) {
|
|
248
|
-
try { // It's a structure object - need to extract composition
|
|
249
|
-
const composition = structure_to_composition(input);
|
|
250
|
-
return format_composition_formula(composition, sort_by_electronegativity, plain_text, delim);
|
|
251
|
-
}
|
|
252
|
-
catch {
|
|
253
|
-
return `Unknown`;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
else { // It's a composition object
|
|
257
|
-
return format_composition_formula(input, sort_by_electronegativity, plain_text, delim);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
// Extract composition from a structure object
|
|
261
|
-
// @param structure - Structure object with sites property
|
|
262
|
-
// @returns Composition object
|
|
263
|
-
function structure_to_composition(structure) {
|
|
264
|
-
const composition = {};
|
|
128
|
+
return normalize_composition(input);
|
|
129
|
+
};
|
|
130
|
+
// Extract composition from structure object
|
|
131
|
+
const structure_to_composition = (structure) => {
|
|
265
132
|
if (!structure.sites || !Array.isArray(structure.sites)) {
|
|
266
133
|
throw new Error(`Invalid structure object`);
|
|
267
134
|
}
|
|
135
|
+
const composition = {};
|
|
268
136
|
for (const site of structure.sites) {
|
|
269
137
|
if (site.species && Array.isArray(site.species)) {
|
|
270
138
|
for (const species of site.species) {
|
|
271
139
|
const element = species.element;
|
|
272
140
|
const occu = species.occu || 1;
|
|
273
|
-
|
|
274
|
-
composition[element] = occu;
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
const current = composition[element];
|
|
278
|
-
if (current !== undefined)
|
|
279
|
-
composition[element] = current + occu;
|
|
280
|
-
}
|
|
141
|
+
composition[element] = (composition[element] || 0) + occu;
|
|
281
142
|
}
|
|
282
143
|
}
|
|
283
144
|
}
|
|
284
145
|
return composition;
|
|
285
|
-
}
|
|
146
|
+
};
|
|
147
|
+
// Format composition into chemical formula string
|
|
148
|
+
export const format_composition_formula = (composition, sort_fn, plain_text = false, delim = ` `, amount_format = `.3~s`) => {
|
|
149
|
+
const symbols = Object.keys(composition);
|
|
150
|
+
return sort_fn(symbols)
|
|
151
|
+
.filter((el) => composition[el] && composition[el] > 0)
|
|
152
|
+
.map((el) => {
|
|
153
|
+
const amount = composition[el];
|
|
154
|
+
if (amount === 1)
|
|
155
|
+
return el;
|
|
156
|
+
const formatted_amount = format_num(Number(amount), amount_format);
|
|
157
|
+
return plain_text
|
|
158
|
+
? `${el}${formatted_amount}`
|
|
159
|
+
: `${el}<sub>${formatted_amount}</sub>`;
|
|
160
|
+
})
|
|
161
|
+
.join(delim);
|
|
162
|
+
};
|
|
163
|
+
// Generic formula formatter with error handling
|
|
164
|
+
const format_formula_generic = (input, sort_fn, plain_text = false, delim = ` `, amount_format = `.3~s`) => {
|
|
165
|
+
try {
|
|
166
|
+
let composition;
|
|
167
|
+
if (typeof input === `string`)
|
|
168
|
+
composition = parse_composition(input);
|
|
169
|
+
else if (`sites` in input || `lattice` in input) {
|
|
170
|
+
composition = structure_to_composition(input);
|
|
171
|
+
}
|
|
172
|
+
else
|
|
173
|
+
composition = input;
|
|
174
|
+
return format_composition_formula(composition, sort_fn, plain_text, delim, amount_format);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return typeof input === `string` ? input : `Unknown`;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
// Create alphabetical formula
|
|
181
|
+
export const get_alphabetical_formula = (input, plain_text = false, delim = ` `, amount_format = `.3~s`) => format_formula_generic(input, (symbols) => symbols.sort(), plain_text, delim, amount_format);
|
|
182
|
+
// Create electronegativity-sorted formula
|
|
183
|
+
export const get_electro_neg_formula = (input, plain_text = false, delim = ` `, amount_format = `.3~s`) => {
|
|
184
|
+
const sort_by_electronegativity = (symbols) => symbols.sort((el1, el2) => {
|
|
185
|
+
const elec_neg1 = element_electronegativity_map.get(el1) ?? 0;
|
|
186
|
+
const elec_neg2 = element_electronegativity_map.get(el2) ?? 0;
|
|
187
|
+
return elec_neg1 !== elec_neg2 ? elec_neg1 - elec_neg2 : el1.localeCompare(el2);
|
|
188
|
+
});
|
|
189
|
+
return format_formula_generic(input, sort_by_electronegativity, plain_text, delim, amount_format);
|
|
190
|
+
};
|
|
@@ -20,6 +20,6 @@ interface Props {
|
|
|
20
20
|
split_layout?: SplitLayout;
|
|
21
21
|
[key: string]: unknown;
|
|
22
22
|
}
|
|
23
|
-
declare const ElementTile: import("svelte").Component<Props, {}, "
|
|
23
|
+
declare const ElementTile: import("svelte").Component<Props, {}, "node" | "text_color">;
|
|
24
24
|
type ElementTile = ReturnType<typeof ElementTile>;
|
|
25
25
|
export default ElementTile;
|