matterviz 0.4.2 → 0.4.3

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 (244) hide show
  1. package/dist/FilePicker.svelte +40 -50
  2. package/dist/api/optimade.js +10 -7
  3. package/dist/app.css +15 -0
  4. package/dist/brillouin/BrillouinZone.svelte +18 -9
  5. package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
  6. package/dist/brillouin/compute.js +10 -16
  7. package/dist/brillouin/geometry.js +2 -2
  8. package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
  9. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
  10. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
  11. package/dist/chempot-diagram/async-compute.svelte.js +12 -13
  12. package/dist/chempot-diagram/color.d.ts +21 -1
  13. package/dist/chempot-diagram/color.js +70 -1
  14. package/dist/chempot-diagram/compute.d.ts +7 -0
  15. package/dist/chempot-diagram/compute.js +85 -26
  16. package/dist/chempot-diagram/export.js +1 -6
  17. package/dist/chempot-diagram/temperature.js +9 -10
  18. package/dist/composition/BarChart.svelte +7 -9
  19. package/dist/composition/Composition.svelte +3 -8
  20. package/dist/composition/Formula.svelte +2 -2
  21. package/dist/composition/FormulaFilter.svelte +27 -93
  22. package/dist/composition/format.js +6 -8
  23. package/dist/composition/parse.js +50 -105
  24. package/dist/constants.d.ts +0 -1
  25. package/dist/constants.js +6 -17
  26. package/dist/convex-hull/ConvexHull2D.svelte +0 -2
  27. package/dist/convex-hull/ConvexHull3D.svelte +19 -47
  28. package/dist/convex-hull/ConvexHull4D.svelte +65 -95
  29. package/dist/convex-hull/StructurePopup.svelte +1 -1
  30. package/dist/convex-hull/gas-thermodynamics.js +21 -53
  31. package/dist/convex-hull/helpers.js +1 -7
  32. package/dist/convex-hull/thermodynamics.js +19 -27
  33. package/dist/coordination/CoordinationBarPlot.svelte +12 -21
  34. package/dist/element/BohrAtom.svelte +0 -1
  35. package/dist/element/ElementTile.svelte +26 -44
  36. package/dist/element/data.d.ts +2 -0
  37. package/dist/element/data.js +5 -1
  38. package/dist/element/index.d.ts +1 -1
  39. package/dist/element/index.js +1 -1
  40. package/dist/fermi-surface/FermiSurface.svelte +22 -23
  41. package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
  42. package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
  43. package/dist/fermi-surface/compute.js +22 -55
  44. package/dist/fermi-surface/parse.js +38 -70
  45. package/dist/file-viewer/JsonBrowser.svelte +1043 -0
  46. package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
  47. package/dist/file-viewer/PlotPanel.svelte +350 -0
  48. package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
  49. package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
  50. package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
  51. package/dist/file-viewer/detect.d.ts +11 -0
  52. package/dist/file-viewer/detect.js +381 -0
  53. package/dist/file-viewer/eligibility.d.ts +4 -0
  54. package/dist/file-viewer/eligibility.js +53 -0
  55. package/dist/file-viewer/host-protocol.d.ts +50 -0
  56. package/dist/file-viewer/host-transfer.d.ts +25 -0
  57. package/dist/file-viewer/host-transfer.js +42 -0
  58. package/dist/file-viewer/index.d.ts +5 -0
  59. package/dist/file-viewer/index.js +5 -0
  60. package/dist/file-viewer/main.d.ts +44 -0
  61. package/dist/file-viewer/main.js +535 -0
  62. package/dist/file-viewer/parse.d.ts +9 -0
  63. package/dist/file-viewer/parse.js +170 -0
  64. package/dist/file-viewer/plot-utils.d.ts +24 -0
  65. package/dist/file-viewer/plot-utils.js +191 -0
  66. package/dist/file-viewer/types.d.ts +4 -0
  67. package/dist/file-viewer/types.js +5 -0
  68. package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
  69. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  70. package/dist/index.d.ts +2 -2
  71. package/dist/index.js +1 -1
  72. package/dist/io/ExportPane.svelte +1 -1
  73. package/dist/io/decompress.d.ts +2 -0
  74. package/dist/io/decompress.js +3 -2
  75. package/dist/io/export.d.ts +7 -4
  76. package/dist/io/export.js +70 -18
  77. package/dist/io/file-drop.js +60 -35
  78. package/dist/io/url-drop.d.ts +2 -0
  79. package/dist/io/url-drop.js +28 -16
  80. package/dist/isosurface/Isosurface.svelte +687 -196
  81. package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
  82. package/dist/isosurface/IsosurfaceControls.svelte +564 -85
  83. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
  84. package/dist/isosurface/VolumeSlice.svelte +224 -0
  85. package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
  86. package/dist/isosurface/coloring.d.ts +20 -0
  87. package/dist/isosurface/coloring.js +118 -0
  88. package/dist/isosurface/geometry-worker-types.d.ts +38 -0
  89. package/dist/isosurface/geometry.worker.d.ts +1 -0
  90. package/dist/isosurface/geometry.worker.js +59 -0
  91. package/dist/isosurface/grid.d.ts +10 -0
  92. package/dist/isosurface/grid.js +31 -0
  93. package/dist/isosurface/index.d.ts +4 -0
  94. package/dist/isosurface/index.js +4 -0
  95. package/dist/isosurface/profile.d.ts +12 -0
  96. package/dist/isosurface/profile.js +19 -0
  97. package/dist/isosurface/sampling.d.ts +34 -0
  98. package/dist/isosurface/sampling.js +408 -0
  99. package/dist/isosurface/slice-rendering.d.ts +13 -0
  100. package/dist/isosurface/slice-rendering.js +79 -0
  101. package/dist/isosurface/slice.d.ts +22 -2
  102. package/dist/isosurface/slice.js +200 -116
  103. package/dist/isosurface/types.d.ts +24 -1
  104. package/dist/isosurface/types.js +120 -8
  105. package/dist/json-path.d.ts +4 -0
  106. package/dist/json-path.js +89 -0
  107. package/dist/labels.js +1 -1
  108. package/dist/layout/InfoCard.svelte +1 -3
  109. package/dist/layout/SettingsSection.svelte +52 -61
  110. package/dist/layout/SettingsSection.svelte.d.ts +6 -7
  111. package/dist/layout/ViewerChrome.svelte +7 -1
  112. package/dist/layout/json-tree/JsonNode.svelte +14 -48
  113. package/dist/layout/json-tree/JsonTree.svelte +48 -95
  114. package/dist/layout/json-tree/JsonValue.svelte +3 -18
  115. package/dist/layout/json-tree/utils.d.ts +0 -3
  116. package/dist/layout/json-tree/utils.js +50 -179
  117. package/dist/marching-cubes.d.ts +8 -1
  118. package/dist/marching-cubes.js +115 -48
  119. package/dist/math.d.ts +3 -2
  120. package/dist/math.js +29 -38
  121. package/dist/overlays/ContextMenu.svelte +15 -32
  122. package/dist/overlays/DraggablePane.svelte +39 -58
  123. package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
  124. package/dist/overlays/GlassChip.svelte +3 -1
  125. package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
  126. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
  127. package/dist/phase-diagram/build-diagram.js +1 -6
  128. package/dist/phase-diagram/svg-to-diagram.js +9 -14
  129. package/dist/plot/bar/BarPlot.svelte +38 -42
  130. package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
  131. package/dist/plot/box/BoxPlot.svelte +27 -23
  132. package/dist/plot/core/auto-place.d.ts +1 -1
  133. package/dist/plot/core/auto-place.js +1 -1
  134. package/dist/plot/core/data-transform.d.ts +1 -2
  135. package/dist/plot/core/data-transform.js +0 -10
  136. package/dist/plot/core/layout.d.ts +12 -5
  137. package/dist/plot/core/layout.js +37 -27
  138. package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
  139. package/dist/plot/core/pan-zoom.svelte.js +34 -17
  140. package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
  141. package/dist/plot/core/placed-tween.svelte.js +68 -21
  142. package/dist/plot/core/svg.js +0 -1
  143. package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
  144. package/dist/plot/core/utils/hierarchy-chart.js +12 -0
  145. package/dist/plot/core/utils/hierarchy-labels.js +3 -2
  146. package/dist/plot/histogram/Histogram.svelte +21 -26
  147. package/dist/plot/sankey/sankey.js +13 -9
  148. package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
  149. package/dist/plot/scatter/ScatterPlot.svelte +67 -52
  150. package/dist/plot/scatter/adaptive-density.d.ts +1 -1
  151. package/dist/plot/scatter/adaptive-density.js +36 -20
  152. package/dist/plot/sunburst/Sunburst.svelte +20 -20
  153. package/dist/plot/treemap/Treemap.svelte +175 -60
  154. package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
  155. package/dist/plot/treemap/index.d.ts +1 -0
  156. package/dist/plot/treemap/labels.d.ts +38 -0
  157. package/dist/plot/treemap/labels.js +80 -0
  158. package/dist/plot/treemap/treemap.d.ts +4 -2
  159. package/dist/plot/treemap/treemap.js +5 -2
  160. package/dist/rdf/RdfPlot.svelte +17 -35
  161. package/dist/rdf/calc-rdf.js +59 -44
  162. package/dist/rdf/index.d.ts +0 -1
  163. package/dist/sanitize.js +84 -26
  164. package/dist/scene/SceneCamera.svelte +1 -1
  165. package/dist/scene/SceneCamera.svelte.d.ts +1 -1
  166. package/dist/scene/index.d.ts +1 -2
  167. package/dist/scene/index.js +1 -1
  168. package/dist/scene/{props.js → props.svelte.js} +15 -1
  169. package/dist/settings.js +159 -482
  170. package/dist/spectral/Bands.svelte +151 -158
  171. package/dist/spectral/BrillouinBandsDos.svelte +4 -19
  172. package/dist/spectral/Dos.svelte +26 -24
  173. package/dist/spectral/helpers.js +40 -105
  174. package/dist/structure/AtomLegend.svelte +8 -13
  175. package/dist/structure/AtomLegend.svelte.d.ts +2 -3
  176. package/dist/structure/CellSelect.svelte +35 -10
  177. package/dist/structure/Structure.svelte +177 -99
  178. package/dist/structure/Structure.svelte.d.ts +1 -1
  179. package/dist/structure/StructureCarousel.svelte +10 -3
  180. package/dist/structure/StructureControls.svelte +8 -26
  181. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  182. package/dist/structure/StructureInfoPane.svelte +65 -76
  183. package/dist/structure/StructureScene.svelte +75 -109
  184. package/dist/structure/StructureScene.svelte.d.ts +3 -1
  185. package/dist/structure/StructureViewport.svelte +120 -55
  186. package/dist/structure/StructureViewport.svelte.d.ts +3 -1
  187. package/dist/structure/atom-properties.d.ts +1 -1
  188. package/dist/structure/atom-properties.js +4 -3
  189. package/dist/structure/bond-order-perception.js +3 -8
  190. package/dist/structure/bonding.d.ts +0 -1
  191. package/dist/structure/bonding.js +16 -23
  192. package/dist/structure/export.js +50 -74
  193. package/dist/structure/format-detect.js +5 -7
  194. package/dist/structure/index.js +6 -12
  195. package/dist/structure/parse.js +38 -100
  196. package/dist/structure/partial-occupancy.js +7 -4
  197. package/dist/structure/pbc.js +8 -10
  198. package/dist/structure/polyhedra.js +5 -4
  199. package/dist/structure/prop-groups.d.ts +85 -0
  200. package/dist/structure/prop-groups.js +18 -0
  201. package/dist/symmetry/SymmetryStats.svelte +0 -4
  202. package/dist/symmetry/WyckoffTable.svelte +5 -4
  203. package/dist/symmetry/cell-transform.js +5 -14
  204. package/dist/symmetry/index.js +6 -9
  205. package/dist/table/HeatmapTable.svelte +61 -103
  206. package/dist/table/ToggleMenu.svelte +166 -44
  207. package/dist/table/ToggleMenu.svelte.d.ts +1 -1
  208. package/dist/theme/embedded.d.ts +18 -0
  209. package/dist/theme/embedded.js +206 -0
  210. package/dist/theme/index.d.ts +4 -0
  211. package/dist/theme/index.js +1 -2
  212. package/dist/theme/themes.mjs +31 -60
  213. package/dist/tooltip/TooltipContent.svelte +1 -1
  214. package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
  215. package/dist/tooltip/index.d.ts +8 -1
  216. package/dist/trajectory/Trajectory.svelte +140 -120
  217. package/dist/trajectory/TrajectoryError.svelte +1 -66
  218. package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
  219. package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
  220. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
  221. package/dist/trajectory/extract.js +19 -29
  222. package/dist/trajectory/format-detect.d.ts +2 -0
  223. package/dist/trajectory/format-detect.js +7 -5
  224. package/dist/trajectory/frame-reader.js +3 -4
  225. package/dist/trajectory/helpers.js +16 -49
  226. package/dist/trajectory/index.js +2 -8
  227. package/dist/trajectory/parse/ase.js +1 -1
  228. package/dist/trajectory/parse/index.d.ts +4 -3
  229. package/dist/trajectory/parse/index.js +12 -17
  230. package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
  231. package/dist/trajectory/parse/vaspout-h5.js +1 -4
  232. package/dist/trajectory/parse/xyz.js +48 -2
  233. package/dist/trajectory/plotting.js +14 -37
  234. package/dist/xrd/XrdPlot.svelte +43 -68
  235. package/dist/xrd/calc-xrd.js +22 -58
  236. package/dist/xrd/parse.js +13 -22
  237. package/package.json +40 -8
  238. package/readme.md +6 -5
  239. package/dist/scene/types.d.ts +0 -26
  240. package/dist/tooltip/types.d.ts +0 -8
  241. package/dist/trajectory/constants.d.ts +0 -6
  242. package/dist/trajectory/constants.js +0 -11
  243. /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
  244. /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
