matterviz 0.1.0

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 (131) hide show
  1. package/dist/BohrAtom.svelte +105 -0
  2. package/dist/BohrAtom.svelte.d.ts +21 -0
  3. package/dist/ControlPanel.svelte +158 -0
  4. package/dist/ControlPanel.svelte.d.ts +18 -0
  5. package/dist/Icon.svelte +23 -0
  6. package/dist/Icon.svelte.d.ts +8 -0
  7. package/dist/InfoCard.svelte +79 -0
  8. package/dist/InfoCard.svelte.d.ts +23 -0
  9. package/dist/Nucleus.svelte +64 -0
  10. package/dist/Nucleus.svelte.d.ts +16 -0
  11. package/dist/Spinner.svelte +44 -0
  12. package/dist/Spinner.svelte.d.ts +7 -0
  13. package/dist/api.d.ts +6 -0
  14. package/dist/api.js +30 -0
  15. package/dist/colors/alloy-colors.json +111 -0
  16. package/dist/colors/dark-mode-colors.json +111 -0
  17. package/dist/colors/index.d.ts +26 -0
  18. package/dist/colors/index.js +72 -0
  19. package/dist/colors/jmol-colors.json +111 -0
  20. package/dist/colors/muted-colors.json +111 -0
  21. package/dist/colors/pastel-colors.json +111 -0
  22. package/dist/colors/vesta-colors.json +111 -0
  23. package/dist/composition/BarChart.svelte +260 -0
  24. package/dist/composition/BarChart.svelte.d.ts +33 -0
  25. package/dist/composition/BubbleChart.svelte +166 -0
  26. package/dist/composition/BubbleChart.svelte.d.ts +30 -0
  27. package/dist/composition/Composition.svelte +73 -0
  28. package/dist/composition/Composition.svelte.d.ts +27 -0
  29. package/dist/composition/PieChart.svelte +236 -0
  30. package/dist/composition/PieChart.svelte.d.ts +36 -0
  31. package/dist/composition/index.d.ts +5 -0
  32. package/dist/composition/index.js +5 -0
  33. package/dist/composition/parse.d.ts +14 -0
  34. package/dist/composition/parse.js +307 -0
  35. package/dist/element/ElementHeading.svelte +21 -0
  36. package/dist/element/ElementHeading.svelte.d.ts +8 -0
  37. package/dist/element/ElementPhoto.svelte +56 -0
  38. package/dist/element/ElementPhoto.svelte.d.ts +9 -0
  39. package/dist/element/ElementStats.svelte +73 -0
  40. package/dist/element/ElementStats.svelte.d.ts +8 -0
  41. package/dist/element/ElementTile.svelte +449 -0
  42. package/dist/element/ElementTile.svelte.d.ts +25 -0
  43. package/dist/element/data.d.ts +4958 -0
  44. package/dist/element/data.js +5628 -0
  45. package/dist/element/index.d.ts +4 -0
  46. package/dist/element/index.js +4 -0
  47. package/dist/icons.d.ts +435 -0
  48. package/dist/icons.js +435 -0
  49. package/dist/index.d.ts +82 -0
  50. package/dist/index.js +43 -0
  51. package/dist/io/decompress.d.ts +16 -0
  52. package/dist/io/decompress.js +78 -0
  53. package/dist/io/export.d.ts +9 -0
  54. package/dist/io/export.js +205 -0
  55. package/dist/io/parse.d.ts +53 -0
  56. package/dist/io/parse.js +747 -0
  57. package/dist/labels.d.ts +31 -0
  58. package/dist/labels.js +209 -0
  59. package/dist/material/MaterialCard.svelte +135 -0
  60. package/dist/material/MaterialCard.svelte.d.ts +10 -0
  61. package/dist/material/SymmetryCard.svelte +23 -0
  62. package/dist/material/SymmetryCard.svelte.d.ts +9 -0
  63. package/dist/material/index.d.ts +2 -0
  64. package/dist/material/index.js +2 -0
  65. package/dist/math.d.ts +24 -0
  66. package/dist/math.js +216 -0
  67. package/dist/periodic-table/PeriodicTable.svelte +284 -0
  68. package/dist/periodic-table/PeriodicTable.svelte.d.ts +50 -0
  69. package/dist/periodic-table/PropertySelect.svelte +20 -0
  70. package/dist/periodic-table/PropertySelect.svelte.d.ts +13 -0
  71. package/dist/periodic-table/TableInset.svelte +18 -0
  72. package/dist/periodic-table/TableInset.svelte.d.ts +9 -0
  73. package/dist/periodic-table/index.d.ts +9 -0
  74. package/dist/periodic-table/index.js +3 -0
  75. package/dist/plot/ColorBar.svelte +414 -0
  76. package/dist/plot/ColorBar.svelte.d.ts +22 -0
  77. package/dist/plot/ColorScaleSelect.svelte +31 -0
  78. package/dist/plot/ColorScaleSelect.svelte.d.ts +15 -0
  79. package/dist/plot/ElementScatter.svelte +38 -0
  80. package/dist/plot/ElementScatter.svelte.d.ts +14 -0
  81. package/dist/plot/Line.svelte +42 -0
  82. package/dist/plot/Line.svelte.d.ts +15 -0
  83. package/dist/plot/PlotLegend.svelte +206 -0
  84. package/dist/plot/PlotLegend.svelte.d.ts +18 -0
  85. package/dist/plot/ScatterPlot.svelte +1753 -0
  86. package/dist/plot/ScatterPlot.svelte.d.ts +114 -0
  87. package/dist/plot/ScatterPlotControls.svelte +505 -0
  88. package/dist/plot/ScatterPlotControls.svelte.d.ts +33 -0
  89. package/dist/plot/ScatterPoint.svelte +72 -0
  90. package/dist/plot/ScatterPoint.svelte.d.ts +17 -0
  91. package/dist/plot/index.d.ts +168 -0
  92. package/dist/plot/index.js +46 -0
  93. package/dist/state.svelte.d.ts +12 -0
  94. package/dist/state.svelte.js +11 -0
  95. package/dist/structure/Bond.svelte +68 -0
  96. package/dist/structure/Bond.svelte.d.ts +13 -0
  97. package/dist/structure/Lattice.svelte +115 -0
  98. package/dist/structure/Lattice.svelte.d.ts +15 -0
  99. package/dist/structure/Structure.svelte +298 -0
  100. package/dist/structure/Structure.svelte.d.ts +28 -0
  101. package/dist/structure/StructureCard.svelte +26 -0
  102. package/dist/structure/StructureCard.svelte.d.ts +9 -0
  103. package/dist/structure/StructureControls.svelte +383 -0
  104. package/dist/structure/StructureControls.svelte.d.ts +23 -0
  105. package/dist/structure/StructureLegend.svelte +130 -0
  106. package/dist/structure/StructureLegend.svelte.d.ts +17 -0
  107. package/dist/structure/StructureScene.svelte +331 -0
  108. package/dist/structure/StructureScene.svelte.d.ts +47 -0
  109. package/dist/structure/bonding.d.ts +16 -0
  110. package/dist/structure/bonding.js +150 -0
  111. package/dist/structure/index.d.ts +98 -0
  112. package/dist/structure/index.js +114 -0
  113. package/dist/structure/pbc.d.ts +6 -0
  114. package/dist/structure/pbc.js +72 -0
  115. package/dist/trajectory/Sidebar.svelte +412 -0
  116. package/dist/trajectory/Sidebar.svelte.d.ts +14 -0
  117. package/dist/trajectory/Trajectory.svelte +1084 -0
  118. package/dist/trajectory/Trajectory.svelte.d.ts +49 -0
  119. package/dist/trajectory/TrajectoryError.svelte +120 -0
  120. package/dist/trajectory/TrajectoryError.svelte.d.ts +12 -0
  121. package/dist/trajectory/extract.d.ts +5 -0
  122. package/dist/trajectory/extract.js +157 -0
  123. package/dist/trajectory/index.d.ts +16 -0
  124. package/dist/trajectory/index.js +49 -0
  125. package/dist/trajectory/parse.d.ts +13 -0
  126. package/dist/trajectory/parse.js +1093 -0
  127. package/dist/trajectory/plotting.d.ts +12 -0
  128. package/dist/trajectory/plotting.js +148 -0
  129. package/license +21 -0
  130. package/package.json +131 -0
  131. package/readme.md +95 -0
