matterviz 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MillerIndexInput.svelte +5 -5
- package/dist/api/optimade.js +3 -3
- package/dist/brillouin/BrillouinZone.svelte +5 -2
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +1 -3
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +1 -1
- package/dist/brillouin/BrillouinZoneScene.svelte +5 -5
- package/dist/brillouin/compute.js +21 -21
- package/dist/brillouin/index.d.ts +1 -1
- package/dist/brillouin/index.js +0 -1
- package/dist/brillouin/types.d.ts +8 -13
- package/dist/chempot-diagram/ChemPotDiagram.svelte +3 -3
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +3 -4
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +33 -34
- package/dist/chempot-diagram/compute.js +1 -7
- package/dist/chempot-diagram/temperature.d.ts +1 -1
- package/dist/chempot-diagram/temperature.js +1 -3
- package/dist/chempot-diagram/types.d.ts +4 -9
- package/dist/colors/index.js +5 -5
- package/dist/composition/Composition.svelte +2 -1
- package/dist/composition/Formula.svelte +7 -4
- package/dist/composition/FormulaFilter.svelte +1 -3
- package/dist/composition/format.js +4 -4
- package/dist/composition/parse.d.ts +2 -1
- package/dist/composition/parse.js +61 -46
- package/dist/convex-hull/ConvexHull2D.svelte +62 -51
- package/dist/convex-hull/ConvexHull3D.svelte +101 -90
- package/dist/convex-hull/ConvexHull4D.svelte +70 -58
- package/dist/convex-hull/ConvexHullControls.svelte +24 -35
- package/dist/convex-hull/ConvexHullInfoPane.svelte +8 -5
- package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +2 -0
- package/dist/convex-hull/ConvexHullStats.svelte +9 -2
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +2 -0
- package/dist/convex-hull/GasPressureControls.svelte +7 -7
- package/dist/convex-hull/StructurePopup.svelte +65 -30
- package/dist/convex-hull/StructurePopup.svelte.d.ts +6 -6
- package/dist/convex-hull/TemperatureSlider.svelte +8 -5
- package/dist/convex-hull/barycentric-coords.d.ts +2 -2
- package/dist/convex-hull/barycentric-coords.js +2 -2
- package/dist/convex-hull/gas-thermodynamics.js +2 -4
- package/dist/convex-hull/helpers.d.ts +13 -2
- package/dist/convex-hull/helpers.js +37 -16
- package/dist/convex-hull/index.d.ts +1 -0
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +2 -1
- package/dist/convex-hull/thermodynamics.js +7 -7
- package/dist/convex-hull/types.d.ts +15 -15
- package/dist/effects.svelte.d.ts +12 -0
- package/dist/effects.svelte.js +37 -0
- package/dist/element/BohrAtom.svelte +4 -4
- package/dist/element/data.json.gz.d.ts +3 -1
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +0 -1
- package/dist/fermi-surface/FermiSurface.svelte +4 -4
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte +15 -19
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +8 -6
- package/dist/fermi-surface/compute.js +2 -2
- package/dist/fermi-surface/export.js +13 -26
- package/dist/fermi-surface/parse.js +8 -12
- package/dist/fermi-surface/types.d.ts +2 -5
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +21 -3
- package/dist/heatmap-matrix/index.js +6 -6
- package/dist/io/decompress.d.ts +2 -1
- package/dist/io/decompress.js +1 -1
- package/dist/io/export.js +1 -1
- package/dist/io/index.d.ts +1 -1
- package/dist/io/index.js +0 -1
- package/dist/io/url-drop.js +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +11 -25
- package/dist/isosurface/slice.js +1 -1
- package/dist/isosurface/types.js +12 -12
- package/dist/labels.d.ts +1 -1
- package/dist/labels.js +14 -11
- package/dist/layout/InfoTag.svelte +6 -4
- package/dist/layout/PropertyFilter.svelte +4 -2
- package/dist/layout/json-tree/JsonTree.svelte +22 -14
- package/dist/layout/json-tree/JsonValue.svelte +2 -2
- package/dist/layout/json-tree/types.d.ts +3 -2
- package/dist/layout/json-tree/types.js +0 -1
- package/dist/layout/json-tree/utils.d.ts +4 -4
- package/dist/layout/json-tree/utils.js +12 -20
- package/dist/marching-cubes.js +13 -15
- package/dist/math.d.ts +11 -1
- package/dist/math.js +15 -6
- package/dist/overlays/DragControlTab.svelte +98 -0
- package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
- package/dist/overlays/DraggablePane.svelte +7 -84
- package/dist/overlays/index.d.ts +1 -0
- package/dist/overlays/index.js +1 -0
- package/dist/periodic-table/PeriodicTable.svelte +11 -11
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +4 -2
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramControls.svelte +4 -9
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +2 -10
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +2 -3
- package/dist/phase-diagram/TdbInfoPanel.svelte +3 -3
- package/dist/phase-diagram/build-diagram.js +11 -18
- package/dist/phase-diagram/diagram-input.d.ts +5 -9
- package/dist/phase-diagram/index.d.ts +2 -2
- package/dist/phase-diagram/index.js +0 -2
- package/dist/phase-diagram/parse.d.ts +2 -2
- package/dist/phase-diagram/parse.js +6 -10
- package/dist/phase-diagram/svg-to-diagram.js +15 -15
- package/dist/phase-diagram/types.d.ts +5 -11
- package/dist/phase-diagram/utils.d.ts +2 -2
- package/dist/phase-diagram/utils.js +9 -11
- package/dist/plot/BarPlot.svelte +162 -314
- package/dist/plot/BarPlot.svelte.d.ts +5 -4
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/BinnedScatterPlot.svelte +1114 -0
- package/dist/plot/BinnedScatterPlot.svelte.d.ts +66 -0
- package/dist/plot/ColorBar.svelte +19 -17
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/FillArea.svelte +2 -4
- package/dist/plot/FillArea.svelte.d.ts +1 -1
- package/dist/plot/Histogram.svelte +167 -281
- package/dist/plot/Histogram.svelte.d.ts +1 -1
- package/dist/plot/HistogramControls.svelte.d.ts +1 -1
- package/dist/plot/InteractiveAxisLabel.svelte +5 -3
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/PlotAxis.svelte +169 -0
- package/dist/plot/PlotAxis.svelte.d.ts +24 -0
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/ReferenceLine3D.svelte +53 -51
- package/dist/plot/ReferencePlane.svelte +39 -42
- package/dist/plot/ScatterPlot.svelte +300 -367
- package/dist/plot/ScatterPlot.svelte.d.ts +8 -5
- package/dist/plot/ScatterPlot3D.svelte +33 -6
- package/dist/plot/ScatterPlot3D.svelte.d.ts +3 -2
- package/dist/plot/ScatterPlot3DControls.svelte +9 -9
- package/dist/plot/ScatterPlotControls.svelte +3 -4
- package/dist/plot/ScatterPoint.svelte +18 -27
- package/dist/plot/ScatterPoint.svelte.d.ts +4 -3
- package/dist/plot/Surface3D.svelte +4 -7
- package/dist/plot/ZeroLines.svelte +2 -1
- package/dist/plot/ZeroLines.svelte.d.ts +2 -1
- package/dist/plot/ZoomRect.svelte +2 -2
- package/dist/plot/ZoomRect.svelte.d.ts +3 -3
- package/dist/plot/adaptive-density.d.ts +69 -0
- package/dist/plot/adaptive-density.js +191 -0
- package/dist/plot/auto-place.d.ts +43 -0
- package/dist/plot/auto-place.js +122 -0
- package/dist/plot/axis-utils.js +3 -5
- package/dist/plot/binned-scatter-types.d.ts +59 -0
- package/dist/plot/binned-scatter-types.js +1 -0
- package/dist/plot/data-cleaning.js +1 -1
- package/dist/plot/data-transform.js +1 -1
- package/dist/plot/fill-utils.d.ts +4 -9
- package/dist/plot/fill-utils.js +29 -44
- package/dist/plot/index.d.ts +4 -0
- package/dist/plot/index.js +2 -0
- package/dist/plot/interactions.d.ts +4 -4
- package/dist/plot/interactions.js +4 -3
- package/dist/plot/layout.d.ts +20 -2
- package/dist/plot/layout.js +59 -16
- package/dist/plot/reference-line.d.ts +1 -1
- package/dist/plot/reference-line.js +9 -11
- package/dist/plot/scales.d.ts +1 -1
- package/dist/plot/scales.js +20 -23
- package/dist/plot/types.d.ts +30 -58
- package/dist/plot/types.js +2 -6
- package/dist/plot/utils/label-placement.d.ts +24 -3
- package/dist/plot/utils/label-placement.js +82 -12
- package/dist/plot/utils/series-visibility.d.ts +8 -2
- package/dist/plot/utils/series-visibility.js +23 -5
- package/dist/rdf/RdfPlot.svelte +5 -5
- package/dist/rdf/calc-rdf.js +3 -3
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.js +2 -0
- package/dist/spectral/Bands.svelte +1 -1
- package/dist/spectral/BandsAndDos.svelte +22 -16
- package/dist/spectral/BrillouinBandsDos.svelte +20 -16
- package/dist/spectral/Dos.svelte +1 -1
- package/dist/spectral/helpers.d.ts +4 -2
- package/dist/spectral/helpers.js +44 -35
- package/dist/spectral/index.d.ts +1 -1
- package/dist/spectral/index.js +0 -1
- package/dist/structure/AtomLegend.svelte +23 -6
- package/dist/structure/AtomLegend.svelte.d.ts +1 -0
- package/dist/structure/CanvasTooltip.svelte +9 -9
- package/dist/structure/CanvasTooltip.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +14 -16
- package/dist/structure/Structure.svelte +317 -68
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureControls.svelte +20 -45
- package/dist/structure/StructureExportPane.svelte +2 -1
- package/dist/structure/StructureInfoPane.svelte +10 -8
- package/dist/structure/StructureScene.svelte +527 -177
- package/dist/structure/StructureScene.svelte.d.ts +5 -2
- package/dist/structure/atom-properties.js +4 -4
- package/dist/structure/bond-order-perception.js +115 -98
- package/dist/structure/bonding.d.ts +27 -1
- package/dist/structure/bonding.js +187 -16
- package/dist/structure/export.js +1 -1
- package/dist/structure/index.d.ts +3 -2
- package/dist/structure/index.js +0 -2
- package/dist/structure/parse.js +88 -59
- package/dist/symmetry/WyckoffTable.svelte +7 -0
- package/dist/symmetry/index.js +13 -14
- package/dist/table/HeatmapTable.svelte +45 -66
- package/dist/table/HeatmapTable.svelte.d.ts +1 -1
- package/dist/table/ToggleMenu.svelte +19 -10
- package/dist/theme/themes.mjs +12 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +0 -1
- package/dist/trajectory/Trajectory.svelte +43 -15
- package/dist/trajectory/TrajectoryInfoPane.svelte +2 -2
- package/dist/trajectory/extract.js +1 -1
- package/dist/trajectory/frame-reader.js +4 -4
- package/dist/trajectory/helpers.d.ts +5 -4
- package/dist/trajectory/helpers.js +9 -17
- package/dist/trajectory/index.d.ts +2 -2
- package/dist/trajectory/index.js +2 -2
- package/dist/trajectory/parse/ase.js +4 -4
- package/dist/trajectory/parse/hdf5.js +1 -1
- package/dist/trajectory/parse/index.js +2 -3
- package/dist/trajectory/parse/lammps.js +1 -1
- package/dist/trajectory/parse/vasp.js +1 -1
- package/dist/trajectory/plotting.d.ts +1 -1
- package/dist/trajectory/plotting.js +38 -38
- package/dist/trajectory/types.d.ts +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +9 -0
- package/dist/xrd/calc-xrd.js +3 -4
- package/dist/xrd/parse.js +1 -1
- package/package.json +42 -22
package/dist/symmetry/index.js
CHANGED
|
@@ -23,6 +23,17 @@ export const default_sym_settings = {
|
|
|
23
23
|
};
|
|
24
24
|
let initialized = false;
|
|
25
25
|
const OCCUPANCY_EPS = 1e-8;
|
|
26
|
+
const to_unit = (value) => value - Math.floor(value);
|
|
27
|
+
const near_zero = (value) => Math.min(value, 1 - value);
|
|
28
|
+
const near_half = (value) => Math.abs(value - 0.5);
|
|
29
|
+
const symmetry_position_key = (pos) => pos.map((coord) => to_unit(coord).toFixed(8)).join(`,`);
|
|
30
|
+
const periodic_distance = (pos1, pos2) => Math.sqrt(pos1.reduce((sum, coord, idx) => {
|
|
31
|
+
// Wrap delta into [-0.5, 0.5) using safe modulo
|
|
32
|
+
const delta = coord - pos2[idx];
|
|
33
|
+
const wrapped = ((((delta + 0.5) % 1) + 1) % 1) - 0.5;
|
|
34
|
+
const distance = Math.abs(wrapped);
|
|
35
|
+
return sum + distance * distance;
|
|
36
|
+
}, 0));
|
|
26
37
|
export async function ensure_moyo_wasm_ready(wasm_url) {
|
|
27
38
|
if (initialized)
|
|
28
39
|
return;
|
|
@@ -117,9 +128,6 @@ export async function analyze_structure_symmetry(struct_or_mol, settings) {
|
|
|
117
128
|
}
|
|
118
129
|
// Helper function to score coordinate simplicity for Wyckoff table
|
|
119
130
|
export function simplicity_score(vec) {
|
|
120
|
-
const to_unit = (v) => v - Math.floor(v);
|
|
121
|
-
const near_zero = (v) => Math.min(v, 1 - v);
|
|
122
|
-
const near_half = (v) => Math.abs(v - 0.5);
|
|
123
131
|
const [ax, ay, az] = vec?.map(to_unit) ?? [];
|
|
124
132
|
return (near_zero(ax) +
|
|
125
133
|
near_zero(ay) +
|
|
@@ -180,8 +188,6 @@ export function wyckoff_positions_from_moyo(sym_data) {
|
|
|
180
188
|
// Apply symmetry operations to find all equivalent positions for a given fractional coordinate
|
|
181
189
|
export function apply_symmetry_operations(position, operations, _tolerance = 1e-6) {
|
|
182
190
|
const seen = new Set();
|
|
183
|
-
const wrap = (coord) => coord - Math.floor(coord);
|
|
184
|
-
const key = (pos) => pos.map((coord) => wrap(coord).toFixed(8)).join(`,`);
|
|
185
191
|
return operations
|
|
186
192
|
.map(({ rotation, translation }) => {
|
|
187
193
|
// Apply 3x3 rotation matrix and translation: new_pos = R * position + t
|
|
@@ -189,10 +195,10 @@ export function apply_symmetry_operations(position, operations, _tolerance = 1e-
|
|
|
189
195
|
rotation[dim * 3 + 1] * position[1] +
|
|
190
196
|
rotation[dim * 3 + 2] * position[2] +
|
|
191
197
|
translation[dim]);
|
|
192
|
-
return new_pos.map(
|
|
198
|
+
return new_pos.map(to_unit);
|
|
193
199
|
})
|
|
194
200
|
.filter((pos) => {
|
|
195
|
-
const pos_key =
|
|
201
|
+
const pos_key = symmetry_position_key(pos);
|
|
196
202
|
if (seen.has(pos_key))
|
|
197
203
|
return false;
|
|
198
204
|
seen.add(pos_key);
|
|
@@ -204,13 +210,6 @@ export function map_wyckoff_to_all_atoms(wyckoff_positions, displayed_structure,
|
|
|
204
210
|
if (!sym_data?.operations || !displayed_structure.sites || !orig_structure.sites) {
|
|
205
211
|
return wyckoff_positions;
|
|
206
212
|
}
|
|
207
|
-
const periodic_distance = (pos1, pos2) => Math.sqrt(pos1.reduce((sum, coord, idx) => {
|
|
208
|
-
// Wrap delta into [-0.5, 0.5) using safe modulo
|
|
209
|
-
const delta = coord - pos2[idx];
|
|
210
|
-
const wrapped = ((((delta + 0.5) % 1) + 1) % 1) - 0.5;
|
|
211
|
-
const d = Math.abs(wrapped);
|
|
212
|
-
return sum + d * d;
|
|
213
|
-
}, 0));
|
|
214
213
|
return wyckoff_positions.map((wyckoff_pos) => {
|
|
215
214
|
const indices = (wyckoff_pos.site_indices || [])
|
|
216
215
|
.filter((idx) => idx < orig_structure.sites.length)
|
|
@@ -28,6 +28,35 @@
|
|
|
28
28
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
29
29
|
import { SvelteMap } from 'svelte/reactivity'
|
|
30
30
|
|
|
31
|
+
// Helper to check if value is invalid (null, undefined, NaN)
|
|
32
|
+
const is_invalid = (val: unknown) =>
|
|
33
|
+
val == null || (typeof val === `number` && Number.isNaN(val))
|
|
34
|
+
|
|
35
|
+
const NUMERIC_WITH_ERROR_RE =
|
|
36
|
+
/^([-+−]?(?:\d+\.?\d*|\d*\.\d+)(?:[eE][-+−]?\d+)?)\s*(?:±|\+[-−]|\()/
|
|
37
|
+
|
|
38
|
+
const parse_numeric_string = (val: string): number | null => {
|
|
39
|
+
const numeric_str = val.match(NUMERIC_WITH_ERROR_RE)?.[1] ?? val
|
|
40
|
+
if (numeric_str.trim() === ``) return null
|
|
41
|
+
const num = Number(normalize_unicode_minus(numeric_str))
|
|
42
|
+
return isNaN(num) ? null : num
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get sort value from a cell (handles HTML data-sort-value and numbers with errors)
|
|
46
|
+
const get_sort_val = (val: CellVal): string | number => {
|
|
47
|
+
if (typeof val === `string`) {
|
|
48
|
+
// Check for HTML data-sort-value attribute first
|
|
49
|
+
const sort_attr_match = val.match(/data-sort-value="([^"]*)"/)
|
|
50
|
+
if (sort_attr_match) {
|
|
51
|
+
const num = Number(sort_attr_match[1])
|
|
52
|
+
return isNaN(num) ? sort_attr_match[1] : num
|
|
53
|
+
}
|
|
54
|
+
const num = parse_numeric_string(val)
|
|
55
|
+
if (num !== null) return num
|
|
56
|
+
}
|
|
57
|
+
return val as string | number
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
let {
|
|
32
61
|
data = $bindable([]),
|
|
33
62
|
columns = [],
|
|
@@ -433,36 +462,6 @@
|
|
|
433
462
|
|
|
434
463
|
if (!sort_state.column && multi_sort.length === 0) return filtered_data
|
|
435
464
|
|
|
436
|
-
// Helper to check if value is invalid (null, undefined, NaN)
|
|
437
|
-
const is_invalid = (val: unknown) =>
|
|
438
|
-
val == null || (typeof val === `number` && Number.isNaN(val))
|
|
439
|
-
|
|
440
|
-
// Get sort value from a cell (handles HTML data-sort-value and numbers with errors)
|
|
441
|
-
const get_sort_val = (val: CellVal): string | number => {
|
|
442
|
-
if (typeof val === `string`) {
|
|
443
|
-
// Check for HTML data-sort-value attribute first
|
|
444
|
-
const sort_attr_match = val.match(/data-sort-value="([^"]*)"/)
|
|
445
|
-
if (sort_attr_match) {
|
|
446
|
-
const num = Number(sort_attr_match[1])
|
|
447
|
-
return isNaN(num) ? sort_attr_match[1] : num
|
|
448
|
-
}
|
|
449
|
-
// Handle numbers with error notation: "1.23 ± 0.05" or "1.23 +- 0.05" or "1.23(5)"
|
|
450
|
-
// Extract the primary number before the ± or +- or (
|
|
451
|
-
// Supports: ± (U+00B1), ASCII +-, Unicode minus − (U+2212), with optional whitespace
|
|
452
|
-
const error_match = val.match(
|
|
453
|
-
/^([+-−]?\d+\.?\d*(?:[eE][+-−]?\d+)?)\s*(?:[±\u00B1]|[+][−-]|\()/,
|
|
454
|
-
)
|
|
455
|
-
if (error_match) {
|
|
456
|
-
const num = Number(error_match[1])
|
|
457
|
-
if (!isNaN(num)) return num
|
|
458
|
-
}
|
|
459
|
-
// Try parsing as a plain number (handles "1.23" strings)
|
|
460
|
-
const plain_num = Number(val)
|
|
461
|
-
if (!isNaN(plain_num) && val.trim() !== ``) return plain_num
|
|
462
|
-
}
|
|
463
|
-
return val as string | number
|
|
464
|
-
}
|
|
465
|
-
|
|
466
465
|
// Build sort criteria: multi_sort takes precedence, fallback to single sort
|
|
467
466
|
const sort_criteria = multi_sort.length > 0
|
|
468
467
|
? multi_sort
|
|
@@ -474,7 +473,7 @@
|
|
|
474
473
|
|
|
475
474
|
return [...filtered_data].sort((row1, row2) => {
|
|
476
475
|
for (const { column, ascending } of sort_criteria) {
|
|
477
|
-
const matched_col = ordered_columns.find((
|
|
476
|
+
const matched_col = ordered_columns.find((col) => get_col_id(col) === column)
|
|
478
477
|
if (!matched_col) continue
|
|
479
478
|
|
|
480
479
|
const col_id = get_col_id(matched_col)
|
|
@@ -548,7 +547,7 @@
|
|
|
548
547
|
) {
|
|
549
548
|
// Find the column using both label and group if provided
|
|
550
549
|
const col = ordered_columns.find(
|
|
551
|
-
(
|
|
550
|
+
(candidate_col) => candidate_col.label === column && candidate_col.group === group,
|
|
552
551
|
)
|
|
553
552
|
|
|
554
553
|
if (!col) return // Skip if column not found
|
|
@@ -558,7 +557,7 @@
|
|
|
558
557
|
|
|
559
558
|
// Shift+click for multi-column sort
|
|
560
559
|
if (event.shiftKey) {
|
|
561
|
-
const existing_idx = multi_sort.findIndex((
|
|
560
|
+
const existing_idx = multi_sort.findIndex((sort_entry) => sort_entry.column === col_id)
|
|
562
561
|
if (existing_idx >= 0) {
|
|
563
562
|
// Toggle direction or remove if clicked again
|
|
564
563
|
const existing = multi_sort[existing_idx]
|
|
@@ -567,8 +566,8 @@
|
|
|
567
566
|
multi_sort = multi_sort.filter((_, idx) => idx !== existing_idx)
|
|
568
567
|
} else {
|
|
569
568
|
// Toggle direction
|
|
570
|
-
multi_sort = multi_sort.map((
|
|
571
|
-
idx === existing_idx ? { ...
|
|
569
|
+
multi_sort = multi_sort.map((sort_entry, idx) =>
|
|
570
|
+
idx === existing_idx ? { ...sort_entry, ascending: !sort_entry.ascending } : sort_entry
|
|
572
571
|
)
|
|
573
572
|
}
|
|
574
573
|
} else {
|
|
@@ -622,27 +621,7 @@
|
|
|
622
621
|
// Extract numeric value from strings with uncertainty notation: "1.23 ± 0.05", "1.23 +- 0.05", "1.23(5)"
|
|
623
622
|
function parse_numeric_val(val: CellVal): number | null {
|
|
624
623
|
if (typeof val === `number`) return Number.isNaN(val) ? null : val
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
// Handle numbers with error notation: "1.23 ± 0.05" or "1.23 +- 0.05" or "1.23(5)"
|
|
628
|
-
// Supports: ± (U+00B1), ASCII +-, Unicode minus − (U+2212), with optional whitespace
|
|
629
|
-
// Note: [-+−] has hyphen first to avoid regex range interpretation
|
|
630
|
-
// Pattern allows leading decimals like .5 or -.5 via (?:\d+\.?\d*|\d*\.\d+)
|
|
631
|
-
const error_match = val.match(
|
|
632
|
-
/^([-+−]?(?:\d+\.?\d*|\d*\.\d+)(?:[eE][-+−]?\d+)?)\s*(?:±|\+[-−]|\()/,
|
|
633
|
-
)
|
|
634
|
-
if (error_match) {
|
|
635
|
-
// Normalize unicode minus (U+2212) to ASCII hyphen for Number()
|
|
636
|
-
const normalized = normalize_unicode_minus(error_match[1])
|
|
637
|
-
const num = Number(normalized)
|
|
638
|
-
if (!isNaN(num)) return num
|
|
639
|
-
}
|
|
640
|
-
// Try parsing as a plain number (handles "1.23" strings)
|
|
641
|
-
// Also normalize unicode minus for plain numbers
|
|
642
|
-
const normalized_val = normalize_unicode_minus(val)
|
|
643
|
-
const plain_num = Number(normalized_val)
|
|
644
|
-
if (!isNaN(plain_num) && val.trim() !== ``) return plain_num
|
|
645
|
-
return null
|
|
624
|
+
return typeof val === `string` ? parse_numeric_string(val) : null
|
|
646
625
|
}
|
|
647
626
|
|
|
648
627
|
// Memoize parsed column values to avoid O(N²) re-parsing in calc_color
|
|
@@ -704,7 +683,7 @@
|
|
|
704
683
|
const col_id = get_col_id(col)
|
|
705
684
|
|
|
706
685
|
// Check multi-sort first
|
|
707
|
-
const multi_idx = multi_sort.findIndex((
|
|
686
|
+
const multi_idx = multi_sort.findIndex((sort_entry) => sort_entry.column === col_id)
|
|
708
687
|
if (multi_idx >= 0) {
|
|
709
688
|
const arrow = multi_sort[multi_idx].ascending ? `↓` : `↑`
|
|
710
689
|
const badge = multi_sort.length > 1 ? `<sup>${multi_idx + 1}</sup>` : ``
|
|
@@ -736,7 +715,7 @@
|
|
|
736
715
|
// Row selection using WeakMap-based ID lookup instead of O(n) JSON.stringify comparison
|
|
737
716
|
function toggle_row_select(row: RowData) {
|
|
738
717
|
const row_id = get_row_id(row)
|
|
739
|
-
const idx = selected_rows.findIndex((
|
|
718
|
+
const idx = selected_rows.findIndex((selected_row) => get_row_id(selected_row) === row_id)
|
|
740
719
|
if (idx >= 0) {
|
|
741
720
|
selected_rows = selected_rows.filter((_, i) => i !== idx)
|
|
742
721
|
} else {
|
|
@@ -746,7 +725,7 @@
|
|
|
746
725
|
|
|
747
726
|
function is_row_selected(row: RowData): boolean {
|
|
748
727
|
const row_id = get_row_id(row)
|
|
749
|
-
return selected_rows.some((
|
|
728
|
+
return selected_rows.some((selected_row) => get_row_id(selected_row) === row_id)
|
|
750
729
|
}
|
|
751
730
|
|
|
752
731
|
// Select-all: checks if every row on the current page is selected
|
|
@@ -788,7 +767,7 @@
|
|
|
788
767
|
return quote(strip_html(String(val)))
|
|
789
768
|
})
|
|
790
769
|
)
|
|
791
|
-
return [headers.join(delimiter), ...rows.map((
|
|
770
|
+
return [headers.join(delimiter), ...rows.map((row) => row.join(delimiter))].join(`\n`)
|
|
792
771
|
}
|
|
793
772
|
|
|
794
773
|
function export_csv(filename = `table-export`) {
|
|
@@ -890,7 +869,7 @@
|
|
|
890
869
|
{/snippet}
|
|
891
870
|
|
|
892
871
|
<div
|
|
893
|
-
{@attach tooltip()}
|
|
872
|
+
{@attach tooltip({ allow_html: true })}
|
|
894
873
|
{...rest_props}
|
|
895
874
|
bind:this={container_el}
|
|
896
875
|
class="table-container {rest_props.class ?? ``}"
|
|
@@ -1155,13 +1134,13 @@
|
|
|
1155
1134
|
{#if !col.group}
|
|
1156
1135
|
<th class:sticky-col={col.sticky}></th>
|
|
1157
1136
|
{:else}
|
|
1158
|
-
{@const group_cols = visible_columns.filter((
|
|
1159
|
-
|
|
1137
|
+
{@const group_cols = visible_columns.filter((column) =>
|
|
1138
|
+
column.group === col.group
|
|
1160
1139
|
)}
|
|
1161
1140
|
<!-- Only render the group header once for each group by checking if this is the first column of this group -->
|
|
1162
|
-
{#if visible_columns.findIndex((
|
|
1163
|
-
visible_columns.findIndex((
|
|
1164
|
-
|
|
1141
|
+
{#if visible_columns.findIndex((column) => column.group === col.group) ===
|
|
1142
|
+
visible_columns.findIndex((column) =>
|
|
1143
|
+
column.group === col.group && column.label === col.label
|
|
1165
1144
|
)}
|
|
1166
1145
|
<th title={col.description} colspan={group_cols.length}>
|
|
1167
1146
|
{@html sanitize_html(col.group)}
|
|
@@ -44,6 +44,6 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
44
44
|
}]>;
|
|
45
45
|
footer?: Snippet;
|
|
46
46
|
};
|
|
47
|
-
declare const HeatmapTable: import("svelte").Component<$$ComponentProps, {}, "sort" | "data" | "show_controls" | "controls_open" | "show_heatmap" | "column_order" | "selected_rows" | "hidden_columns" | "
|
|
47
|
+
declare const HeatmapTable: import("svelte").Component<$$ComponentProps, {}, "sort" | "data" | "show_controls" | "loading" | "controls_open" | "show_heatmap" | "column_order" | "selected_rows" | "hidden_columns" | "heatmap_opacity">;
|
|
48
48
|
type HeatmapTable = ReturnType<typeof HeatmapTable>;
|
|
49
49
|
export default HeatmapTable;
|
|
@@ -24,24 +24,31 @@
|
|
|
24
24
|
const col_id = (col: Label) => col.key ?? col.label
|
|
25
25
|
|
|
26
26
|
// Snapshot default visibility when column set changes (new dataset).
|
|
27
|
-
// Compare by
|
|
28
|
-
//
|
|
29
|
-
let
|
|
30
|
-
let
|
|
27
|
+
// Compare by keys and visibility defaults. Internal updates opt out below.
|
|
28
|
+
// Signature state is non-reactive; default visibility is state so reset UI updates after snapshots.
|
|
29
|
+
let prev_default_signature = ``
|
|
30
|
+
let internal_default_signature: string | undefined
|
|
31
|
+
let default_visibility = $state<Record<string, boolean>>({})
|
|
32
|
+
const default_signature = () =>
|
|
33
|
+
columns.map((col) => `${col_id(col)}:${col.visible !== false}`).join(`\0`)
|
|
34
|
+
|
|
31
35
|
function snapshot_defaults() {
|
|
32
36
|
default_visibility = {}
|
|
33
37
|
for (const col of columns) {
|
|
34
38
|
default_visibility[col_id(col)] = col.visible !== false
|
|
35
39
|
}
|
|
36
|
-
|
|
40
|
+
prev_default_signature = default_signature()
|
|
37
41
|
}
|
|
38
42
|
snapshot_defaults()
|
|
39
43
|
|
|
40
44
|
$effect(() => {
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
|
|
45
|
+
const current_signature = default_signature()
|
|
46
|
+
if (current_signature === internal_default_signature) {
|
|
47
|
+
internal_default_signature = undefined
|
|
48
|
+
return
|
|
44
49
|
}
|
|
50
|
+
if (current_signature === prev_default_signature) return
|
|
51
|
+
snapshot_defaults()
|
|
45
52
|
})
|
|
46
53
|
|
|
47
54
|
// Check if a column's visibility differs from its default
|
|
@@ -55,6 +62,7 @@
|
|
|
55
62
|
for (const col of items) {
|
|
56
63
|
col.visible = default_visibility[col_id(col)] ?? true
|
|
57
64
|
}
|
|
65
|
+
internal_default_signature = default_signature()
|
|
58
66
|
columns = [...columns]
|
|
59
67
|
}
|
|
60
68
|
|
|
@@ -94,13 +102,14 @@
|
|
|
94
102
|
|
|
95
103
|
function toggle_section(name: string) {
|
|
96
104
|
collapsed_sections = collapsed_sections.includes(name)
|
|
97
|
-
? collapsed_sections.filter((
|
|
105
|
+
? collapsed_sections.filter((section) => section !== name)
|
|
98
106
|
: [...collapsed_sections, name]
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
function toggle_column_visibility(col: Label, event: Event) {
|
|
102
110
|
if (!(event.target instanceof HTMLInputElement)) return
|
|
103
111
|
col.visible = event.target.checked
|
|
112
|
+
internal_default_signature = default_signature()
|
|
104
113
|
columns = [...columns] // trigger reactivity on parent binding
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -140,7 +149,7 @@
|
|
|
140
149
|
<label
|
|
141
150
|
class="toggle-label"
|
|
142
151
|
class:disabled={col.disabled}
|
|
143
|
-
{@attach tooltip({ content: col.description })}
|
|
152
|
+
{@attach tooltip({ allow_html: true, content: sanitize_html(col.description ?? ``) })}
|
|
144
153
|
>
|
|
145
154
|
<input
|
|
146
155
|
type="checkbox"
|
package/dist/theme/themes.mjs
CHANGED
|
@@ -148,6 +148,18 @@ const themes = {
|
|
|
148
148
|
|
|
149
149
|
// Tooltips
|
|
150
150
|
'tooltip-bg': tooltip_bg(`243, 244, 246`, `0, 40, 60`),
|
|
151
|
+
'canvas-tooltip-bg': {
|
|
152
|
+
light: `rgba(226, 232, 240, 0.96)`,
|
|
153
|
+
dark: `rgba(15, 23, 42, 0.96)`,
|
|
154
|
+
white: `rgba(241, 245, 249, 0.98)`,
|
|
155
|
+
black: `rgba(20, 20, 20, 0.98)`,
|
|
156
|
+
},
|
|
157
|
+
'canvas-tooltip-text-color': {
|
|
158
|
+
light: `#0f172a`,
|
|
159
|
+
dark: `#f8fafc`,
|
|
160
|
+
white: `#0f172a`,
|
|
161
|
+
black: `#f8fafc`,
|
|
162
|
+
},
|
|
151
163
|
'tooltip-border': {
|
|
152
164
|
light: `1px solid rgba(0, 0, 0, 0.15)`,
|
|
153
165
|
dark: `1px solid rgba(255, 255, 255, 0.15)`,
|
package/dist/tooltip/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default as TooltipContent } from './TooltipContent.svelte';
|
|
2
|
-
export * from './types';
|
|
2
|
+
export type * from './types';
|
package/dist/tooltip/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { sanitize_html } from '../sanitize'
|
|
11
11
|
import { toggle_fullscreen } from '../layout'
|
|
12
12
|
import type { ControlsConfig, DataSeries, Orientation, Point } from '../plot'
|
|
13
|
+
import type { ScatterHandlerProps } from '../plot/types'
|
|
13
14
|
import { Histogram, ScatterPlot } from '../plot'
|
|
14
15
|
import { toggle_series_visibility } from '../plot/utils/series-visibility'
|
|
15
16
|
import { DEFAULTS } from '../settings'
|
|
@@ -229,6 +230,7 @@
|
|
|
229
230
|
|
|
230
231
|
// Current frame - load on demand for indexed trajectories
|
|
231
232
|
let current_frame = $state<TrajectoryFrame | null>(null)
|
|
233
|
+
let frame_load_request_id = 0
|
|
232
234
|
|
|
233
235
|
// Auto-play when trajectory changes (handles both props and file loading)
|
|
234
236
|
$effect(() => {
|
|
@@ -254,15 +256,25 @@
|
|
|
254
256
|
|
|
255
257
|
// Load frame on demand - works for both indexed files and external streaming
|
|
256
258
|
async function load_frame_on_demand(frame_idx: number) {
|
|
257
|
-
|
|
259
|
+
const load_trajectory = trajectory
|
|
260
|
+
const frame_loader = load_trajectory?.frame_loader
|
|
261
|
+
if (!load_trajectory || !frame_loader) return
|
|
262
|
+
|
|
263
|
+
const request_id = ++frame_load_request_id
|
|
264
|
+
const request_is_current = () =>
|
|
265
|
+
request_id === frame_load_request_id &&
|
|
266
|
+
trajectory === load_trajectory &&
|
|
267
|
+
current_step_idx === frame_idx
|
|
258
268
|
|
|
259
269
|
try {
|
|
260
|
-
const frame = await
|
|
270
|
+
const frame = await frame_loader.load_frame(
|
|
261
271
|
orig_data || ``, // Use original_data for indexed files, empty string for external streaming
|
|
262
272
|
frame_idx,
|
|
263
273
|
)
|
|
274
|
+
if (!request_is_current()) return
|
|
264
275
|
current_frame = frame
|
|
265
276
|
} catch (error) {
|
|
277
|
+
if (!request_is_current()) return
|
|
266
278
|
console.error(`Failed to load frame ${frame_idx}:`, error)
|
|
267
279
|
current_frame = null
|
|
268
280
|
on_error?.({
|
|
@@ -292,8 +304,10 @@
|
|
|
292
304
|
if (step_labels > 0) {
|
|
293
305
|
return scaleLinear().domain([0, total_frames - 1]).nice()
|
|
294
306
|
.ticks(Math.min(step_labels, total_frames))
|
|
295
|
-
.map((
|
|
296
|
-
.filter((
|
|
307
|
+
.map((tick) => Math.round(tick))
|
|
308
|
+
.filter((tick, idx, ticks) =>
|
|
309
|
+
tick >= 0 && tick < total_frames && ticks.indexOf(tick) === idx
|
|
310
|
+
)
|
|
297
311
|
}
|
|
298
312
|
if (step_labels < 0) {
|
|
299
313
|
const spacing = Math.abs(step_labels)
|
|
@@ -760,7 +774,8 @@
|
|
|
760
774
|
|
|
761
775
|
// Handle click outside to close dropdowns
|
|
762
776
|
function handle_click_outside(event: MouseEvent) {
|
|
763
|
-
const target = event.target
|
|
777
|
+
const target = event.target
|
|
778
|
+
if (!(target instanceof Element)) return
|
|
764
779
|
if (view_mode_dropdown_open) {
|
|
765
780
|
const dropdown_wrapper = target.closest(`.view-mode-dropdown-wrapper`)
|
|
766
781
|
// Don't close if clicking on dropdown wrapper (contains both button and menu)
|
|
@@ -773,10 +788,10 @@
|
|
|
773
788
|
if (!trajectory) return
|
|
774
789
|
|
|
775
790
|
// Don't handle shortcuts if user is typing in an input field (but allow if it's our step input and not focused)
|
|
776
|
-
const target = event.target
|
|
777
|
-
const is_step_input = target
|
|
778
|
-
const is_input_focused =
|
|
779
|
-
target
|
|
791
|
+
const target = event.target instanceof HTMLElement ? event.target : null
|
|
792
|
+
const is_step_input = target?.classList.contains(`step-input`) ?? false
|
|
793
|
+
const is_input_focused =
|
|
794
|
+
target?.tagName === `INPUT` || target?.tagName === `TEXTAREA`
|
|
780
795
|
|
|
781
796
|
// Skip if typing in an input that's not our step input
|
|
782
797
|
if (is_input_focused && !is_step_input) return
|
|
@@ -784,7 +799,7 @@
|
|
|
784
799
|
// If typing in step input, only handle certain navigation keys
|
|
785
800
|
if (is_step_input && is_input_focused) {
|
|
786
801
|
// Allow normal typing, but handle special navigation keys
|
|
787
|
-
if ([`Escape`, `Enter`].includes(event.key)) target
|
|
802
|
+
if ([`Escape`, `Enter`].includes(event.key)) target?.blur() // Remove focus from input
|
|
788
803
|
return
|
|
789
804
|
}
|
|
790
805
|
|
|
@@ -905,7 +920,7 @@
|
|
|
905
920
|
<button
|
|
906
921
|
class="filename"
|
|
907
922
|
title="Click to copy filename <code>{current_filename}</code>"
|
|
908
|
-
{@attach tooltip()}
|
|
923
|
+
{@attach tooltip({ allow_html: true })}
|
|
909
924
|
onclick={() => {
|
|
910
925
|
if (current_filename) {
|
|
911
926
|
navigator.clipboard.writeText(current_filename)
|
|
@@ -1170,14 +1185,19 @@
|
|
|
1170
1185
|
{...scatter_props}
|
|
1171
1186
|
legend={{
|
|
1172
1187
|
...scatter_props.legend ?? {},
|
|
1173
|
-
on_toggle: (series_idx) => {
|
|
1188
|
+
on_toggle: (series_idx: number) => {
|
|
1174
1189
|
handle_legend_toggle(series_idx)
|
|
1175
1190
|
scatter_props.legend?.on_toggle?.(series_idx)
|
|
1176
1191
|
},
|
|
1177
1192
|
}}
|
|
1178
1193
|
class="plot {scatter_props.class ?? ``}"
|
|
1179
1194
|
>
|
|
1180
|
-
{#snippet tooltip({
|
|
1195
|
+
{#snippet tooltip({
|
|
1196
|
+
x,
|
|
1197
|
+
y,
|
|
1198
|
+
metadata,
|
|
1199
|
+
label,
|
|
1200
|
+
}: ScatterHandlerProps)}
|
|
1181
1201
|
{@const formatted_y = typeof y === `number` ? format_num(y) : y}
|
|
1182
1202
|
Step: {Math.round(x)}<br />
|
|
1183
1203
|
{@html sanitize_html(metadata?.series_label || label || `Value`)}: {formatted_y}
|
|
@@ -1195,7 +1215,7 @@
|
|
|
1195
1215
|
mode={histogram_props.mode ?? `overlay`}
|
|
1196
1216
|
show_legend={histogram_props.show_legend ?? plot_series.length > 1}
|
|
1197
1217
|
legend={histogram_props.legend}
|
|
1198
|
-
on_series_toggle={(series_idx) => {
|
|
1218
|
+
on_series_toggle={(series_idx: number) => {
|
|
1199
1219
|
handle_legend_toggle(series_idx)
|
|
1200
1220
|
histogram_props.on_series_toggle?.(series_idx)
|
|
1201
1221
|
}}
|
|
@@ -1203,7 +1223,15 @@
|
|
|
1203
1223
|
class="plot {histogram_props.class ?? ``}"
|
|
1204
1224
|
--ctrl-btn-top="6ex"
|
|
1205
1225
|
>
|
|
1206
|
-
{#snippet tooltip({
|
|
1226
|
+
{#snippet tooltip({
|
|
1227
|
+
value,
|
|
1228
|
+
count,
|
|
1229
|
+
property,
|
|
1230
|
+
}: {
|
|
1231
|
+
value: number
|
|
1232
|
+
count: number
|
|
1233
|
+
property?: string
|
|
1234
|
+
})}
|
|
1207
1235
|
{#if property}<div><strong>{property}</strong></div>{/if}
|
|
1208
1236
|
<div>Value: {format_num(value)}</div>
|
|
1209
1237
|
<div>Count: {count}</div>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import DraggablePane from '../overlays/DraggablePane.svelte'
|
|
6
6
|
import { get_electro_neg_formula } from '../composition'
|
|
7
7
|
import { SETTINGS_CONFIG } from '../settings'
|
|
8
|
-
import {
|
|
8
|
+
import type { AnyStructure } from '../structure'
|
|
9
9
|
import type { ComponentProps } from 'svelte'
|
|
10
10
|
import type { TrajectoryType } from './index'
|
|
11
11
|
|
|
@@ -260,7 +260,7 @@
|
|
|
260
260
|
{ structure },
|
|
261
261
|
) => (`lattice` in structure && structure.lattice?.volume))
|
|
262
262
|
.filter(is_valid_number)
|
|
263
|
-
.filter((
|
|
263
|
+
.filter((volume) => volume > 0)
|
|
264
264
|
|
|
265
265
|
if (volumes.length > 1) {
|
|
266
266
|
const vol_change = (Math.max(...volumes) - Math.min(...volumes)) /
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Data extraction functions for trajectory analysis and plotting
|
|
2
|
-
import { get_density } from '../structure';
|
|
2
|
+
import { get_density } from '../structure/index';
|
|
3
3
|
// Common data extractor that extracts energy and structural properties
|
|
4
4
|
export const energy_data_extractor = (frame) => {
|
|
5
5
|
const data = {
|
|
@@ -104,7 +104,7 @@ export class TrajFrameReader {
|
|
|
104
104
|
return this.load_ase_frame(data, frame_number);
|
|
105
105
|
}
|
|
106
106
|
async extract_plot_metadata(data, options, on_progress) {
|
|
107
|
-
const { sample_rate = 1, properties } = options
|
|
107
|
+
const { sample_rate = 1, properties } = options ?? {};
|
|
108
108
|
const metadata_list = [];
|
|
109
109
|
const total_frames = await this.get_total_frames(data);
|
|
110
110
|
if (this.format === `xyz`) {
|
|
@@ -240,11 +240,11 @@ export class TrajFrameReader {
|
|
|
240
240
|
const frame_offset = Number(view.getBigInt64(offsets_pos + frame_number * 8, true));
|
|
241
241
|
const json_length = Number(view.getBigInt64(frame_offset, true));
|
|
242
242
|
const frame_data = JSON.parse(new TextDecoder().decode(new Uint8Array(data, frame_offset + 8, json_length)));
|
|
243
|
-
const positions_ref = frame_data[`positions.`]
|
|
243
|
+
const positions_ref = frame_data[`positions.`] ?? frame_data.positions;
|
|
244
244
|
const positions = positions_ref?.ndarray
|
|
245
245
|
? read_ndarray_from_view(view, positions_ref)
|
|
246
246
|
: positions_ref;
|
|
247
|
-
const numbers_ref = frame_data[`numbers.`]
|
|
247
|
+
const numbers_ref = frame_data[`numbers.`] ?? frame_data.numbers ?? this.global_numbers;
|
|
248
248
|
const numbers = numbers_ref?.ndarray
|
|
249
249
|
? read_ndarray_from_view(view, numbers_ref).flat()
|
|
250
250
|
: numbers_ref;
|
|
@@ -266,7 +266,7 @@ export class TrajFrameReader {
|
|
|
266
266
|
console.warn(`Failed to calculate volume for frame ${frame_number}:`, error);
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
-
return create_trajectory_frame(positions, convert_atomic_numbers(numbers), cell, frame_data.pbc
|
|
269
|
+
return create_trajectory_frame(positions, convert_atomic_numbers(numbers), cell, frame_data.pbc ?? [true, true, true], frame_number, metadata);
|
|
270
270
|
}
|
|
271
271
|
catch (error) {
|
|
272
272
|
console.warn(`Failed to load ASE frame ${frame_number}:`, error);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { ElementSymbol } from '../element';
|
|
1
|
+
import type { ElementSymbol } from '../element/types';
|
|
2
2
|
import * as math from '../math';
|
|
3
|
-
import type { AnyStructure
|
|
3
|
+
import type { AnyStructure } from '../structure/index';
|
|
4
|
+
import type { Pbc } from '../structure/pbc';
|
|
4
5
|
import type { TrajectoryFrame } from './index';
|
|
5
|
-
export declare
|
|
6
|
-
export declare
|
|
6
|
+
export declare const is_valid_element_symbol: (symbol: string) => symbol is ElementSymbol;
|
|
7
|
+
export declare const coerce_element_symbol: (symbol: string) => ElementSymbol | undefined;
|
|
7
8
|
export declare function validate_3x3_matrix(data: unknown): math.Matrix3x3;
|
|
8
9
|
export declare const convert_atomic_numbers: (numbers: number[]) => ElementSymbol[];
|
|
9
10
|
export declare const create_structure: (positions: number[][], elements: ElementSymbol[], lattice_matrix?: math.Matrix3x3, pbc?: Pbc, force_data?: number[][]) => AnyStructure;
|
|
@@ -3,25 +3,20 @@ import { ATOMIC_NUMBER_TO_SYMBOL } from '../composition/parse';
|
|
|
3
3
|
import { ELEM_SYMBOLS } from '../labels';
|
|
4
4
|
import * as math from '../math';
|
|
5
5
|
const element_symbol_set = new Set(ELEM_SYMBOLS);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const is_valid_row = (row) => {
|
|
7
|
+
if (!(Array.isArray(row) || (ArrayBuffer.isView(row) && `length` in row)))
|
|
8
|
+
return false;
|
|
9
|
+
return math.is_finite_vec3_like(row);
|
|
10
|
+
};
|
|
11
|
+
const is_valid_vec3 = (coords) => Array.isArray(coords) && math.is_finite_vec3_like(coords);
|
|
12
|
+
export const is_valid_element_symbol = (symbol) => element_symbol_set.has(symbol);
|
|
13
|
+
export const coerce_element_symbol = (symbol) => is_valid_element_symbol(symbol) ? symbol : undefined;
|
|
12
14
|
// Validate that data is a proper 3x3 matrix
|
|
13
15
|
// Accepts both regular arrays and typed arrays (Float32Array, Float64Array, etc.)
|
|
14
16
|
export function validate_3x3_matrix(data) {
|
|
15
17
|
if (!Array.isArray(data) || data.length !== 3) {
|
|
16
18
|
throw new Error(`Expected 3x3 matrix, got array of length ${Array.isArray(data) ? data.length : `non-array`}`);
|
|
17
19
|
}
|
|
18
|
-
const is_valid_row = (row) => {
|
|
19
|
-
if (Array.isArray(row))
|
|
20
|
-
return row.length === 3;
|
|
21
|
-
if (!ArrayBuffer.isView(row))
|
|
22
|
-
return false;
|
|
23
|
-
return `length` in row && typeof row.length === `number` && row.length === 3;
|
|
24
|
-
};
|
|
25
20
|
if (!data.every(is_valid_row)) {
|
|
26
21
|
throw new Error(`Invalid 3x3 matrix structure`);
|
|
27
22
|
}
|
|
@@ -39,9 +34,6 @@ export const create_structure = (positions, elements, lattice_matrix, pbc, force
|
|
|
39
34
|
throw new Error(`create_structure requires matching positions and elements lengths, got positions=${positions.length}, elements=${elements.length}`);
|
|
40
35
|
}
|
|
41
36
|
const cart_to_frac = lattice_matrix ? math.create_cart_to_frac(lattice_matrix) : null;
|
|
42
|
-
const is_valid_vec3 = (coords) => Array.isArray(coords) &&
|
|
43
|
-
coords.length === 3 &&
|
|
44
|
-
coords.every((value) => typeof value === `number` && Number.isFinite(value));
|
|
45
37
|
const sites = positions.map((pos, idx) => {
|
|
46
38
|
if (!is_valid_vec3(pos)) {
|
|
47
39
|
throw new Error(`Invalid position at index ${idx}: expected 3 finite coordinates`);
|
|
@@ -64,7 +56,7 @@ export const create_structure = (positions, elements, lattice_matrix, pbc, force
|
|
|
64
56
|
lattice: {
|
|
65
57
|
matrix: lattice_matrix,
|
|
66
58
|
...math.calc_lattice_params(lattice_matrix),
|
|
67
|
-
pbc: pbc
|
|
59
|
+
pbc: pbc ?? [true, true, true],
|
|
68
60
|
},
|
|
69
61
|
}
|
|
70
62
|
: { sites };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AnyStructure } from '../structure';
|
|
2
|
-
import type Trajectory from './Trajectory.svelte';
|
|
3
1
|
import type { ComponentProps } from 'svelte';
|
|
2
|
+
import type { AnyStructure } from '../structure/index';
|
|
3
|
+
import type Trajectory from './Trajectory.svelte';
|
|
4
4
|
export { default as Trajectory } from './Trajectory.svelte';
|
|
5
5
|
export { default as TrajectoryError } from './TrajectoryError.svelte';
|
|
6
6
|
export { default as TrajectoryExportPane } from './TrajectoryExportPane.svelte';
|