@@ -8,11 +8,12 @@
8
8
  import { SETTINGS_CONFIG } from '../settings'
9
9
  import type { AnyStructure } from '../structure'
10
10
  import type { ComponentProps } from 'svelte'
11
- import type { TrajectoryType } from './index'
11
+ import type { TrajectoryFrame, TrajectoryType } from './index'
12
12
 
13
13
  let {
14
14
  trajectory,
15
15
  current_step_idx,
16
+ current_frame = null,
16
17
  current_filename,
17
18
  current_file_path,
18
19
  file_size,
@@ -24,6 +25,7 @@
24
25
  }: Omit<ComponentProps<typeof DraggablePane>, `children`> & {
25
26
  trajectory: TrajectoryType
26
27
  current_step_idx: number
28
+ current_frame?: TrajectoryFrame | null
27
29
  current_filename?: string | null
28
30
  current_file_path?: string | null
29
31
  file_size?: number | null
@@ -76,14 +78,18 @@
76
78
  )
77
79
  return []
78
80
 
79
- // For indexed trajectories, we might not have the current frame loaded
80
- const current_frame = trajectory.frames?.[current_step_idx]
81
+ // For indexed trajectories, the resolved frame may live outside sparse frame arrays.
82
+ const displayed_frame = current_frame ?? trajectory.frames?.[current_step_idx]
81
83
  const total_frames = trajectory.total_frames ?? trajectory.frames?.length ?? 0
