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.
Files changed (63) hide show
  1. package/dist/DraggablePanel.svelte +52 -17
  2. package/dist/app.css +3 -3
  3. package/dist/colors/index.js +3 -12
  4. package/dist/composition/BarChart.svelte +1 -1
  5. package/dist/composition/BubbleChart.svelte +1 -1
  6. package/dist/composition/PieChart.svelte +1 -1
  7. package/dist/composition/parse.d.ts +9 -9
  8. package/dist/composition/parse.js +119 -214
  9. package/dist/element/ElementTile.svelte.d.ts +1 -1
  10. package/dist/element/Nucleus.svelte +0 -9
  11. package/dist/icons.d.ts +3 -3
  12. package/dist/icons.js +3 -3
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +1 -0
  15. package/dist/io/decompress.d.ts +6 -2
  16. package/dist/io/decompress.js +27 -27
  17. package/dist/io/export.d.ts +4 -0
  18. package/dist/io/export.js +210 -12
  19. package/dist/io/fetch.d.ts +4 -0
  20. package/dist/io/fetch.js +34 -0
  21. package/dist/io/index.js +51 -33
  22. package/dist/io/parse.d.ts +24 -1
  23. package/dist/io/parse.js +321 -206
  24. package/dist/math.d.ts +3 -1
  25. package/dist/math.js +18 -1
  26. package/dist/mp-api.d.ts +6 -6
  27. package/dist/mp-api.js +18 -36
  28. package/dist/plot/Histogram.svelte +231 -146
  29. package/dist/plot/Histogram.svelte.d.ts +13 -1
  30. package/dist/plot/HistogramControls.svelte +214 -225
  31. package/dist/plot/HistogramControls.svelte.d.ts +6 -1
  32. package/dist/plot/ScatterPlot.svelte.d.ts +1 -1
  33. package/dist/plot/ScatterPlotControls.svelte +239 -363
  34. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  35. package/dist/settings.d.ts +113 -0
  36. package/dist/settings.js +489 -0
  37. package/dist/structure/Bond.svelte +3 -7
  38. package/dist/structure/Bond.svelte.d.ts +3 -2
  39. package/dist/structure/Lattice.svelte +2 -2
  40. package/dist/structure/Structure.svelte +112 -40
  41. package/dist/structure/Structure.svelte.d.ts +15 -2
  42. package/dist/structure/StructureControls.svelte +352 -285
  43. package/dist/structure/StructureControls.svelte.d.ts +1 -8
  44. package/dist/structure/StructureInfoPanel.svelte +2 -75
  45. package/dist/structure/StructureScene.svelte +220 -152
  46. package/dist/structure/StructureScene.svelte.d.ts +8 -2
  47. package/dist/structure/Vector.svelte +2 -2
  48. package/dist/structure/index.d.ts +17 -24
  49. package/dist/structure/index.js +0 -22
  50. package/dist/theme/index.d.ts +5 -9
  51. package/dist/theme/index.js +6 -2
  52. package/dist/trajectory/Trajectory.svelte +402 -199
  53. package/dist/trajectory/Trajectory.svelte.d.ts +22 -6
  54. package/dist/trajectory/TrajectoryInfoPanel.svelte +150 -153
  55. package/dist/trajectory/TrajectoryInfoPanel.svelte.d.ts +2 -2
  56. package/dist/trajectory/index.d.ts +51 -8
  57. package/dist/trajectory/index.js +103 -32
  58. package/dist/trajectory/parse.d.ts +35 -7
  59. package/dist/trajectory/parse.js +757 -516
  60. package/dist/trajectory/plotting.d.ts +15 -10
  61. package/dist/trajectory/plotting.js +316 -280
  62. package/package.json +7 -7
  63. 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: 4pt;
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: auto;
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: 0.5px;
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: auto;
260
+ margin-left: 4pt;
224
261
  width: 100px;
225
262
  flex-shrink: 0;
226
263
  }
227
- .draggable-panel :global(.slider-control input[type='range']) {
228
- margin-left: 0;
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: 4pt;
261
- align-items: flex-start;
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: 8pt;
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: 60px;
121
+ min-width: 40px;
122
122
  width: auto;
123
123
  }
