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.
- package/dist/BohrAtom.svelte +105 -0
- package/dist/BohrAtom.svelte.d.ts +21 -0
- package/dist/ControlPanel.svelte +158 -0
- package/dist/ControlPanel.svelte.d.ts +18 -0
- package/dist/Icon.svelte +23 -0
- package/dist/Icon.svelte.d.ts +8 -0
- package/dist/InfoCard.svelte +79 -0
- package/dist/InfoCard.svelte.d.ts +23 -0
- package/dist/Nucleus.svelte +64 -0
- package/dist/Nucleus.svelte.d.ts +16 -0
- package/dist/Spinner.svelte +44 -0
- package/dist/Spinner.svelte.d.ts +7 -0
- package/dist/api.d.ts +6 -0
- package/dist/api.js +30 -0
- package/dist/colors/alloy-colors.json +111 -0
- package/dist/colors/dark-mode-colors.json +111 -0
- package/dist/colors/index.d.ts +26 -0
- package/dist/colors/index.js +72 -0
- package/dist/colors/jmol-colors.json +111 -0
- package/dist/colors/muted-colors.json +111 -0
- package/dist/colors/pastel-colors.json +111 -0
- package/dist/colors/vesta-colors.json +111 -0
- package/dist/composition/BarChart.svelte +260 -0
- package/dist/composition/BarChart.svelte.d.ts +33 -0
- package/dist/composition/BubbleChart.svelte +166 -0
- package/dist/composition/BubbleChart.svelte.d.ts +30 -0
- package/dist/composition/Composition.svelte +73 -0
- package/dist/composition/Composition.svelte.d.ts +27 -0
- package/dist/composition/PieChart.svelte +236 -0
- package/dist/composition/PieChart.svelte.d.ts +36 -0
- package/dist/composition/index.d.ts +5 -0
- package/dist/composition/index.js +5 -0
- package/dist/composition/parse.d.ts +14 -0
- package/dist/composition/parse.js +307 -0
- package/dist/element/ElementHeading.svelte +21 -0
- package/dist/element/ElementHeading.svelte.d.ts +8 -0
- package/dist/element/ElementPhoto.svelte +56 -0
- package/dist/element/ElementPhoto.svelte.d.ts +9 -0
- package/dist/element/ElementStats.svelte +73 -0
- package/dist/element/ElementStats.svelte.d.ts +8 -0
- package/dist/element/ElementTile.svelte +449 -0
- package/dist/element/ElementTile.svelte.d.ts +25 -0
- package/dist/element/data.d.ts +4958 -0
- package/dist/element/data.js +5628 -0
- package/dist/element/index.d.ts +4 -0
- package/dist/element/index.js +4 -0
- package/dist/icons.d.ts +435 -0
- package/dist/icons.js +435 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +43 -0
- package/dist/io/decompress.d.ts +16 -0
- package/dist/io/decompress.js +78 -0
- package/dist/io/export.d.ts +9 -0
- package/dist/io/export.js +205 -0
- package/dist/io/parse.d.ts +53 -0
- package/dist/io/parse.js +747 -0
- package/dist/labels.d.ts +31 -0
- package/dist/labels.js +209 -0
- package/dist/material/MaterialCard.svelte +135 -0
- package/dist/material/MaterialCard.svelte.d.ts +10 -0
- package/dist/material/SymmetryCard.svelte +23 -0
- package/dist/material/SymmetryCard.svelte.d.ts +9 -0
- package/dist/material/index.d.ts +2 -0
- package/dist/material/index.js +2 -0
- package/dist/math.d.ts +24 -0
- package/dist/math.js +216 -0
- package/dist/periodic-table/PeriodicTable.svelte +284 -0
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +50 -0
- package/dist/periodic-table/PropertySelect.svelte +20 -0
- package/dist/periodic-table/PropertySelect.svelte.d.ts +13 -0
- package/dist/periodic-table/TableInset.svelte +18 -0
- package/dist/periodic-table/TableInset.svelte.d.ts +9 -0
- package/dist/periodic-table/index.d.ts +9 -0
- package/dist/periodic-table/index.js +3 -0
- package/dist/plot/ColorBar.svelte +414 -0
- package/dist/plot/ColorBar.svelte.d.ts +22 -0
- package/dist/plot/ColorScaleSelect.svelte +31 -0
- package/dist/plot/ColorScaleSelect.svelte.d.ts +15 -0
- package/dist/plot/ElementScatter.svelte +38 -0
- package/dist/plot/ElementScatter.svelte.d.ts +14 -0
- package/dist/plot/Line.svelte +42 -0
- package/dist/plot/Line.svelte.d.ts +15 -0
- package/dist/plot/PlotLegend.svelte +206 -0
- package/dist/plot/PlotLegend.svelte.d.ts +18 -0
- package/dist/plot/ScatterPlot.svelte +1753 -0
- package/dist/plot/ScatterPlot.svelte.d.ts +114 -0
- package/dist/plot/ScatterPlotControls.svelte +505 -0
- package/dist/plot/ScatterPlotControls.svelte.d.ts +33 -0
- package/dist/plot/ScatterPoint.svelte +72 -0
- package/dist/plot/ScatterPoint.svelte.d.ts +17 -0
- package/dist/plot/index.d.ts +168 -0
- package/dist/plot/index.js +46 -0
- package/dist/state.svelte.d.ts +12 -0
- package/dist/state.svelte.js +11 -0
- package/dist/structure/Bond.svelte +68 -0
- package/dist/structure/Bond.svelte.d.ts +13 -0
- package/dist/structure/Lattice.svelte +115 -0
- package/dist/structure/Lattice.svelte.d.ts +15 -0
- package/dist/structure/Structure.svelte +298 -0
- package/dist/structure/Structure.svelte.d.ts +28 -0
- package/dist/structure/StructureCard.svelte +26 -0
- package/dist/structure/StructureCard.svelte.d.ts +9 -0
- package/dist/structure/StructureControls.svelte +383 -0
- package/dist/structure/StructureControls.svelte.d.ts +23 -0
- package/dist/structure/StructureLegend.svelte +130 -0
- package/dist/structure/StructureLegend.svelte.d.ts +17 -0
- package/dist/structure/StructureScene.svelte +331 -0
- package/dist/structure/StructureScene.svelte.d.ts +47 -0
- package/dist/structure/bonding.d.ts +16 -0
- package/dist/structure/bonding.js +150 -0
- package/dist/structure/index.d.ts +98 -0
- package/dist/structure/index.js +114 -0
- package/dist/structure/pbc.d.ts +6 -0
- package/dist/structure/pbc.js +72 -0
- package/dist/trajectory/Sidebar.svelte +412 -0
- package/dist/trajectory/Sidebar.svelte.d.ts +14 -0
- package/dist/trajectory/Trajectory.svelte +1084 -0
- package/dist/trajectory/Trajectory.svelte.d.ts +49 -0
- package/dist/trajectory/TrajectoryError.svelte +120 -0
- package/dist/trajectory/TrajectoryError.svelte.d.ts +12 -0
- package/dist/trajectory/extract.d.ts +5 -0
- package/dist/trajectory/extract.js +157 -0
- package/dist/trajectory/index.d.ts +16 -0
- package/dist/trajectory/index.js +49 -0
- package/dist/trajectory/parse.d.ts +13 -0
- package/dist/trajectory/parse.js +1093 -0
- package/dist/trajectory/plotting.d.ts +12 -0
- package/dist/trajectory/plotting.js +148 -0
- package/license +21 -0
- package/package.json +131 -0
- 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;
|