82
84
 
83
85
  const sections: { title: string; items: InfoItem[] }[] = []
86
+ // Append a section unless every item filtered out as falsy
87
+ const push_section = (title: string, items: unknown[]) => {
88
+ const valid_items = items.filter(is_info_item)
89
+ if (valid_items.length > 0) sections.push({ title, items: valid_items })
90
+ }
84
91
 
85
- // File info section
86
- const file_items = [
92
+ push_section(`File`, [
87
93
  current_filename &&
88
94
  safe_item(`Name`, current_filename, `file-name`, current_file_path || undefined),
89
95
  file_size &&
@@ -97,14 +103,9 @@
97
103
  ),
98
104
  trajectory.metadata?.source_format &&
99
105
  safe_item(`Format`, String(trajectory.metadata.source_format), `file-format`),
100
- ].filter(is_info_item)
101
-
102
- if (file_items.length > 0) {
103
- sections.push({ title: `File`, items: file_items })
104
- }
106
+ ])
105
107
 
106
- // Trajectory info section (always show this)
107
- const traj_items = [
108
+ push_section(`Trajectory`, [
108
109
  safe_item(
109
110
  `Total Frames`,
110
111
  `${format_num(total_frames, `.3~s`)} (current: ${format_num(
@@ -134,20 +135,16 @@
134
135
  `plot-metadata`,
135
136
  `Pre-extracted metadata for plotting`,
136
137
  ),
137
- ].filter(is_info_item)
138
-
139
- if (traj_items.length > 0) {
140
- sections.push({ title: `Trajectory`, items: traj_items })
141
- }
138
+ ])
142
139
 
143
140
  // Structure info section (only if we have the current frame)
144
- if (current_frame?.structure?.sites) {
145
- const { structure } = current_frame
141
+ if (displayed_frame?.structure?.sites) {
142
+ const { structure } = displayed_frame
146
143
  const lattice = `lattice` in structure ? structure.lattice : null
147
144
  const { volume, a, b, c, alpha, beta, gamma } = lattice || {}
148
145
  const formula = safe_formula(structure)
149
146
 
150
- const structure_items = [
147
+ push_section(`Structure`, [
151
148
  safe_item(`Atoms`, `${structure.sites.length}`, `atoms`),
152
149
  formula && safe_item(`Formula`, String(formula), `formula`),
153
150
  is_valid_number(volume) &&
@@ -158,7 +155,7 @@
158
155
  structure.sites.length > 0 &&
159
156
  safe_item(
160
157
  `Density`,
161
- `${format_num(structure.sites.length / volume, `.4~s`)} atoms/ų`,
158
+ `${format_num(structure.sites.length / volume, `.3~s`)} atoms/ų`,
162
159
  `density`,
163
160
  ),
164
161
  [a, b, c].every(is_valid_number) &&
@@ -179,77 +176,59 @@
179
176
  )}°, ${format_num(gamma as number, `.2~f`)}°`,
180
177
  `cell-angles`,
181
178
  ),
182
- ].filter(is_info_item)
183
-
184
- if (structure_items.length > 0) {
185
- sections.push({ title: `Structure`, items: structure_items })
186
- }
179
+ ])
187
180
  } else if (trajectory.is_indexed) {
188
181
  // For indexed trajectories, show a note that frame data is loaded on demand
189
- const structure_items = [
182
+ push_section(`Structure`, [
190
183
  safe_item(
191
184
  `Frame Loading`,
192
185
  `On-demand`,
193
186
  `frame-loading`,
194
187
  `Structure data loaded when frame is accessed`,
195
188
  ),
196
- ].filter(is_info_item)
197
-
198
- if (structure_items.length > 0) {
199
- sections.push({ title: `Structure`, items: structure_items })
200
- }
201
- }
202
-
203
- // Energy section (only for regular trajectories with multiple frames)
204
- if (!trajectory.is_indexed && trajectory.frames.length > 1) {
205
- const energies = extract_numeric_array(trajectory.frames, `energy`)
206
- if (energies.length > 1) {
207
- const current_energy = current_frame?.metadata?.energy
208
- const energy_range = format_range(energies, `eV`, `.3~s`)
209
-
210
- const energy_items = [
211
- is_valid_number(current_energy) &&
212
- safe_item(
213
- `Current Energy`,
214
- `${format_num(current_energy, `.3~s`)} eV`,
215
- `energy-current`,
216
- ),
217
- energy_range && safe_item(`Energy Range`, energy_range, `energy-range`),
218
- ].filter(is_info_item)
219
-
220
- if (energy_items.length > 0) {
221
- sections.push({ title: `Energy`, items: energy_items })
222
- }
223
- }
189
+ ])
224
190
  }