@@ -0,0 +1,114 @@
1
+ import { format_num, scale } from '..';
2
+ import element_data from '../element/data';
3
+ export { default as Bond } from './Bond.svelte';
4
+ export * as bonding_strategies from './bonding';
5
+ export { default as Lattice } from './Lattice.svelte';
6
+ export * from './pbc';
7
+ export { default as Structure } from './Structure.svelte';
8
+ export { default as StructureCard } from './StructureCard.svelte';
9
+ export { default as StructureControls } from './StructureControls.svelte';
10
+ export { default as StructureLegend } from './StructureLegend.svelte';
11
+ export { default as StructureScene } from './StructureScene.svelte';
12
+ export const CELL_DEFAULTS = {
13
+ edge_opacity: 0.4,
14
+ surface_opacity: 0.05,
15
+ color: `#ffffff`,
16
+ line_width: 1.5,
17
+ };
18
+ export const BOND_DEFAULTS = {
19
+ thickness: 0.25,
20
+ offset: 0,
21
+ color: `white`,
22
+ from_color: `white`,
23
+ to_color: `white`,
24
+ };
25
+ export const lattice_param_keys = [
26
+ `a`,
27
+ `b`,
28
+ `c`,
29
+ `alpha`,
30
+ `beta`,
31
+ `gamma`,
32
+ ];
33
+ export function get_elem_amounts(structure) {
34
+ const elements = {};
35
+ for (const site of structure.sites) {
36
+ for (const species of site.species) {
37
+ const { element: elem, occu } = species;
38
+ elements[elem] = (elements[elem] ?? 0) + occu;
39
+ }
40
+ }
41
+ return elements;
42
+ }
43
+ export function format_chemical_formula(structure, sort_fn) {
44
+ // concatenate elements in a pymatgen Structure followed by their amount
45
+ const elements = get_elem_amounts(structure);
46
+ const formula = [];
47
+ for (const el of sort_fn(Object.keys(elements))) {
48
+ const amount = elements[el] ?? 0;
49
+ if (amount === 1)
50
+ formula.push(el);
51
+ else
52
+ formula.push(`${el}<sub>${amount}</sub>`);
53
+ }
54
+ return formula.join(` `);
55
+ }
56
+ export function alphabetical_formula(structure) {
57
+ // concatenate elements in a pymatgen Structure followed by their amount in alphabetical order
58
+ return format_chemical_formula(structure, (symbols) => symbols.sort());
59
+ }
60
+ export function electro_neg_formula(structure) {
61
+ // concatenate elements in a pymatgen Structure followed by their amount sorted by electronegativity
62
+ return format_chemical_formula(structure, (symbols) => {
63
+ return symbols.sort((el1, el2) => {
64
+ const elec_neg1 = element_data.find((el) => el.symbol === el1)?.electronegativity ??
65
+ 0;
66
+ const elec_neg2 = element_data.find((el) => el.symbol === el2)?.electronegativity ??
67
+ 0;
68
+ // Sort by electronegativity (ascending), then alphabetically for ties
69
+ if (elec_neg1 !== elec_neg2)
70
+ return elec_neg1 - elec_neg2;
71
+ return el1.localeCompare(el2);
72
+ });
73
+ });
74
+ }
75
+ export const atomic_radii = Object.fromEntries(element_data.map((el) => [el.symbol, (el.atomic_radius ?? 1) / 2]));
76
+ export const atomic_weights = Object.fromEntries(element_data.map((el) => [el.symbol, el.atomic_mass]));
77
+ export function get_elements(structure) {
78
+ const elems = structure.sites.flatMap((site) => site.species.map((sp) => sp.element));
79
+ return [...new Set(elems)].sort(); // unique elements
80
+ }
81
+ // unified atomic mass units (u) per cubic angstrom (Å^3)
82
+ // to grams per cubic centimeter (g/cm^3)
83
+ const uA3_to_gcm3 = 1.66053907;
84
+ export function density(structure, prec = `.2f`) {
85
+ // calculate the density of a pymatgen Structure in
86
+ const elements = get_elem_amounts(structure);
87
+ let mass = 0;
88
+ for (const [el, amt] of Object.entries(elements)) {
89
+ const element = el;
90
+ const weight = atomic_weights[element];
91
+ if (weight !== undefined) {
92
+ mass += amt * weight;
93
+ }
94
+ }
95
+ const dens = (uA3_to_gcm3 * mass) / structure.lattice.volume;
96
+ return format_num(dens, prec);
97
+ }
98
+ export function get_center_of_mass(struct_or_mol) {
99
+ let center = [0, 0, 0];
100
+ let total_weight = 0;
101
+ for (const site of struct_or_mol.sites) {
102
+ // TODO this assumes there's just one species. doesn't handle disordered sites
103
+ const wt = site.species[0].occu;
104
+ const scaled_pos = scale(site.xyz, wt);
105
+ center = [
106
+ center[0] + scaled_pos[0],
107
+ center[1] + scaled_pos[1],
108
+ center[2] + scaled_pos[2],
109
+ ];
110
+ total_weight += wt;
111
+ }
112
+ const result = scale(center, 1 / total_weight);
113
+ return [result[0], result[1], result[2]];
114
+ }
@@ -0,0 +1,6 @@
1
+ import type { Vec3 } from '..';
2
+ import type { PymatgenStructure } from './index';
3
+ export declare function find_image_atoms(structure: PymatgenStructure, { tolerance }?: {
4
+ tolerance?: number;
5
+ }): [number, Vec3, Vec3][];
6
+ export declare function get_pbc_image_sites(...args: Parameters<typeof find_image_atoms>): PymatgenStructure;
@@ -0,0 +1,72 @@
1
+ import { add, scale } from '..';
2
+ export function find_image_atoms(structure, { tolerance = 0.05 } = {}) {
3
+ // Find image atoms for PBC. Returns [atom_idx, image_xyz, image_abc] tuples.
4
+ // Skips image generation for trajectory data with scattered atoms.
5
+ if (!structure.lattice)
6
+ return [];
7
+ const image_sites = [];
8
+ const lattice_vecs = structure.lattice.matrix;
9
+ // Check for trajectory data (atoms scattered outside unit cell)
10
+ const atoms_outside_cell = structure.sites.filter(({ abc }) => abc.some((coord) => coord < -0.1 || coord > 1.1));
11
+ // Skip image generation for trajectory data (>10% atoms outside cell)
12
+ if (atoms_outside_cell.length > structure.sites.length * 0.1) {
13
+ console.log(`Detected trajectory data with ${atoms_outside_cell.length} atoms outside unit cell. Skipping image atom generation.`);
14
+ return [];
15
+ }
16
+ // Generate image atoms for properly bounded structures
17
+ for (const [idx, site] of structure.sites.entries()) {
18
+ const abc = site.abc;
19
+ // Find edge dimensions and translation directions
20
+ const edge_dims = [];
21
+ for (let dim = 0; dim < 3; dim++) {
22
+ const frac_coord = abc[dim];
23
+ if (Math.abs(frac_coord) < tolerance)
24
+ edge_dims.push({ dim, direction: 1 }); // Near 0 → translate +1
25
+ if (Math.abs(frac_coord - 1) < tolerance)
26
+ edge_dims.push({ dim, direction: -1 }); // Near 1 → translate -1
27
+ }
28
+ // Generate all translation combinations (avoids duplicates)
29
+ if (edge_dims.length > 0) {
30
+ for (let mask = 1; mask < (1 << edge_dims.length); mask++) {
31
+ let img_xyz = [...site.xyz];
32
+ const img_abc = [...site.abc];
33
+ // Apply selected translations
34
+ for (let bit = 0; bit < edge_dims.length; bit++) {
35
+ if (mask & (1 << bit)) {
36
+ const { dim, direction } = edge_dims[bit];
37
+ const translation = scale(lattice_vecs[dim], direction);
38
+ const sum = add(img_xyz, translation);
39
+ img_xyz = [sum[0], sum[1], sum[2]];
40
+ img_abc[dim] += direction; // Update fractional coordinate
41
+ }
42
+ }
43
+ image_sites.push([idx, img_xyz, img_abc]);
44
+ }
45
+ }
46
+ }
47
+ return image_sites;
48
+ }
49
+ // Return structure with image atoms added
50
+ export function get_pbc_image_sites(...args) {
51
+ const structure = args[0];
52
+ // Check for trajectory data
53
+ const atoms_outside_cell = structure.sites.filter((site) => {
54
+ const abc = site.abc;
55
+ return abc.some((coord) => coord < -0.1 || coord > 1.1);
56
+ });
57
+ // Return trajectory data unchanged
58
+ if (atoms_outside_cell.length > structure.sites.length * 0.1) {
59
+ console.log(`Detected trajectory data with ${atoms_outside_cell.length} atoms outside unit cell. Returning structure as-is for proper trajectory visualization.`);
60
+ return structure;
61
+ }
62
+ // Add image atoms to regular crystal structures
63
+ const image_sites = find_image_atoms(...args);
64
+ const symmetrized_structure = { ...structure };
65
+ symmetrized_structure.sites = [...structure.sites];
66
+ // Add image atoms as new sites
67
+ for (const [site_idx, img_xyz, img_abc] of image_sites) {
68
+ const original_site = structure.sites[site_idx];
69
+ symmetrized_structure.sites.push({ ...original_site, abc: img_abc, xyz: img_xyz });
70
+ }
71
+ return symmetrized_structure;
72
+ }
@@ -0,0 +1,412 @@
1
+ <script lang="ts">import { Icon } from '..';
2
+ import { format_num } from '../labels';
3
+ import { electro_neg_formula } from '../structure';
4
+ import { titles_as_tooltips } from 'svelte-zoo';
5
+ let { trajectory, current_step_idx, current_filename, current_file_path, file_size, file_object, is_open = false, onclose = () => { }, } = $props();
6
+ // Structured sidebar data for trajectory statistics
7
+ let sidebar_data = $derived.by(() => {
8
+ if (!trajectory)
9
+ return [];
10
+ const first_frame = trajectory.frames[0];
11
+ const current_frame = trajectory.frames[current_step_idx];
12
+ const sections = [];
13
+ // File Information Section
14
+ if (current_filename || file_size !== null || trajectory.metadata?.source_format ||
15
+ file_object) {
16
+ const file_info = [];
17
+ if (current_filename) {
18
+ file_info.push({
19
+ label: `Filename`,
20
+ value: current_filename,
21
+ tooltip: current_file_path || undefined,
22
+ });
23
+ }
24
+ if (file_size !== null && file_size !== undefined) {
25
+ const size_str = file_size > 1024 * 1024
26
+ ? `${format_num(file_size / (1024 * 1024), `.2~f`)} MB`
27
+ : `${format_num(file_size / 1024, `.2~f`)} KB`;
28
+ file_info.push({ label: `File Size`, value: size_str });
29
+ // Add file size in bytes for very large files
30
+ if (file_size > 1024 * 1024) {
31
+ const bytes_str = file_size.toLocaleString();
32
+ file_info.push({ label: `Size (bytes)`, value: bytes_str });
33
+ }
34
+ }
35
+ // Add file timestamps if available
36
+ if (file_object) {
37
+ file_info.push({
38
+ label: `Last Modified`,
39
+ value: file_object.lastModified.toLocaleString(),
40
+ tooltip: `File system last modified time`,
41
+ });
42
+ }
43
+ if (trajectory.metadata?.source_format) {
44
+ file_info.push({
45
+ label: `Format`,
46
+ value: String(trajectory.metadata.source_format),
47
+ });
48
+ }
49
+ // Add total atoms across all frames if it varies
50
+ const atom_counts = trajectory.frames.map((f) => f.structure.sites.length);
51
+ const min_atoms = Math.min(...atom_counts);
52
+ const max_atoms = Math.max(...atom_counts);
53
+ if (min_atoms !== max_atoms) {
54
+ file_info.push({
55
+ label: `Atoms Range`,
56
+ value: `${min_atoms} - ${max_atoms}`,
57
+ });
58
+ }
59
+ // Add creation/modification time if available from trajectory metadata
60
+ if (typeof trajectory?.metadata?.created_at === `string`) {
61
+ const date = new Date(trajectory.metadata.created_at);
62
+ file_info.push({
63
+ label: `Created (metadata)`,
64
+ value: date.toLocaleDateString(),
65
+ tooltip: `Creation time from file metadata`,
66
+ });
67
+ }
68
+ sections.push({ title: `File`, items: file_info });
69
+ }
70
+ // Structure Information Section
71
+ const structure_info = [];
72
+ structure_info.push({
73
+ label: `Atoms`,
74
+ value: `${current_frame.structure.sites.length}`,
75
+ });
76
+ // Add chemical formula
77
+ structure_info.push({
78
+ label: `Formula`,
79
+ value: String(electro_neg_formula(current_frame.structure)),
80
+ });
81
+ if (`lattice` in first_frame.structure) {
82
+ const { volume } = first_frame.structure.lattice;
83
+ structure_info.push({
84
+ label: `Volume`,
85
+ value: `${format_num(volume, `.3~s`)} ų`,
86
+ });
87
+ structure_info.push({
88
+ label: `Density`,
89
+ value: `${format_num(first_frame.structure.sites.length / volume, `.4~s`)} atoms/ų`,
90
+ });
91
+ }
92
+ sections.push({ title: `Structure`, items: structure_info });
93
+ // Unit Cell Section (if lattice exists)
94
+ if (`lattice` in first_frame.structure) {
95
+ const { a, b, c, alpha, beta, gamma } = first_frame.structure.lattice;
96
+ const cell_info = [
97
+ {
98
+ label: `Lengths`,
99
+ value: `${format_num(a, `.3~f`)}, ${format_num(b, `.3~f`)}, ${format_num(c, `.3~f`)} Å`,
100
+ },
101
+ {
102
+ label: `Angles`,
103
+ value: `${format_num(alpha, `.2~f`)}°, ${format_num(beta, `.2~f`)}°, ${format_num(gamma, `.2~f`)}°`,
104
+ },
105
+ ];
106
+ sections.push({ title: `Unit Cell`, items: cell_info });
107
+ }
108
+ // Trajectory Information Section
109
+ const traj_info = [
110
+ {
111
+ label: `Steps`,
112
+ value: `${trajectory.frames.length} (current: ${current_step_idx + 1})`,
113
+ },
114
+ ];
115
+ // Add trajectory duration if we have time metadata
116
+ const times = trajectory.frames
117
+ .map((f) => f.metadata?.time)
118
+ .filter((t) => typeof t === `number`);
119
+ if (times.length > 1) {
120
+ const duration = Math.max(...times) - Math.min(...times);
121
+ const time_unit = trajectory.metadata?.time_unit || `fs`;
122
+ traj_info.push({
123
+ label: `Duration`,
124
+ value: `${format_num(duration, `.3~s`)} ${time_unit}`,
125
+ });
126
+ }
127
+ // Add temperature range if available
128
+ const temperatures = trajectory.frames
129
+ .map((f) => f.metadata?.temperature)
130
+ .filter((t) => typeof t === `number`);
131
+ if (temperatures.length > 1) {
132
+ const min_temp = Math.min(...temperatures);
133
+ const max_temp = Math.max(...temperatures);
134
+ if (Math.abs(max_temp - min_temp) > 1) {
135
+ traj_info.push({
136
+ label: `Temperature`,
137
+ value: `${format_num(min_temp, `.2~f`)} - ${format_num(max_temp, `.2~f`)} K`,
138
+ });
139
+ }
140
+ }
141
+ else if (temperatures.length === 1) {
142
+ traj_info.push({
143
+ label: `Temperature`,
144
+ value: `${format_num(temperatures[0], `.2~f`)} K`,
145
+ });
146
+ }
147
+ // Add pressure range if available
148
+ const pressures = trajectory.frames
149
+ .map((f) => f.metadata?.pressure)
150
+ .filter((p) => typeof p === `number`);
151
+ if (pressures.length > 1) {
152
+ const min_pressure = Math.min(...pressures);
153
+ const max_pressure = Math.max(...pressures);
154
+ if (Math.abs(max_pressure - min_pressure) > 0.1) {
155
+ traj_info.push({
156
+ label: `Pressure`,
157
+ value: `${format_num(min_pressure, `.2~f`)} - ${format_num(max_pressure, `.2~f`)} GPa`,
158
+ });
159
+ }
160
+ }
161
+ else if (pressures.length === 1) {
162
+ traj_info.push({
163
+ label: `Pressure`,
164
+ value: `${format_num(pressures[0], `.2~f`)} GPa`,
165
+ });
166
+ }
167
+ sections.push({ title: `Trajectory`, items: traj_info });
168
+ // Energy Information Section (if available)
169
+ const energies = trajectory.frames
170
+ .map((f) => f.metadata?.energy)
171
+ .filter(Boolean);
172
+ if (energies.length > 1) {
173
+ const min_energy = Math.min(...energies);
174
+ const max_energy = Math.max(...energies);
175
+ const energy_span = max_energy - min_energy;
176
+ const current_energy = current_frame.metadata?.energy;
177
+ const energy_info = [];
178
+ if (current_energy !== undefined) {
179
+ energy_info.push({
180
+ label: `Current Energy`,
181
+ value: `${format_num(current_energy, `.3~s`)} eV`,
182
+ });
183
+ }
184
+ energy_info.push({
185
+ label: `Energy Range`,
186
+ value: `${format_num(min_energy, `.3~s`)} to ${format_num(max_energy, `.3~s`)} eV`,
187
+ });
188
+ energy_info.push({
189
+ label: `Energy Span`,
190
+ value: `${format_num(energy_span, `.3~s`)} eV`,
191
+ });
192
+ sections.push({ title: `Energy`, items: energy_info });
193
+ }
194
+ // Forces Information Section (if available)
195
+ const force_maxes = trajectory.frames
196
+ .map((f) => f.metadata?.force_max)
197
+ .filter((f) => typeof f === `number`);
198
+ if (force_maxes.length > 1) {
199
+ const min_force = Math.min(...force_maxes);
200
+ const max_force = Math.max(...force_maxes);
201
+ const current_force = current_frame.metadata?.force_max;
202
+ const force_info = [];
203
+ if (current_force !== undefined) {
204
+ force_info.push({
205
+ label: `Current Max Force`,
206
+ value: `${format_num(current_force, `.3~s`)} eV/Å`,
207
+ });
208
+ }
209
+ force_info.push({
210
+ label: `Force Range`,
211
+ value: `${format_num(min_force, `.3~s`)} - ${format_num(max_force, `.3~s`)} eV/Å`,
212
+ });
213
+ sections.push({ title: `Forces`, items: force_info });
214
+ }
215
+ // Volume Change Section (if available)
216
+ if (`lattice` in first_frame.structure && trajectory.frames.length > 1) {
217
+ const volumes = trajectory.frames
218
+ .map((frame) => `lattice` in frame.structure ? frame.structure.lattice.volume : null)
219
+ .filter((v) => v !== null);
220
+ if (volumes.length > 1) {
221
+ const vol_change = ((Math.max(...volumes) - Math.min(...volumes)) / Math.min(...volumes)) * 100;
222
+ if (Math.abs(vol_change) > 0.1) {
223
+ const dynamics_info = [
224
+ {
225
+ label: `Volume Change`,
226
+ value: `${format_num(vol_change, `.2~f`)}%`,
227
+ },
228
+ ];
229
+ sections.push({ title: `Dynamics`, items: dynamics_info });
230
+ }
231
+ }
232
+ }
233
+ return sections;
234
+ });
235
+ </script>
236
+
237
+ <aside class="info-sidebar" class:open={is_open}>
238
+ <header class="sidebar-header">
239
+ <h3>Trajectory Info</h3>
240
+ <button
241
+ onclick={onclose}
242
+ aria-label="Close info panel"
243
+ class="close-button"
244
+ type="button"
245
+ >
246
+ <Icon icon="Cross" />
247
+ </button>
248
+ </header>
249
+ <div class="sidebar-content">
250
+ {#each sidebar_data as section (section.title)}
251
+ <section>
252
+ <h4>{section.title}</h4>
253
+ {#each section.items as item (item.label)}
254
+ <div>
255
+ <span>{item.label}</span>
256
+ <span
257
+ title={item.tooltip}
258
+ use:titles_as_tooltips
259
+ >
260
+ {@html item.value}
261
+ </span>
262
+ </div>
263
+ {/each}
264
+ </section>
265
+ {/each}
266
+ </div>
267
+ </aside>
268
+
269
+ <style>
270
+ /* Info Sidebar Styles */
271
+ .info-sidebar {
272
+ position: absolute;
273
+ top: 0;
274
+ right: -320px;
275
+ width: 320px;
276
+ height: 100%; /* needed for scroll in sidebar */
277
+ background: var(--sidebar-bg, rgba(15, 23, 42, 0.96));
278
+ backdrop-filter: blur(8px);
279
+ border-left: var(--sidebar-border, 1px solid rgba(71, 85, 105, 0.3));
280
+ visibility: hidden;
281
+ opacity: 0;
282
+ transition:
283
+ right 0.3s cubic-bezier(0.4, 0, 0.2, 1),
284
+ visibility 0.3s cubic-bezier(0.4, 0, 0.2, 1),
285
+ opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
286
+ z-index: 10;
287
+ display: flex;
288
+ flex-direction: column;
289
+ overflow: hidden;
290
+ box-shadow: var(--sidebar-shadow, -4px 0 20px rgba(0, 0, 0, 0.15));
291
+ pointer-events: none;
292
+ }
293
+ .info-sidebar.open {
294
+ right: 0;
295
+ visibility: visible;
296
+ opacity: 1;
297
+ pointer-events: auto;
298
+ }
299
+ .sidebar-header {
300
+ display: flex;
301
+ align-items: center;
302
+ justify-content: space-between;
303
+ padding: 0.75rem;
304
+ border-bottom: var(--sidebar-header-border, 1px solid rgba(71, 85, 105, 0.2));
305
+ background: var(--sidebar-header-bg, rgba(30, 41, 59, 0.8));
306
+ }
307
+ .sidebar-header h3 {
308
+ margin: 0;
309
+ font-size: 1rem;
310
+ font-weight: 600;
311
+ color: var(--sidebar-title-color, #f1f5f9);
312
+ }
313
+ .close-button {
314
+ min-width: 24px;
315
+ height: 24px;
316
+ padding: 0;
317
+ background: transparent;
318
+ border: none;
319
+ color: var(--sidebar-close-color, #94a3b8);
320
+ cursor: pointer;
321
+ border-radius: 4px;
322
+ transition: all 0.2s ease;
323
+ }
324
+ .close-button:hover {
325
+ background: var(--sidebar-close-hover-bg, rgba(71, 85, 105, 0.3));
326
+ color: var(--sidebar-close-hover-color, #f1f5f9);
327
+ }
328
+ .sidebar-content {
329
+ flex: 1;
330
+ overflow-y: auto;
331
+ overflow-x: hidden;
332
+ padding: 0.375rem;
333
+ scrollbar-width: thin;
334
+ scrollbar-color: var(--sidebar-scrollbar-thumb, rgba(71, 85, 105, 0.5)) transparent;
335
+ }
336
+ .sidebar-content::-webkit-scrollbar {
337
+ width: 6px;
338
+ }
339
+ .sidebar-content::-webkit-scrollbar-track {
340
+ background: transparent;
341
+ }
342
+ .sidebar-content::-webkit-scrollbar-thumb {
343
+ background: var(--sidebar-scrollbar-thumb, rgba(71, 85, 105, 0.5));
344
+ border-radius: 3px;
345
+ }
346
+ .sidebar-content::-webkit-scrollbar-thumb:hover {
347
+ background: var(--sidebar-scrollbar-thumb-hover, rgba(71, 85, 105, 0.7));
348
+ }
349
+ .sidebar-content section {
350
+ margin-bottom: 1rem;
351
+ &:last-child {
352
+ margin-bottom: 0.25rem;
353
+ }
354
+ h4 {
355
+ margin: 0 0 0.5rem 0;
356
+ font-size: 0.875rem;
357
+ font-weight: 600;
358
+ color: var(--sidebar-section-title-color, #cbd5e1);
359
+ text-transform: uppercase;
360
+ letter-spacing: 0.05em;
361
+ padding: 0 0.375rem;
362
+ }
363
+ div {
364
+ display: flex;
365
+ align-items: center;
366
+ justify-content: space-between;
367
+ gap: 0.75rem;
368
+ padding: 1pt 4pt;
369
+ margin-bottom: 0.25rem;
370
+ background: var(--sidebar-item-bg, rgba(30, 41, 59, 0.4));
371
+ border-radius: 4px;
372
+ border: var(--sidebar-item-border, 1px solid rgba(71, 85, 105, 0.2));
373
+ transition: all 0.2s ease;
374
+ &:hover {
375
+ background: var(--sidebar-item-hover-bg, rgba(30, 41, 59, 0.6));
376
+ border-color: var(--sidebar-item-hover-border, rgba(71, 85, 105, 0.4));
377
+ }
378
+ &:last-child {
379
+ margin-bottom: 0;
380
+ }
381
+ span:first-child {
382
+ font-size: 0.8rem;
383
+ color: var(--sidebar-label-color, #94a3b8);
384
+ font-weight: 500;
385
+ min-width: 0;
386
+ flex: 1;
387
+ overflow: hidden;
388
+ text-overflow: ellipsis;
389
+ white-space: nowrap;
390
+ }
391
+ span:last-child {
392
+ font-size: 0.75rem;
393
+ color: var(--sidebar-value-color, #f1f5f9);
394
+ font-weight: 500;
395
+ text-align: right;
396
+ font-family: inherit;
397
+ flex-shrink: 0;
398
+ overflow: hidden;
399
+ text-overflow: ellipsis;
400
+ white-space: nowrap;
401
+ }
402
+ }
403
+ }
404
+
405
+ /* Responsive design */
406
+ @media (max-width: 768px) {
407
+ .info-sidebar {
408
+ width: 100%;
409
+ max-width: 320px;
410
+ }
411
+ }
412
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { Trajectory } from './index';
2
+ interface Props {
3
+ trajectory: Trajectory;
4
+ current_step_idx: number;
5
+ current_filename?: string | null;
6
+ current_file_path?: string | null;
7
+ file_size?: number | null;
8
+ file_object?: File | null;
9
+ is_open?: boolean;
10
+ onclose?: () => void;
11
+ }
12
+ declare const Sidebar: import("svelte").Component<Props, {}, "">;
13
+ type Sidebar = ReturnType<typeof Sidebar>;
14
+ export default Sidebar;