124
124
  input[type='color'] {
@@ -21,18 +21,9 @@ export const default_category_colors = {
21
21
  lanthanide: `#58748e`,
22
22
  actinide: `#6495ed`, // cornflowerblue
23
23
  };
24
- function rgb_scheme_to_hex(obj) {
25
- const result = {};
26
- for (const key in obj) {
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);
@@ -40,7 +40,7 @@ let segments = $derived.by(() => {
40
40
  below_labels++;
41
41
  }
42
42
  return {
43
- element: element,
43
+ element,
44
44
  amount: amount,
45
45
  percentage,
46
46
  color,
@@ -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: 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: 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 function convert_atomic_numbers_to_symbols(atomic_composition: Record<number, number>): CompositionType;
7
- export declare function convert_symbols_to_atomic_numbers(symbol_composition: CompositionType): Record<number, number>;
8
- export declare function parse_formula(formula: string): CompositionType;
9
- export declare function normalize_composition(composition: CompositionType | Record<number, number> | Record<string | number, number>): CompositionType;
10
- export declare function composition_to_percentages(composition: CompositionType, by_weight?: boolean): CompositionType;
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 function parse_composition(input: string | CompositionType | Record<number, number> | Record<string | number, number>): CompositionType;
13
- export declare function format_composition_formula(composition: CompositionType, sort_fn: (symbols: ElementSymbol[]) => ElementSymbol[], plain_text?: boolean, delim?: string): string;
14
- export declare function get_alphabetical_formula(input: string | CompositionType | AnyStructure, plain_text?: boolean, delim?: string): string;
15
- export declare function get_electro_neg_formula(input: string | CompositionType | AnyStructure, plain_text?: boolean, delim?: string): string;
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 a mapping from atomic numbers to element symbols
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 an object represents a composition with atomic numbers as keys
17
- // Returns true if ALL keys are valid atomic numbers (1-118) and at least one key exists
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 (keys.length > 0 &&
21
- keys.map(Number).every((num) => Number.isInteger(num) && num >= 1 && num <= 118));
22
- }
23
- // Convert a composition with atomic numbers to element symbols
24
- // Example: {26: 2, 8: 3} -> {Fe: 2, O: 3}
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 [atomic_number_str, amount] of Object.entries(atomic_composition)) {
28
- const atomic_number = Number(atomic_number_str);
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: ${atomic_number}`);
32
- if (typeof amount === `number` && amount > 0) {
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 a composition with element symbols to atomic numbers
39
- // Example: {Fe: 2, O: 3} -> {26: 2, 8: 3}
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 atomic_number = symbol_to_atomic_number[symbol];
44
- if (!atomic_number)
37
+ const atomic_num = symbol_to_atomic_number[symbol];
38
+ if (!atomic_num)
45
39
  throw new Error(`Invalid element symbol: ${symbol}`);
46
- if (typeof amount === `number` && amount > 0) {
47
- atomic_composition[atomic_number] = (atomic_composition[atomic_number] || 0) +
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
- // Parse a chemical formula string into a composition object
54
- // Examples: "Fe2O3" -> {Fe: 2, O: 3}, "CaCO3" -> {Ca: 1, C: 1, O: 3}
55
- export function parse_formula(formula) {
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
- // Match element symbols followed by numbers
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
- // Expand parentheses in chemical formulas
75
- // Example: "Ca(OH)2" -> "CaO2H2"
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
- const atomic_comp = composition;
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 by weight or by count
113
- export function composition_to_percentages(composition, by_weight = false) {
85
+ };
86
+ // Convert composition to percentages
87
+ export const composition_to_percentages = (composition, by_weight = false) => {
114
88
  if (by_weight) {
115
- // Calculate weight-based percentages using atomic masses
116
- let total_weight = 0;
117
- const element_weights = {};
118
- // Calculate weight for each element
119
- for (const [element, amount] of Object.entries(composition)) {
120
- if (typeof amount === `number` && amount > 0) {
121
- const atomic_mass = atomic_weights.get(element);
122
- if (atomic_mass === undefined)
123
- throw new Error(`Unknown element: ${element}`);
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
- // Convert to percentages
132
- const percentages = {};
133
- for (const [element, weight] of Object.entries(element_weights)) {
134
- percentages[element] = (weight / total_weight) * 100;
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 = get_total_atoms(composition);
105
+ const total = Object.values(composition).reduce((sum, count) => sum + (count ?? 0), 0);
139
106
  if (total === 0)
140
107
  return {};
141
- const percentages = {};
142
- for (const [element, amount] of Object.entries(composition)) {
143
- if (typeof amount === `number`) {
144
- percentages[element] = (amount / total) * 100;
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
- // Convert composition input (string, symbol object, or atomic number object) to normalized composition object
152
- export function parse_composition(input) {
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
- const parsed_json = JSON.parse(input);
158
- return normalize_composition(parsed_json);
120
+ return normalize_composition(JSON.parse(input));
159
121
  }
160
122
  catch {
161
- // If JSON parsing fails, fall through to formula parsing
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
- else
168
- return normalize_composition(input);
169
- }
170
- // Format a composition object into a chemical formula string
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
- if (composition[element] === undefined) {
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, {}, "text_color" | "node">;
23
+ declare const ElementTile: import("svelte").Component<Props, {}, "node" | "text_color">;
24
24
  type ElementTile = ReturnType<typeof ElementTile>;
25
25
  export default ElementTile;