225
191
 
226
- // Forces section (only for regular trajectories with multiple frames)
192
+ // Energy/Forces sections (only for regular trajectories with multiple frames)
227
193
  if (!trajectory.is_indexed && trajectory.frames.length > 1) {
228
- const forces = extract_numeric_array(trajectory.frames, `force_max`)
229
- if (forces.length > 1) {
230
- const current_force = current_frame?.metadata?.force_max
231
- const force_range = format_range(forces, `eV/Å`, `.3~s`)
232
-
233
- const force_items = [
234
- is_valid_number(current_force) &&
194
+ const range_sections = [
195
+ {
196
+ title: `Energy`,
197
+ prop: `energy`,
198
+ unit: `eV`,
199
+ key: `energy`,
200
+ current_label: `Current Energy`,
201
+ range_label: `Energy Range`,
202
+ },
203
+ {
204
+ title: `Forces`,
205
+ prop: `force_max`,
206
+ unit: `eV/Å`,
207
+ key: `force`,
208
+ current_label: `Max Force`,
209
+ range_label: `Force Range`,
210
+ },
211
+ ] as const
212
+ for (const { title, prop, unit, key, current_label, range_label } of range_sections) {
213
+ const values = extract_numeric_array(trajectory.frames, prop)
214
+ if (values.length <= 1) continue
215
+ const current = displayed_frame?.metadata?.[prop]
216
+ push_section(title, [
217
+ is_valid_number(current) &&
235
218
  safe_item(
236
- `Max Force`,
237
- `${format_num(current_force, `.3~s`)} eV/Å`,
238
- `force-current`,
219
+ current_label,
220
+ `${format_num(current, `.3~s`)} ${unit}`,
221
+ `${key}-current`,
239
222
  ),
240
- force_range && safe_item(`Force Range`, force_range, `force-range`),
241
- ].filter(is_info_item)
242
-
243
- if (force_items.length > 0) {
244
- sections.push({ title: `Forces`, items: force_items })
245
- }
223
+ safe_item(range_label, format_range(values, unit, `.3~s`), `${key}-range`),
224
+ ])
246
225
  }
247
226
  }
248
227
 
249
228
  // Volume change section (only for regular trajectories)
250
- if (!trajectory.is_indexed && current_frame?.structure && trajectory.frames.length > 1) {
229
+ if (!trajectory.is_indexed && displayed_frame?.structure && trajectory.frames.length > 1) {
251
230
  const lattice =
252
- `lattice` in current_frame.structure ? current_frame.structure.lattice : null
231
+ `lattice` in displayed_frame.structure ? displayed_frame.structure.lattice : null
253
232
  if (lattice) {
254
233
  const volumes = trajectory.frames
255
234
  .map(({ structure }) => `lattice` in structure && structure.lattice?.volume)
@@ -260,13 +239,9 @@
260
239
  const vol_change =
261
240
  (Math.max(...volumes) - Math.min(...volumes)) / Math.min(...volumes)
262
241
  if (Math.abs(vol_change) > 0.1 && is_valid_number(vol_change)) {
263
- const vol_items = [
242
+ push_section(`Volume`, [
264
243
  safe_item(`Volume Change`, `${format_num(vol_change, `.2~%`)}`, `vol-change`),
265
- ].filter(is_info_item)
266
-
267
- if (vol_items.length > 0) {
268
- sections.push({ title: `Volume`, items: vol_items })
269
- }
244
+ ])
270
245
  }
271
246
  }
272
247
  }
@@ -1,10 +1,11 @@
1
1
  import type { PaneProps, PaneToggleProps } from '../overlays';
2
2
  import DraggablePane from '../overlays/DraggablePane.svelte';
3
3
  import type { ComponentProps } from 'svelte';
4
- import type { TrajectoryType } from './index';
4
+ import type { TrajectoryFrame, TrajectoryType } from './index';
5
5
  type $$ComponentProps = Omit<ComponentProps<typeof DraggablePane>, `children`> & {
6
6
  trajectory: TrajectoryType;
7
7
  current_step_idx: number;
8
+ current_frame?: TrajectoryFrame | null;
8
9
  current_filename?: string | null;
9
10
  current_file_path?: string | null;
10
11
  file_size?: number | null;
@@ -1,23 +1,21 @@
1
1
  // Data extraction functions for trajectory analysis and plotting
2
2
  import { get_density } from '../structure/index';
3
3
  import { calc_force_stats, copy_numeric_fields } from './helpers';
4
- // Common data extractor that extracts energy and structural properties
5
- export const energy_data_extractor = (frame) => {
6
- const data = {
7
- Step: frame.step,
8
- };
9
- if (frame.metadata) {
10
- // Extract energy-related properties
11
- copy_numeric_fields(data, frame.metadata, [
12
- `energy`,
13
- `energy_per_atom`,
14
- `potential_energy`,
15
- `kinetic_energy`,
16
- `total_energy`,
17
- ]);
18
- }
4
+ // Build an extractor that copies the listed numeric metadata fields (plus Step)
5
+ const make_metadata_extractor = (fields) => (frame) => {
6
+ const data = { Step: frame.step };
7
+ if (frame.metadata)
8
+ copy_numeric_fields(data, frame.metadata, fields);
19
9
  return data;
20
10
  };
11
+ // Common data extractor that extracts energy-related properties
12
+ export const energy_data_extractor = make_metadata_extractor([
13
+ `energy`,
14
+ `energy_per_atom`,
15
+ `potential_energy`,
16
+ `kinetic_energy`,
17
+ `total_energy`,
18
+ ]);
21
19
  // Data extractor for forces and stresses
22
20
  export const force_stress_data_extractor = (frame) => {
23
21
  const data = {
@@ -55,20 +53,12 @@ export const force_stress_data_extractor = (frame) => {
55
53
  // Data extractor for SCF/electronic-convergence properties. Parsers emit these
56
54
  // per frame (per ionic step, or per SCF step for static single-point runs) —
57
55
  // e.g. the vaspout.h5 parser fills them from VASP's OSZICAR data.
58
- const scf_data_extractor = (frame) => {
59
- const data = {
60
- Step: frame.step,
61
- };
62
- if (frame.metadata) {
63
- copy_numeric_fields(data, frame.metadata, [
64
- `n_scf_steps`,
65
- `scf_energy_delta`,
66
- `scf_rms`,
67
- `scf_charge_rms`,
68
- ]);
69
- }
70
- return data;
71
- };
56
+ const scf_data_extractor = make_metadata_extractor([
57
+ `n_scf_steps`,
58
+ `scf_energy_delta`,
59
+ `scf_rms`,
60
+ `scf_charge_rms`,
61
+ ]);
72
62
  // Data extractor for structural properties
73
63
  export const structural_data_extractor = (frame) => {
74
64
  const data = {
@@ -1,4 +1,6 @@
1
1
  export declare function ext_hint(filename: string | undefined, ext_regex: RegExp): boolean | null;
2
+ export declare const indexed_trajectory_format: (filename: string) => `ase` | `xyz`;
3
+ export declare const is_indexable_trajectory_filename: (filename: string) => boolean;
2
4
  export declare const FORMAT_PATTERNS: {
3
5
  readonly ase: (data: unknown, filename?: string) => boolean;
4
6
  readonly hdf5: (data: unknown, filename?: string) => boolean;
@@ -1,5 +1,5 @@
1
1
  // Format detection for trajectory files
2
- import { CONFIG_DIRS_REGEX, MD_SIM_EXCLUDE_REGEX, TRAJ_EXTENSIONS_REGEX, TRAJ_FALLBACK_EXTENSIONS_REGEX, TRAJ_KEYWORDS_SIMPLE_REGEX, XDATCAR_REGEX, } from '../constants';
2
+ import { CONFIG_DIRS_REGEX, MD_SIM_EXCLUDE_REGEX, TRAJ_EXTENSIONS_REGEX, TRAJ_FALLBACK_EXTENSIONS_REGEX, TRAJ_KEYWORDS_REGEX, XDATCAR_REGEX, } from '../constants';
3
3
  import { strip_compression_extensions } from '../io/decompress';
4
4
  import { parse_leading_num } from '../utils';
5
5
  import { count_xyz_frames } from './helpers';
@@ -18,6 +18,9 @@ export function ext_hint(filename, ext_regex) {
18
18
  return true;
19
19
  return KNOWN_FORMAT_EXT_REGEX.test(base) ? false : null;
20
20
  }
21
+ // Large-file frame indexing currently supports text XYZ/EXTXYZ and binary ASE .traj.
22
+ export const indexed_trajectory_format = (filename) => strip_compression_extensions(filename).endsWith(`.traj`) ? `ase` : `xyz`;
23
+ export const is_indexable_trajectory_filename = (filename) => /\.(?:xyz|extxyz|traj)$/i.test(strip_compression_extensions(filename));
21
24
  // Unified format detection. Each pattern trusts a matching file extension when present
22
25
  // but falls back to content/magic-byte detection when the filename gives no hint
23
26
  // (e.g. blob: object URLs, extensionless API endpoints).
@@ -67,7 +70,7 @@ export function is_trajectory_file(filename, content) {
67
70
  if (/\.(?:xyz|extxyz)$/i.test(base_name)) {
68
71
  if (content)
69
72
  return count_xyz_frames(content) >= 2;
70
- return TRAJ_KEYWORDS_SIMPLE_REGEX.test(base_name);
73
+ return TRAJ_KEYWORDS_REGEX.test(base_name);
71
74
  }
72
75
  // Always detect these specific trajectory formats
73
76
  if (TRAJ_EXTENSIONS_REGEX.test(base_name) || XDATCAR_REGEX.test(base_name))
@@ -78,9 +81,8 @@ export function is_trajectory_file(filename, content) {
78
81
  // For .h5/.hdf5 files, require trajectory keywords. vaspout.h5 (VASP's
79
82
  // HDF5 output) is always trajectory-shaped regardless of keywords.
80
83
  if (/\.(?:h5|hdf5)$/i.test(base_name)) {
81
- return /vaspout/i.test(base_name) || TRAJ_KEYWORDS_SIMPLE_REGEX.test(base_name);
84
+ return /vaspout/i.test(base_name) || TRAJ_KEYWORDS_REGEX.test(base_name);
82
85
  }
83
86
  // For other extensions, require both keywords and specific extensions
84
- return (TRAJ_KEYWORDS_SIMPLE_REGEX.test(base_name) &&
85
- TRAJ_FALLBACK_EXTENSIONS_REGEX.test(base_name));
87
+ return TRAJ_KEYWORDS_REGEX.test(base_name) && TRAJ_FALLBACK_EXTENSIONS_REGEX.test(base_name);
86
88
  }
@@ -1,10 +1,10 @@
1
1
  // Unified frame loader for XYZ and ASE trajectories (large file indexing)
2
2
  import * as math from '../math';
3
- import { MAX_METADATA_SIZE } from './constants';
4
3
  import { copy_numeric_fields, count_xyz_frames, iter_xyz_frames, validate_3x3_matrix, } from './helpers';
5
- import { strip_compression_extensions } from '../io/decompress';
4
+ import { indexed_trajectory_format } from './format-detect';
6
5
  import { decode_ase_frame, read_ase_header } from './parse/ase';
7
6
  import { build_xyz_frame, parse_xyz_comment_metadata } from './parse/xyz';
7
+ const MAX_METADATA_SIZE = 50 * 1024 * 1024; // 50MB limit for metadata
8
8
  // Restrict frame metadata to the requested property keys (no-op when unset)
9
9
  const filter_properties = (metadata, properties) => {
10
10
  if (!properties)
@@ -18,8 +18,7 @@ export class TrajFrameReader {
18
18
  // O(1) lookup instead of re-splitting + rescanning from line 0 (was O(n²) over playback)
19
19
  xyz_cache;
20
20
  constructor(filename) {
21
- const base_filename = strip_compression_extensions(filename);
22
- this.format = base_filename.endsWith(`.traj`) ? `ase` : `xyz`;
21
+ this.format = indexed_trajectory_format(filename);
23
22
  }
24
23
  async get_total_frames(data) {
25
24
  if (this.format === `xyz`) {
@@ -3,11 +3,6 @@ import { ATOMIC_NUMBER_TO_SYMBOL } from '../composition/parse';
3
3
  import { is_elem_symbol } from '../element/helpers';
4
4
  import * as math from '../math';
5
5
  import { make_site } from '../structure/site';
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
6
  const is_valid_vec3 = (coords) => Array.isArray(coords) && math.is_finite_vec3_like(coords);
12
7
  // Validate that data is a proper 3x3 matrix
13
8
  // Accepts both regular arrays and typed arrays (Float32Array, Float64Array, etc.)
@@ -15,7 +10,8 @@ export function validate_3x3_matrix(data) {
15
10
  if (!Array.isArray(data) || data.length !== 3) {
16
11
  throw new Error(`Expected 3x3 matrix, got array of length ${Array.isArray(data) ? data.length : `non-array`}`);
17
12
  }
18
- if (!data.every(is_valid_row)) {
13
+ if (!data.every((row) => (Array.isArray(row) || (ArrayBuffer.isView(row) && `length` in row)) &&
14
+ math.is_finite_vec3_like(row))) {
19
15
  throw new Error(`Invalid 3x3 matrix structure`);
20
16
  }
21
17
  return data;
@@ -69,54 +65,25 @@ export const create_trajectory_frame = (positions, elements, lattice_matrix, pbc
69
65
  export const read_ndarray_from_view = (view, ref) => {
70
66
  const [shape, dtype, array_offset] = ref.ndarray;
71
67
  const total = shape.reduce((product, dim_size) => product * dim_size, 1);
72
- const data = [];
73
- let pos = array_offset;
74
68
  const readers = {
75
- int64: {
76
- bytes_per_element: 8,
77
- read: () => {
78
- const value = Number(view.getBigInt64(pos, true));
79
- pos += 8;
80
- return value;
81
- },
82
- },
83
- int32: {
84
- bytes_per_element: 4,
85
- read: () => {
86
- const value = view.getInt32(pos, true);
87
- pos += 4;
88
- return value;
89
- },
90
- },
91
- float64: {
92
- bytes_per_element: 8,
93
- read: () => {
94
- const value = view.getFloat64(pos, true);
95
- pos += 8;
96
- return value;
97
- },
98
- },
99
- float32: {
100
- bytes_per_element: 4,
101
- read: () => {
102
- const value = view.getFloat32(pos, true);
103
- pos += 4;
104
- return value;
105
- },
106
- },
69
+ int64: { bytes: 8, read: (pos) => Number(view.getBigInt64(pos, true)) },
70
+ int32: { bytes: 4, read: (pos) => view.getInt32(pos, true) },
71
+ float64: { bytes: 8, read: (pos) => view.getFloat64(pos, true) },
72
+ float32: { bytes: 4, read: (pos) => view.getFloat32(pos, true) },
107
73
  };
108
- const reader_config = readers[dtype];
109
- if (!reader_config)
74
+ const reader = readers[dtype];
75
+ if (!reader)
110
76
  throw new Error(`Unsupported dtype: ${dtype}`);
111
77
  if (!Number.isInteger(array_offset) || array_offset < 0) {
112
78
  throw new Error(`Invalid array_offset: expected non-negative integer, got ${array_offset}`);
113
79
  }
114
- const bytes_needed = total * reader_config.bytes_per_element;
115
- if (array_offset + bytes_needed > view.byteLength) {
80
+ if (array_offset + total * reader.bytes > view.byteLength) {
116
81
  throw new Error(`Out-of-bounds read: array_offset + bytesNeeded exceeds view.byteLength`);
117
82
  }
118
- for (let idx = 0; idx < total; idx++)
119
- data.push(reader_config.read());
83
+ const data = [];
84
+ for (let idx = 0; idx < total; idx++) {
85
+ data.push(reader.read(array_offset + idx * reader.bytes));
86
+ }
120
87
  return shape.length === 1
121
88
  ? [data]
122
89
  : shape.length === 2
@@ -147,7 +114,6 @@ export function calc_force_stats(forces) {
147
114
  }
148
115
  return { force_max, force_norm: Math.sqrt(sum_sq / forces.length) };
149
116
  }
150
- // Walk concatenated (ext)XYZ frames in `lines`, yielding each frame's atom-count line
151
117
  // True when a whitespace-split line looks like an XYZ atom line: a short
152
118
  // non-numeric element token followed by three numeric coordinates. Shared by
153
119
  // trajectory frame iteration and structure-format sniffing so both stay in sync.
@@ -156,7 +122,8 @@ export const is_xyz_atom_line = (parts) => parts !== undefined &&
156
122
  isNaN(Number(parts[0])) &&
157
123
  parts[0].length <= 3 &&
158
124
  parts.slice(1, 4).every((coord) => coord !== `` && !isNaN(Number(coord)));
159
- // index, parsed atom count, and comment line. A candidate frame is accepted only when its
125
+ // Walk concatenated (ext)XYZ frames in `lines`, yielding each frame's start line index,
126
+ // parsed atom count, and comment line. A candidate frame is accepted only when its
160
127
  // first few atom lines look like "<element> <x> <y> <z>"; otherwise we advance one line and
161
128
  // rescan. That validation doubles as content sniffing so numeric-leading non-XYZ formats
162
129
  // (e.g. VASP XDATCAR) aren't misread as frames, and keeps count_xyz_frames consistent with
@@ -185,7 +152,7 @@ export function* iter_xyz_frames(lines) {
185
152
  }
186
153
  // Count XYZ frames via iter_xyz_frames so total_frames matches what gets indexed/loaded
187
154
  export function count_xyz_frames(data) {
188
- if (!data || typeof data !== `string`)
155
+ if (!data)
189
156
  return 0;
190
157
  const frames = iter_xyz_frames(data.trim().split(/\r?\n/));
191
158
  let frame_count = 0;
@@ -6,7 +6,6 @@ export { default as TrajectoryInfoPane } from './TrajectoryInfoPane.svelte';
6
6
  // the user slides past. Exported so tests stay in sync with the real delay.
7
7
  export const FRAME_LOAD_DEBOUNCE_MS = 75;
8
8
  export function validate_trajectory(trajectory) {
9
- // with detailed error reporting
10
9
  const errors = [];
11
10
  const { frames, total_frames, indexed_frames, plot_metadata, is_indexed } = trajectory;
12
11
  if (!frames?.length)
@@ -113,13 +112,8 @@ export function get_trajectory_stats(trajectory) {
113
112
  else
114
113
  stats.atom_count_range = [Math.min(...all_counts), Math.max(...all_counts)];
115
114
  }
116
- else {
117
- // Handle empty trajectory case
118
- stats.steps = [];
119
- stats.step_range = undefined;
120
- stats.constant_atom_count = undefined;
121
- stats.total_atoms = undefined;
122
- }
115
+ else
116
+ stats.steps = []; // empty trajectory
123
117
  // Additional metadata for large files
124
118
  if (indexed_frames)
125
119
  stats.indexed_frame_count = indexed_frames.length;
@@ -1,7 +1,7 @@
1
1
  // ASE trajectory (.traj) parsing - binary format
2
2
  import * as math from '../../math';
3
- import { MAX_SAFE_STRING_LENGTH } from '../constants';
4
3
  import { convert_atomic_numbers, create_trajectory_frame, read_ndarray_from_view, validate_3x3_matrix, } from '../helpers';
4
+ const MAX_SAFE_STRING_LENGTH = 0x1fffffe8 * 0.5; // 50% of JS max string length as safety
5
5
  // ULM header: frame count lives at byte 32, frame-offsets table position at byte 40
6
6
  export const read_ase_header = (view) => ({
7
7
  n_items: Number(view.getBigInt64(32, true)),
@@ -1,11 +1,12 @@
1
- import type { FrameLoader, ParseProgress, TrajectoryType } from '../index';
1
+ import type { ParseProgress, TrajectoryType } from '../index';
2
2
  import type { AtomTypeMapping, LoadingOptions } from '../types';
3
- export { LARGE_FILE_THRESHOLD, MAX_BIN_FILE_SIZE, MAX_TEXT_FILE_SIZE, } from '../constants';
3
+ export declare const LARGE_FILE_THRESHOLD: number;
4
+ export declare const MAX_BIN_FILE_SIZE: number;
5
+ export declare const MAX_TEXT_FILE_SIZE: number;
4
6
  export type { AtomTypeMapping, LoadingOptions } from '../types';
5
7
  export { is_trajectory_file } from '../format-detect';
6
8
  export { TrajFrameReader } from '../frame-reader';
7
9
  export declare function parse_trajectory_data(data: unknown, filename?: string, atom_type_mapping?: AtomTypeMapping): Promise<TrajectoryType>;
8
10
  export declare function get_unsupported_format_message(filename: string, content: string): string | null;
9
11
  export declare function parse_trajectory_async(data: ArrayBuffer | string, filename: string, on_progress?: (progress: ParseProgress) => void, options?: LoadingOptions): Promise<TrajectoryType>;
10
- export declare function create_frame_loader(filename: string): FrameLoader;
11
12
  export declare function load_binary_traj(resp: Response, type: string, fallback?: boolean): Promise<ArrayBuffer | string>;
@@ -1,10 +1,9 @@
1
1
  // Parsing functions for trajectory data from various formats
2
2
  import { is_binary } from '../../io/is-binary';
3
3
  import { is_plain_object } from '../../utils';
4
+ import { DEFAULTS } from '../../settings';
4
5
  import { is_parsed_structure, parse_xyz } from '../../structure/parse';
5
- import { INDEX_SAMPLE_RATE, LARGE_FILE_THRESHOLD } from '../constants';
6
- import { strip_compression_extensions } from '../../io/decompress';
7
- import { ext_hint, FORMAT_PATTERNS } from '../format-detect';
6
+ import { ext_hint, FORMAT_PATTERNS, indexed_trajectory_format, is_indexable_trajectory_filename, } from '../format-detect';
8
7
  import { TrajFrameReader } from '../frame-reader';
9
8
  import { count_xyz_frames } from '../helpers';
10
9
  import { parse_ase_trajectory } from './ase';
@@ -21,8 +20,14 @@ const assert_frame_structure = (structure, label) => {
21
20
  throw new Error(`Invalid structure in ${context}: expected non-empty 'sites' array with species and coordinates`);
22
21
  }
23
22
  };
24
- // Re-export constants and types for consumers
25
- export { LARGE_FILE_THRESHOLD, MAX_BIN_FILE_SIZE, MAX_TEXT_FILE_SIZE, } from '../constants';
23
+ // Constants for trajectory parsing and large file handling
24
+ export const LARGE_FILE_THRESHOLD = 400 * 1024 * 1024; // 400MB
25
+ const INDEX_SAMPLE_RATE = 100; // Default sample rate for frame indexing
26
+ // Fallback thresholds for component usage without loading_options, derived from the
27
+ // settings schema so settings-driven contexts (e.g. the VSCode extension) and direct
28
+ // component use agree on when large-file/indexed loading kicks in.
29
+ export const MAX_BIN_FILE_SIZE = DEFAULTS.trajectory.bin_file_threshold; // 50MB
30
+ export const MAX_TEXT_FILE_SIZE = DEFAULTS.trajectory.text_file_threshold; // 25MB
26
31
  export { is_trajectory_file } from '../format-detect';
27
32
  export { TrajFrameReader } from '../frame-reader';
28
33
  export async function parse_trajectory_data(data, filename, atom_type_mapping) {
@@ -154,8 +159,7 @@ export async function parse_trajectory_async(data, filename, on_progress, option
154
159
  // Use indexed loading for supported large files (including compressed names).
155
160
  // When the filename gives no format hint (e.g. blob: URLs), sniff a content
156
161
  // prefix for XYZ frames so large extensionless files still get indexed.
157
- const base_filename = strip_compression_extensions(filename);
158
- const can_index = /\.(?:xyz|extxyz|traj)$/.test(base_filename) ||
162
+ const can_index = is_indexable_trajectory_filename(filename) ||
159
163
  (typeof data === `string` &&
160
164
  ext_hint(filename, /\.(?:xyz|extxyz)$/) === null &&
161
165
  count_xyz_frames(data.slice(0, 2 ** 20)) >= 1);
@@ -213,9 +217,7 @@ async function parse_with_unified_loader(data, filename, options, on_progress) {
213
217
  }
214
218
  const stage = `Ready: ${total_frames} frames indexed`;
215
219
  on_progress?.({ current: 100, total: 100, stage });
216
- const source_format = filename.toLowerCase().endsWith(`.traj`)
217
- ? `ase_trajectory`
218
- : `xyz_trajectory`;
220
+ const source_format = `${indexed_trajectory_format(filename)}_trajectory`;
219
221
  return {
220
222
  frames,
221
223
  metadata: { source_format, frame_count: total_frames },
@@ -226,13 +228,6 @@ async function parse_with_unified_loader(data, filename, options, on_progress) {
226
228
  frame_loader: loader,
227
229
  };
228
230
  }
229
- // Factory function for frame loader (simplified)
230
- export function create_frame_loader(filename) {
231
- if (!/\.(?:xyz|extxyz|traj)$/.test(filename.toLowerCase())) {
232
- throw new Error(`Unsupported format for frame loading: ${filename}`);
233
- }
234
- return new TrajFrameReader(filename);
235
- }
236
231
  export async function load_binary_traj(resp, type, fallback = false) {
237
232
  try {
238
233
  // Read binary from a clone so the original can be used for text fallback
@@ -2,4 +2,3 @@ import type * as h5wasm from 'h5wasm';
2
2
  import type { TrajectoryType } from '../index';
3
3
  export declare const is_vaspout_h5_file: (h5_file: h5wasm.File) => boolean;
4
4
  export declare function parse_vaspout_h5_file(h5_file: h5wasm.File): TrajectoryType;
5
- export declare function parse_vaspout_h5(buffer: ArrayBuffer, filename?: string): Promise<TrajectoryType>;
@@ -17,7 +17,7 @@
17
17
  // VASP 6.x schema definitions).
18
18
  import { calc_lattice_params, create_frac_to_cart } from '../../math';
19
19
  import { count_elements, create_trajectory_frame, validate_3x3_matrix, } from '../helpers';
20
- import { expand_ion_types, is_hdf5_group, read_dataset, scale_matrix, to_number_array, to_scalar_number, to_string_array, with_h5_file, } from './h5-utils';
20
+ import { expand_ion_types, is_hdf5_group, read_dataset, scale_matrix, to_number_array, to_scalar_number, to_string_array, } from './h5-utils';
21
21
  import { read_vaspout_dos, read_vaspout_electronic } from './vaspout-electronic';
22
22
  const FINAL_ION_TYPES = `results/positions/ion_types`;
23
23
  const FINAL_ION_COUNTS = `results/positions/number_ion_types`;
@@ -279,6 +279,3 @@ export function parse_vaspout_h5_file(h5_file) {
279
279
  },
280
280
  };
281
281
  }
282
- export async function parse_vaspout_h5(buffer, filename) {
283
- return with_h5_file(buffer, filename, parse_vaspout_h5_file);
284
- }