matterviz 0.3.2 → 0.3.4

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 (281) hide show
  1. package/dist/EmptyState.svelte +10 -2
  2. package/dist/FilePicker.svelte +123 -82
  3. package/dist/Icon.svelte +18 -12
  4. package/dist/MillerIndexInput.svelte +27 -21
  5. package/dist/api/optimade.js +6 -6
  6. package/dist/app.css +216 -207
  7. package/dist/brillouin/BrillouinZone.svelte +292 -149
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +69 -42
  11. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  12. package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte +275 -163
  14. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  15. package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
  16. package/dist/brillouin/compute.js +11 -6
  17. package/dist/chempot-diagram/ChemPotDiagram.svelte +162 -27
  18. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +451 -281
  19. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2148 -1642
  20. package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -5
  21. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  22. package/dist/chempot-diagram/async-compute.svelte.js +77 -0
  23. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  24. package/dist/chempot-diagram/chempot-worker.js +11 -0
  25. package/dist/chempot-diagram/color.js +1 -2
  26. package/dist/chempot-diagram/compute.d.ts +10 -0
  27. package/dist/chempot-diagram/compute.js +250 -88
  28. package/dist/chempot-diagram/index.d.ts +2 -1
  29. package/dist/chempot-diagram/index.js +2 -1
  30. package/dist/chempot-diagram/temperature.js +8 -9
  31. package/dist/chempot-diagram/types.d.ts +3 -0
  32. package/dist/chempot-diagram/types.js +1 -0
  33. package/dist/colors/index.d.ts +1 -1
  34. package/dist/colors/index.js +5 -3
  35. package/dist/composition/BarChart.svelte +128 -55
  36. package/dist/composition/BubbleChart.svelte +102 -49
  37. package/dist/composition/Composition.svelte +100 -79
  38. package/dist/composition/Formula.svelte +108 -62
  39. package/dist/composition/FormulaFilter.svelte +665 -537
  40. package/dist/composition/PieChart.svelte +183 -108
  41. package/dist/composition/format.d.ts +5 -0
  42. package/dist/composition/format.js +20 -3
  43. package/dist/composition/parse.js +14 -9
  44. package/dist/convex-hull/ConvexHull.svelte +93 -40
  45. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHull2D.svelte +549 -360
  47. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  48. package/dist/convex-hull/ConvexHull3D.svelte +1296 -827
  49. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  50. package/dist/convex-hull/ConvexHull4D.svelte +1004 -688
  51. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  52. package/dist/convex-hull/ConvexHullControls.svelte +115 -28
  53. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
  54. package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
  55. package/dist/convex-hull/ConvexHullStats.svelte +425 -328
  56. package/dist/convex-hull/ConvexHullTooltip.svelte +40 -16
  57. package/dist/convex-hull/GasPressureControls.svelte +104 -61
  58. package/dist/convex-hull/StructurePopup.svelte +25 -4
  59. package/dist/convex-hull/TemperatureSlider.svelte +45 -25
  60. package/dist/convex-hull/barycentric-coords.js +13 -7
  61. package/dist/convex-hull/demo-temperature.js +8 -4
  62. package/dist/convex-hull/gas-thermodynamics.js +17 -12
  63. package/dist/convex-hull/helpers.d.ts +9 -0
  64. package/dist/convex-hull/helpers.js +77 -34
  65. package/dist/convex-hull/thermodynamics.js +61 -56
  66. package/dist/convex-hull/types.d.ts +9 -14
  67. package/dist/convex-hull/types.js +0 -17
  68. package/dist/coordination/CoordinationBarPlot.svelte +227 -154
  69. package/dist/element/BohrAtom.svelte +55 -12
  70. package/dist/element/ElementHeading.svelte +7 -2
  71. package/dist/element/ElementPhoto.svelte +15 -9
  72. package/dist/element/ElementStats.svelte +10 -4
  73. package/dist/element/ElementTile.svelte +137 -73
  74. package/dist/element/Nucleus.svelte +39 -11
  75. package/dist/element/data.js +1 -1
  76. package/dist/feedback/ClickFeedback.svelte +16 -5
  77. package/dist/feedback/DragOverlay.svelte +10 -2
  78. package/dist/feedback/Spinner.svelte +4 -2
  79. package/dist/feedback/StatusMessage.svelte +8 -2
  80. package/dist/fermi-surface/FermiSlice.svelte +118 -88
  81. package/dist/fermi-surface/FermiSurface.svelte +328 -187
  82. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  83. package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
  84. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  85. package/dist/fermi-surface/FermiSurfaceScene.svelte +535 -342
  86. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  87. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
  88. package/dist/fermi-surface/compute.js +16 -20
  89. package/dist/fermi-surface/parse.js +24 -14
  90. package/dist/fermi-surface/symmetry.js +2 -7
  91. package/dist/fermi-surface/types.d.ts +3 -5
  92. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1019 -765
  93. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +1 -1
  94. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +76 -22
  95. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +2 -3
  96. package/dist/icons.js +47 -0
  97. package/dist/index.d.ts +2 -1
  98. package/dist/index.js +2 -1
  99. package/dist/io/decompress.js +1 -1
  100. package/dist/io/export.d.ts +3 -0
  101. package/dist/io/export.js +129 -143
  102. package/dist/io/is-binary.js +2 -3
  103. package/dist/io/url-drop.js +1 -2
  104. package/dist/isosurface/Isosurface.svelte +202 -148
  105. package/dist/isosurface/IsosurfaceControls.svelte +46 -28
  106. package/dist/isosurface/parse.js +34 -29
  107. package/dist/isosurface/slice.js +5 -10
  108. package/dist/isosurface/types.d.ts +2 -1
  109. package/dist/isosurface/types.js +61 -12
  110. package/dist/labels.js +11 -8
  111. package/dist/layout/FullscreenToggle.svelte +11 -2
  112. package/dist/layout/InfoCard.svelte +38 -6
  113. package/dist/layout/InfoTag.svelte +63 -32
  114. package/dist/layout/PropertyFilter.svelte +82 -37
  115. package/dist/layout/SettingsSection.svelte +85 -55
  116. package/dist/layout/SubpageGrid.svelte +10 -2
  117. package/dist/layout/json-tree/JsonNode.svelte +183 -138
  118. package/dist/layout/json-tree/JsonTree.svelte +499 -413
  119. package/dist/layout/json-tree/JsonValue.svelte +127 -99
  120. package/dist/layout/json-tree/utils.js +4 -2
  121. package/dist/marching-cubes.js +25 -2
  122. package/dist/math.d.ts +13 -17
  123. package/dist/math.js +133 -67
  124. package/dist/overlays/ContextMenu.svelte +65 -40
  125. package/dist/overlays/DraggablePane.svelte +211 -139
  126. package/dist/periodic-table/PeriodicTable.svelte +278 -145
  127. package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
  128. package/dist/periodic-table/PropertySelect.svelte +25 -7
  129. package/dist/periodic-table/TableInset.svelte +8 -3
  130. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +446 -309
  131. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  132. package/dist/phase-diagram/PhaseDiagramControls.svelte +102 -43
  133. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  134. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +63 -40
  135. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +71 -28
  136. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +1 -1
  137. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +158 -101
  138. package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
  139. package/dist/phase-diagram/build-diagram.js +9 -9
  140. package/dist/phase-diagram/colors.js +1 -3
  141. package/dist/phase-diagram/parse.js +10 -9
  142. package/dist/phase-diagram/svg-to-diagram.js +53 -49
  143. package/dist/phase-diagram/utils.d.ts +1 -0
  144. package/dist/phase-diagram/utils.js +80 -25
  145. package/dist/plot/AxisLabel.svelte +28 -3
  146. package/dist/plot/BarPlot.svelte +1182 -734
  147. package/dist/plot/BarPlot.svelte.d.ts +2 -2
  148. package/dist/plot/BarPlotControls.svelte +31 -5
  149. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  150. package/dist/plot/ColorBar.svelte +479 -329
  151. package/dist/plot/ColorScaleSelect.svelte +27 -6
  152. package/dist/plot/ElementScatter.svelte +36 -15
  153. package/dist/plot/FillArea.svelte +152 -95
  154. package/dist/plot/Histogram.svelte +934 -571
  155. package/dist/plot/Histogram.svelte.d.ts +1 -1
  156. package/dist/plot/HistogramControls.svelte +53 -9
  157. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  158. package/dist/plot/InteractiveAxisLabel.svelte +34 -11
  159. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  160. package/dist/plot/Line.svelte +63 -28
  161. package/dist/plot/PlotControls.svelte +157 -114
  162. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  163. package/dist/plot/PlotLegend.svelte +174 -91
  164. package/dist/plot/PlotTooltip.svelte +45 -6
  165. package/dist/plot/PortalSelect.svelte +175 -147
  166. package/dist/plot/ReferenceLine.svelte +76 -22
  167. package/dist/plot/ReferenceLine3D.svelte +132 -107
  168. package/dist/plot/ReferencePlane.svelte +146 -121
  169. package/dist/plot/ScatterPlot.svelte +1681 -1091
  170. package/dist/plot/ScatterPlot.svelte.d.ts +2 -2
  171. package/dist/plot/ScatterPlot3D.svelte +256 -131
  172. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  173. package/dist/plot/ScatterPlot3DControls.svelte +113 -63
  174. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
  175. package/dist/plot/ScatterPlot3DScene.svelte +608 -403
  176. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  177. package/dist/plot/ScatterPlotControls.svelte +65 -25
  178. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  179. package/dist/plot/ScatterPoint.svelte +98 -26
  180. package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
  181. package/dist/plot/SpacegroupBarPlot.svelte +142 -85
  182. package/dist/plot/Surface3D.svelte +159 -108
  183. package/dist/plot/ZeroLines.svelte +55 -3
  184. package/dist/plot/ZoomRect.svelte +4 -2
  185. package/dist/plot/axis-utils.js +1 -3
  186. package/dist/plot/data-cleaning.js +12 -28
  187. package/dist/plot/data-transform.js +2 -1
  188. package/dist/plot/fill-utils.js +2 -0
  189. package/dist/plot/layout.d.ts +4 -1
  190. package/dist/plot/layout.js +33 -14
  191. package/dist/plot/reference-line.d.ts +2 -2
  192. package/dist/plot/reference-line.js +7 -5
  193. package/dist/plot/scales.js +24 -36
  194. package/dist/plot/types.d.ts +11 -23
  195. package/dist/plot/types.js +6 -11
  196. package/dist/plot/utils/label-placement.d.ts +32 -15
  197. package/dist/plot/utils/label-placement.js +227 -66
  198. package/dist/plot/utils/series-visibility.js +2 -3
  199. package/dist/rdf/RdfPlot.svelte +143 -91
  200. package/dist/rdf/calc-rdf.js +4 -5
  201. package/dist/sanitize.d.ts +4 -0
  202. package/dist/sanitize.js +107 -0
  203. package/dist/settings.d.ts +18 -6
  204. package/dist/settings.js +46 -16
  205. package/dist/spectral/Bands.svelte +632 -453
  206. package/dist/spectral/BandsAndDos.svelte +90 -49
  207. package/dist/spectral/BrillouinBandsDos.svelte +151 -93
  208. package/dist/spectral/Dos.svelte +389 -258
  209. package/dist/spectral/helpers.js +55 -43
  210. package/dist/state.svelte.d.ts +1 -1
  211. package/dist/state.svelte.js +3 -2
  212. package/dist/structure/Arrow.svelte +59 -20
  213. package/dist/structure/AtomLegend.svelte +215 -134
  214. package/dist/structure/Bond.svelte +73 -47
  215. package/dist/structure/CanvasTooltip.svelte +10 -2
  216. package/dist/structure/CellSelect.svelte +72 -45
  217. package/dist/structure/Cylinder.svelte +33 -17
  218. package/dist/structure/Lattice.svelte +88 -33
  219. package/dist/structure/Structure.svelte +1063 -797
  220. package/dist/structure/Structure.svelte.d.ts +1 -1
  221. package/dist/structure/StructureControls.svelte +349 -118
  222. package/dist/structure/StructureExportPane.svelte +124 -89
  223. package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
  224. package/dist/structure/StructureInfoPane.svelte +304 -237
  225. package/dist/structure/StructureScene.svelte +879 -443
  226. package/dist/structure/StructureScene.svelte.d.ts +15 -7
  227. package/dist/structure/atom-properties.js +8 -8
  228. package/dist/structure/bonding.js +6 -7
  229. package/dist/structure/export.js +14 -29
  230. package/dist/structure/ferrox-wasm.js +1 -1
  231. package/dist/structure/index.d.ts +13 -3
  232. package/dist/structure/index.js +83 -23
  233. package/dist/structure/measure.d.ts +2 -2
  234. package/dist/structure/measure.js +4 -44
  235. package/dist/structure/parse.js +113 -141
  236. package/dist/structure/partial-occupancy.js +7 -10
  237. package/dist/structure/pbc.d.ts +1 -0
  238. package/dist/structure/pbc.js +16 -6
  239. package/dist/structure/supercell.d.ts +2 -2
  240. package/dist/structure/supercell.js +12 -22
  241. package/dist/structure/validation.js +1 -2
  242. package/dist/symmetry/SymmetryStats.svelte +84 -41
  243. package/dist/symmetry/WyckoffTable.svelte +26 -6
  244. package/dist/symmetry/cell-transform.js +5 -3
  245. package/dist/symmetry/index.js +8 -7
  246. package/dist/symmetry/spacegroups.js +148 -148
  247. package/dist/table/HeatmapTable.svelte +790 -554
  248. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  249. package/dist/table/ToggleMenu.svelte +125 -92
  250. package/dist/table/index.js +2 -4
  251. package/dist/theme/ThemeControl.svelte +21 -12
  252. package/dist/time.js +4 -1
  253. package/dist/tooltip/TooltipContent.svelte +33 -8
  254. package/dist/trajectory/Trajectory.svelte +758 -558
  255. package/dist/trajectory/TrajectoryError.svelte +14 -3
  256. package/dist/trajectory/TrajectoryExportPane.svelte +137 -83
  257. package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
  258. package/dist/trajectory/extract.js +10 -26
  259. package/dist/trajectory/format-detect.js +5 -5
  260. package/dist/trajectory/frame-reader.d.ts +1 -1
  261. package/dist/trajectory/frame-reader.js +5 -12
  262. package/dist/trajectory/helpers.d.ts +0 -1
  263. package/dist/trajectory/helpers.js +2 -17
  264. package/dist/trajectory/index.js +14 -12
  265. package/dist/trajectory/parse/ase.js +5 -4
  266. package/dist/trajectory/parse/hdf5.js +26 -18
  267. package/dist/trajectory/parse/index.js +13 -18
  268. package/dist/trajectory/parse/lammps.js +17 -7
  269. package/dist/trajectory/parse/vasp.js +5 -2
  270. package/dist/trajectory/parse/xyz.js +8 -7
  271. package/dist/trajectory/plotting.js +13 -8
  272. package/dist/utils.d.ts +1 -0
  273. package/dist/utils.js +13 -0
  274. package/dist/xrd/XrdPlot.svelte +337 -247
  275. package/dist/xrd/broadening.js +14 -9
  276. package/dist/xrd/calc-xrd.js +12 -18
  277. package/dist/xrd/parse.d.ts +1 -1
  278. package/dist/xrd/parse.js +17 -17
  279. package/package.json +99 -103
  280. package/readme.md +1 -1
  281. /package/dist/theme/{themes.js → themes.mjs} +0 -0
@@ -1,178 +1,232 @@
1
- <script lang="ts">// Threlte component that renders isosurface meshes from volumetric data using marching cubes.
2
- // Supports multiple layers at different isovalues with independent colors,
3
- // plus positive/negative lobes and two-pass transparency.
4
- import { marching_cubes } from '../marching-cubes';
5
- import { T } from '@threlte/core';
6
- import { BackSide, BufferAttribute, BufferGeometry, DoubleSide, FrontSide, Uint32BufferAttribute, } from 'three';
7
- import { DEFAULT_ISOSURFACE_SETTINGS, downsample_grid, pad_periodic_grid, } from './types';
8
- let { volume, settings = DEFAULT_ISOSURFACE_SETTINGS, } = $props();
9
- // Resolve layers: use explicit layers array if provided, else build from single-isovalue settings
10
- let resolved_layers = $derived.by(() => {
11
- if (settings.layers?.length)
12
- return settings.layers;
1
+ <script lang="ts">
2
+ // Threlte component that renders isosurface meshes from volumetric data using marching cubes.
3
+ // Supports multiple layers at different isovalues with independent colors,
4
+ // plus positive/negative lobes and two-pass transparency.
5
+ import { marching_cubes } from '../marching-cubes'
6
+ import type { Matrix3x3, Vec3 } from '../math'
7
+ import { T } from '@threlte/core'
8
+ import {
9
+ BackSide,
10
+ BufferAttribute,
11
+ BufferGeometry,
12
+ DoubleSide,
13
+ FrontSide,
14
+ Uint32BufferAttribute,
15
+ } from 'three'
16
+ import type { IsosurfaceLayer, IsosurfaceSettings, VolumetricData } from './types'
17
+ import {
18
+ DEFAULT_ISOSURFACE_SETTINGS,
19
+ downsample_grid,
20
+ pad_periodic_grid,
21
+ } from './types'
22
+
23
+ let { volume, settings = DEFAULT_ISOSURFACE_SETTINGS }: {
24
+ volume: VolumetricData
25
+ settings?: IsosurfaceSettings
26
+ } = $props()
27
+
28
+ // Resolve layers: use explicit layers array if provided, else build from single-isovalue settings
29
+ let resolved_layers = $derived.by((): IsosurfaceLayer[] => {
30
+ if (settings.layers?.length) return settings.layers
13
31
  return [{
14
- isovalue: settings.isovalue,
15
- color: settings.positive_color,
16
- opacity: settings.opacity,
17
- visible: true,
18
- show_negative: settings.show_negative,
19
- negative_color: settings.negative_color,
20
- }];
21
- });
22
- // Build indexed BufferGeometry from marching cubes output.
23
- // Uses Three.js index buffer to avoid tripling vertex data, and
24
- // computeVertexNormals() for fast GPU-friendly normals.
25
- function build_geometry(vertices, faces) {
26
- if (vertices.length === 0 || faces.length === 0)
27
- return null;
32
+ isovalue: settings.isovalue,
33
+ color: settings.positive_color,
34
+ opacity: settings.opacity,
35
+ visible: true,
36
+ show_negative: settings.show_negative,
37
+ negative_color: settings.negative_color,
38
+ }]
39
+ })
40
+
41
+ // Build indexed BufferGeometry from marching cubes output.
42
+ // Uses Three.js index buffer to avoid tripling vertex data, and
43
+ // computeVertexNormals() for fast GPU-friendly normals.
44
+ function build_geometry(
45
+ vertices: Vec3[],
46
+ faces: number[][],
47
+ ): BufferGeometry | null {
48
+ if (vertices.length === 0 || faces.length === 0) return null
49
+
28
50
  // Flatten vertices: Vec3[] → Float32Array
29
- const positions = new Float32Array(vertices.length * 3);
51
+ const positions = new Float32Array(vertices.length * 3)
30
52
  for (let idx = 0; idx < vertices.length; idx++) {
31
- const vert = vertices[idx];
32
- positions[idx * 3] = vert[0];
33
- positions[idx * 3 + 1] = vert[1];
34
- positions[idx * 3 + 2] = vert[2];
53
+ const vert = vertices[idx]
54
+ positions[idx * 3] = vert[0]
55
+ positions[idx * 3 + 1] = vert[1]
56
+ positions[idx * 3 + 2] = vert[2]
35
57
  }
58
+
36
59
  // Flatten face indices: number[][] → Uint32Array
37
- const indices = new Uint32Array(faces.length * 3);
60
+ const indices = new Uint32Array(faces.length * 3)
38
61
  for (let idx = 0; idx < faces.length; idx++) {
39
- const face = faces[idx];
40
- indices[idx * 3] = face[0];
41
- indices[idx * 3 + 1] = face[1];
42
- indices[idx * 3 + 2] = face[2];
62
+ const face = faces[idx]
63
+ indices[idx * 3] = face[0]
64
+ indices[idx * 3 + 1] = face[1]
65
+ indices[idx * 3 + 2] = face[2]
43
66
  }
44
- const geometry = new BufferGeometry();
45
- geometry.setAttribute(`position`, new BufferAttribute(positions, 3));
46
- geometry.setIndex(new Uint32BufferAttribute(indices, 1));
47
- geometry.computeVertexNormals();
48
- geometry.computeBoundingSphere();
49
- return geometry;
50
- }
51
- // Downsample large grids once when volume changes to keep marching cubes interactive
52
- let ds_result = $derived.by(() => {
53
- if (!volume)
54
- return undefined;
55
- return downsample_grid(volume.grid, volume.grid_dims);
56
- });
57
- // Run marching cubes at the given isovalue with pre-prepared grid/lattice/shift.
58
- function extract_surface(isovalue, mc_grid, mc_lattice, origin_shift) {
59
- if (isovalue === 0)
60
- return null;
67
+
68
+ const geometry = new BufferGeometry()
69
+ geometry.setAttribute(`position`, new BufferAttribute(positions, 3))
70
+ geometry.setIndex(new Uint32BufferAttribute(indices, 1))
71
+ geometry.computeVertexNormals()
72
+ geometry.computeBoundingSphere()
73
+ return geometry
74
+ }
75
+
76
+ // Downsample large grids once when volume changes to keep marching cubes interactive
77
+ let ds_result = $derived.by(() => {
78
+ if (!volume) return undefined
79
+ return downsample_grid(volume.grid, volume.grid_dims)
80
+ })
81
+
82
+ // Run marching cubes at the given isovalue with pre-prepared grid/lattice/shift.
83
+ function extract_surface(
84
+ isovalue: number,
85
+ mc_grid: number[][][],
86
+ mc_lattice: Matrix3x3,
87
+ origin_shift: Vec3 | null,
88
+ ): BufferGeometry | null {
89
+ if (isovalue === 0) return null
90
+
61
91
  const result = marching_cubes(mc_grid, isovalue, mc_lattice, {
62
- periodic: false,
63
- interpolate: true,
64
- centered: false,
65
- normals: false,
66
- });
92
+ periodic: false,
93
+ interpolate: true,
94
+ centered: false,
95
+ normals: false,
96
+ })
97
+
67
98
  if (origin_shift) {
68
- for (const vert of result.vertices) {
69
- vert[0] += origin_shift[0];
70
- vert[1] += origin_shift[1];
71
- vert[2] += origin_shift[2];
72
- }
99
+ for (const vert of result.vertices) {
100
+ vert[0] += origin_shift[0]
101
+ vert[1] += origin_shift[1]
102
+ vert[2] += origin_shift[2]
103
+ }
73
104
  }
74
- return build_geometry(result.vertices, result.faces);
75
- }
76
- let active_geometries = $state([]);
77
- let raf_id = 0;
78
- let debounce_id = 0;
79
- // Dispose all current geometries
80
- function dispose_all() {
81
- for (const entry of active_geometries)
82
- entry.geometry.dispose();
83
- active_geometries = [];
84
- }
85
- // Dispose on unmount
86
- $effect(() => () => dispose_all());
87
- function rebuild_geometries(layers) {
88
- if (!volume || !ds_result)
89
- return;
90
- const old = active_geometries;
91
- const entries = [];
105
+
106
+ return build_geometry(result.vertices, result.faces)
107
+ }
108
+
109
+ // === Multi-layer geometry management ===
110
+ // Each layer produces up to 2 geometries (positive + optional negative lobe).
111
+ // Keyed by "layer_idx:sign" for cache invalidation.
112
+ type GeoEntry = {
113
+ geometry: BufferGeometry
114
+ color: string
115
+ opacity: number
116
+ render_order: number
117
+ }
118
+ let active_geometries = $state<GeoEntry[]>([])
119
+ let raf_id = 0
120
+ let debounce_id = 0
121
+
122
+ // Dispose all current geometries
123
+ function dispose_all() {
124
+ for (const entry of active_geometries) entry.geometry.dispose()
125
+ active_geometries = []
126
+ }
127
+
128
+ // Dispose on unmount
129
+ $effect(() => () => dispose_all())
130
+
131
+ function rebuild_geometries(layers: IsosurfaceLayer[]) {
132
+ if (!volume || !ds_result) return
133
+ const old = active_geometries
134
+ const entries: GeoEntry[] = []
135
+
92
136
  // Prepare grid/lattice/shift once for all layers.
93
137
  // When halo > 0 for periodic volumes, the downsampled grid is padded with
94
138
  // halo cells from the opposite face so isosurfaces extend beyond the unit
95
139
  // cell and close into complete enclosed shapes around boundary atoms.
96
- let mc_grid = ds_result.grid;
97
- let mc_lattice = volume.lattice;
98
- let origin_shift = null;
140
+ let mc_grid = ds_result.grid
141
+ let mc_lattice: Matrix3x3 = volume.lattice
142
+ let origin_shift: Vec3 | null = null
143
+
99
144
  if (settings.halo > 0 && volume.periodic) {
100
- const padded = pad_periodic_grid(ds_result.grid, ds_result.dims, settings.halo);
101
- mc_grid = padded.grid;
102
- // marching_cubes maps [0,1] fractional -> Cartesian via lattice.
103
- // The padded grid covers a wider fractional range, so scale the lattice
104
- // to match. Then shift all vertices by the fractional offset.
105
- const [la, lb, lc] = volume.lattice;
106
- const sx = padded.dims[0] / ds_result.dims[0];
107
- const sy = padded.dims[1] / ds_result.dims[1];
108
- const sz = padded.dims[2] / ds_result.dims[2];
109
- mc_lattice = [
110
- [la[0] * sx, la[1] * sx, la[2] * sx],
111
- [lb[0] * sy, lb[1] * sy, lb[2] * sy],
112
- [lc[0] * sz, lc[1] * sz, lc[2] * sz],
113
- ];
114
- const [ox, oy, oz] = padded.offset;
115
- origin_shift = [
116
- ox * la[0] + oy * lb[0] + oz * lc[0],
117
- ox * la[1] + oy * lb[1] + oz * lc[1],
118
- ox * la[2] + oy * lb[2] + oz * lc[2],
119
- ];
145
+ const padded = pad_periodic_grid(ds_result.grid, ds_result.dims, settings.halo)
146
+ mc_grid = padded.grid
147
+ // marching_cubes maps [0,1] fractional -> Cartesian via lattice.
148
+ // The padded grid covers a wider fractional range, so scale the lattice
149
+ // to match. Then shift all vertices by the fractional offset.
150
+ const [la, lb, lc] = volume.lattice
151
+ const sx = padded.dims[0] / ds_result.dims[0]
152
+ const sy = padded.dims[1] / ds_result.dims[1]
153
+ const sz = padded.dims[2] / ds_result.dims[2]
154
+ mc_lattice = [
155
+ [la[0] * sx, la[1] * sx, la[2] * sx],
156
+ [lb[0] * sy, lb[1] * sy, lb[2] * sy],
157
+ [lc[0] * sz, lc[1] * sz, lc[2] * sz],
158
+ ]
159
+ const [ox, oy, oz] = padded.offset
160
+ origin_shift = [
161
+ ox * la[0] + oy * lb[0] + oz * lc[0],
162
+ ox * la[1] + oy * lb[1] + oz * lc[1],
163
+ ox * la[2] + oy * lb[2] + oz * lc[2],
164
+ ]
120
165
  }
121
- const surface_at = (isovalue) => extract_surface(isovalue, mc_grid, mc_lattice, origin_shift);
166
+
167
+ const surface_at = (isovalue: number) =>
168
+ extract_surface(isovalue, mc_grid, mc_lattice, origin_shift)
169
+
122
170
  // Render lower-isovalue (outer) shells earlier so per-layer back/front passes
123
171
  // interleave back-to-front across shells and reduce transparency artefacts.
124
- const layer_render_rank = new Map(layers
172
+ const layer_render_rank = new Map<number, number>(
173
+ layers
125
174
  .map((layer, layer_idx) => ({ layer_idx, isovalue: layer.isovalue }))
126
175
  .sort((layer_a, layer_b) => layer_a.isovalue - layer_b.isovalue)
127
- .map(({ layer_idx }, rank) => [layer_idx, rank]));
176
+ .map(({ layer_idx }, rank) => [layer_idx, rank]),
177
+ )
178
+
128
179
  for (let layer_idx = 0; layer_idx < layers.length; layer_idx++) {
129
- const layer = layers[layer_idx];
130
- if (!layer.visible || layer.isovalue <= 0)
131
- continue;
132
- // Each layer gets 4 slots (positive back/front + negative back/front).
133
- const base_order = (layer_render_rank.get(layer_idx) ?? layer_idx) * 4;
134
- const pos_geo = surface_at(layer.isovalue);
135
- if (pos_geo) {
136
- entries.push({
137
- geometry: pos_geo,
138
- color: layer.color,
139
- opacity: layer.opacity,
140
- render_order: base_order,
141
- });
142
- }
143
- if (layer.show_negative) {
144
- const neg_geo = surface_at(-layer.isovalue);
145
- if (neg_geo) {
146
- entries.push({
147
- geometry: neg_geo,
148
- color: layer.negative_color,
149
- opacity: layer.opacity,
150
- render_order: base_order + 2,
151
- });
152
- }
180
+ const layer = layers[layer_idx]
181
+ if (!layer.visible || layer.isovalue <= 0) continue
182
+
183
+ // Each layer gets 4 slots (positive back/front + negative back/front).
184
+ const base_order = (layer_render_rank.get(layer_idx) ?? layer_idx) * 4
185
+
186
+ const pos_geo = surface_at(layer.isovalue)
187
+ if (pos_geo) {
188
+ entries.push({
189
+ geometry: pos_geo,
190
+ color: layer.color,
191
+ opacity: layer.opacity,
192
+ render_order: base_order,
193
+ })
194
+ }
195
+
196
+ if (layer.show_negative) {
197
+ const neg_geo = surface_at(-layer.isovalue)
198
+ if (neg_geo) {
199
+ entries.push({
200
+ geometry: neg_geo,
201
+ color: layer.negative_color,
202
+ opacity: layer.opacity,
203
+ render_order: base_order + 2,
204
+ })
153
205
  }
206
+ }
154
207
  }
155
- active_geometries = entries;
156
- for (const entry of old)
157
- entry.geometry.dispose();
158
- }
159
- // Rebuild all layer geometries when layers or volume change.
160
- // Debounces rapid changes (e.g. slider drags) to avoid repeated expensive marching cubes.
161
- $effect(() => {
162
- const layers = resolved_layers;
163
- void settings.halo;
208
+
209
+ active_geometries = entries
210
+ for (const entry of old) entry.geometry.dispose()
211
+ }
212
+
213
+ // Rebuild all layer geometries when layers or volume change.
214
+ // Debounces rapid changes (e.g. slider drags) to avoid repeated expensive marching cubes.
215
+ $effect(() => {
216
+ const layers = resolved_layers
217
+ void settings.halo
164
218
  if (!ds_result) {
165
- dispose_all();
166
- return;
219
+ dispose_all()
220
+ return
167
221
  }
168
222
  debounce_id = window.setTimeout(() => {
169
- raf_id = requestAnimationFrame(() => rebuild_geometries(layers));
170
- }, 50);
223
+ raf_id = requestAnimationFrame(() => rebuild_geometries(layers))
224
+ }, 50)
171
225
  return () => {
172
- clearTimeout(debounce_id);
173
- cancelAnimationFrame(raf_id);
174
- };
175
- });
226
+ clearTimeout(debounce_id)
227
+ cancelAnimationFrame(raf_id)
228
+ }
229
+ })
176
230
  </script>
177
231
 
178
232
  <!-- Render each geometry entry with appropriate material -->
@@ -1,35 +1,53 @@
1
- <script lang="ts">// Controls panel for isosurface visualization settings (isovalue, opacity, colors, etc.)
2
- // Supports both single-isovalue mode and multi-layer mode.
3
- import { format_num } from '../labels';
4
- import { SettingsSection } from '../layout';
5
- import { tooltip } from 'svelte-multiselect/attachments';
6
- import { DEFAULT_ISOSURFACE_SETTINGS, generate_layers } from './types';
7
- let { settings = $bindable({ ...DEFAULT_ISOSURFACE_SETTINGS }), volumes = [], active_volume_idx = $bindable(0), } = $props();
8
- // Clamp active_volume_idx when volumes list changes (e.g. dataset swap)
9
- $effect(() => {
1
+ <script lang="ts">
2
+ // Controls panel for isosurface visualization settings (isovalue, opacity, colors, etc.)
3
+ // Supports both single-isovalue mode and multi-layer mode.
4
+ import { format_num } from '../labels'
5
+ import { SettingsSection } from '../layout'
6
+ import { tooltip } from 'svelte-multiselect/attachments'
7
+ import type { IsosurfaceLayer, IsosurfaceSettings, VolumetricData } from './types'
8
+ import { DEFAULT_ISOSURFACE_SETTINGS, generate_layers } from './types'
9
+
10
+ let {
11
+ settings = $bindable({ ...DEFAULT_ISOSURFACE_SETTINGS }),
12
+ volumes = [],
13
+ active_volume_idx = $bindable(0),
14
+ }: {
15
+ settings?: IsosurfaceSettings
16
+ volumes?: VolumetricData[]
17
+ active_volume_idx?: number
18
+ } = $props()
19
+
20
+ // Clamp active_volume_idx when volumes list changes (e.g. dataset swap)
21
+ $effect(() => {
10
22
  if (volumes.length > 0 && active_volume_idx >= volumes.length) {
11
- active_volume_idx = 0;
23
+ active_volume_idx = 0
12
24
  }
13
- });
14
- // Use precomputed data_range from the active volume
15
- let data_range = $derived(volumes[active_volume_idx]?.data_range ?? { min: 0, max: 1, abs_max: 1, mean: 0 });
16
- let slider_max = $derived(Math.max(data_range.abs_max, 0.001));
17
- let step = $derived(slider_max / 200);
18
- let is_multi_layer = $derived((settings.layers?.length ?? 0) > 0);
19
- let n_layers = $derived(settings.layers?.length ?? 1);
20
- function set_layer_count(count) {
25
+ })
26
+
27
+ // Use precomputed data_range from the active volume
28
+ let data_range = $derived(
29
+ volumes[active_volume_idx]?.data_range ?? { min: 0, max: 1, abs_max: 1, mean: 0 },
30
+ )
31
+
32
+ let slider_max = $derived(Math.max(data_range.abs_max, 0.001))
33
+ let step = $derived(slider_max / 200)
34
+ let is_multi_layer = $derived((settings.layers?.length ?? 0) > 0)
35
+ let n_layers = $derived(settings.layers?.length ?? 1)
36
+
37
+ function set_layer_count(count: number) {
21
38
  if (count <= 1) {
22
- settings.layers = undefined;
39
+ settings.layers = undefined
40
+ } else {
41
+ settings.layers = generate_layers(data_range, count)
23
42
  }
24
- else {
25
- settings.layers = generate_layers(data_range, count);
26
- }
27
- }
28
- function update_layer(idx, updates) {
29
- if (!settings.layers)
30
- return;
31
- settings.layers = settings.layers.map((layer, layer_idx) => layer_idx === idx ? { ...layer, ...updates } : layer);
32
- }
43
+ }
44
+
45
+ function update_layer(idx: number, updates: Partial<IsosurfaceLayer>) {
46
+ if (!settings.layers) return
47
+ settings.layers = settings.layers.map((layer, layer_idx) =>
48
+ layer_idx === idx ? { ...layer, ...updates } : layer
49
+ )
50
+ }
33
51
  </script>
34
52
 
35
53
  <SettingsSection
@@ -1,11 +1,10 @@
1
1
  // Parsers for volumetric data file formats (VASP CHGCAR, Gaussian .cube)
2
- import { VASP_VOLUMETRIC_REGEX } from '../constants';
2
+ import { COMPRESSION_EXTENSIONS_REGEX, VASP_VOLUMETRIC_REGEX } from '../constants';
3
3
  import { ELEM_SYMBOLS } from '../labels';
4
4
  import * as math from '../math';
5
+ import { wrap_to_unit_cell } from '../structure/pbc';
5
6
  // Bohr radius in Angstroms (for Gaussian .cube unit conversion)
6
7
  const BOHR_TO_ANGSTROM = 0.529177249;
7
- // Wrap a value to [0, 1) range for fractional coordinates
8
- const wrap_frac = (val) => val - Math.floor(val);
9
8
  // === Fast number parsing utilities ===
10
9
  // Parse whitespace-separated numbers directly from a string, starting at `pos`.
11
10
  // Writes into a pre-allocated Float64Array and returns { count, end_pos }.
@@ -87,11 +86,9 @@ function read_lines(text, pos, count) {
87
86
  }
88
87
  return { lines: result, next: pos };
89
88
  }
90
- function build_grid({ data, nx, ny, nz, divisor = 1, data_order = `z_fastest` }) {
89
+ function build_grid({ data, nx, ny, nz, divisor = 1, data_order = `z_fastest`, }) {
91
90
  const grid = new Array(nx);
92
- let min_val = Infinity;
93
- let max_val = -Infinity;
94
- let sum = 0;
91
+ let [min_val, max_val, sum] = [Infinity, -Infinity, 0];
95
92
  const total = nx * ny * nz;
96
93
  const data_len = Math.min(data.length, total);
97
94
  if (data_len === 0) {
@@ -135,8 +132,7 @@ function build_grid({ data, nx, ny, nz, divisor = 1, data_order = `z_fastest` })
135
132
  plane[iy] = new Array(nz).fill(0);
136
133
  grid[ix] = plane;
137
134
  }
138
- let flat_idx = 0;
139
- let data_exhausted = false;
135
+ let [flat_idx, data_exhausted] = [0, false];
140
136
  for (let iz = 0; iz < nz; iz++) {
141
137
  for (let iy = 0; iy < ny; iy++) {
142
138
  for (let ix = 0; ix < nx; ix++) {
@@ -165,8 +161,8 @@ function build_grid({ data, nx, ny, nz, divisor = 1, data_order = `z_fastest` })
165
161
  return { grid, data_range };
166
162
  }
167
163
  // === CHGCAR Parser ===
168
- // Parse VASP CHGCAR/AECCAR/ELFCAR/LOCPOT file format.
169
- // CHGCAR consists of a POSCAR header followed by volumetric data on a 3D grid.
164
+ // Parse VASP CHGCAR/AECCAR/ELFCAR/LOCPOT/PARCHG file format.
165
+ // CHGCAR/PARCHG consists of a POSCAR header followed by volumetric data on a 3D grid.
170
166
  // Spin-polarized files contain two data blocks (total charge + magnetization).
171
167
  export function parse_chgcar(content) {
172
168
  // Strip leading whitespace
@@ -240,8 +236,16 @@ export function parse_chgcar(content) {
240
236
  const is_direct = cur.line.trim().toUpperCase().startsWith(`D`);
241
237
  pos = cur.next;
242
238
  // Parse atomic positions
243
- const lattice_transposed = math.transpose_3x3_matrix(lattice);
244
- const lattice_inv = math.matrix_inverse_3x3(lattice_transposed);
239
+ let cart_to_frac;
240
+ let frac_to_cart;
241
+ try {
242
+ ;
243
+ ({ cart_to_frac, frac_to_cart } = math.create_lattice_converters(lattice));
244
+ }
245
+ catch {
246
+ console.error(`CHGCAR: lattice matrix is singular; cannot convert coordinates`);
247
+ return null;
248
+ }
245
249
  const sites = [];
246
250
  let atom_idx = 0;
247
251
  for (let elem_idx = 0; elem_idx < element_symbols.length; elem_idx++) {
@@ -259,13 +263,13 @@ export function parse_chgcar(content) {
259
263
  let abc;
260
264
  let xyz;
261
265
  if (is_direct) {
262
- abc = [wrap_frac(coords[0]), wrap_frac(coords[1]), wrap_frac(coords[2])];
263
- xyz = math.mat3x3_vec3_multiply(lattice_transposed, abc);
266
+ abc = wrap_to_unit_cell(coords);
267
+ xyz = frac_to_cart(abc);
264
268
  }
265
269
  else {
266
270
  xyz = math.scale(coords, scale_factor);
267
- const raw = math.mat3x3_vec3_multiply(lattice_inv, xyz);
268
- abc = [wrap_frac(raw[0]), wrap_frac(raw[1]), wrap_frac(raw[2])];
271
+ const raw = cart_to_frac(xyz);
272
+ abc = wrap_to_unit_cell(raw);
269
273
  }
270
274
  sites.push({
271
275
  species: [{ element, occu: 1, oxidation_state: 0 }],
@@ -414,21 +418,22 @@ export function parse_cube(content, options = {}) {
414
418
  const is_periodic = options.periodic ?? Math.hypot(...origin) < 1e-6;
415
419
  // Parse atomic positions
416
420
  const sites = [];
417
- const lattice_transposed = math.transpose_3x3_matrix(lattice);
418
- let lattice_inv;
421
+ let cube_cart_to_frac;
419
422
  try {
420
- lattice_inv = math.matrix_inverse_3x3(lattice_transposed);
423
+ cube_cart_to_frac = math.create_cart_to_frac(lattice);
421
424
  }
422
425
  catch {
423
426
  // Non-periodic system (molecule), use identity
424
- lattice_inv = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
427
+ cube_cart_to_frac = (v) => [v[0], v[1], v[2]];
425
428
  }
426
429
  for (let atom_idx = 0; atom_idx < n_atoms; atom_idx++) {
427
430
  const cur = read_line(content, pos);
428
431
  const atom_line = cur.line.trim().split(/\s+/).map(Number);
429
432
  pos = cur.next;
430
433
  // Validate: need atomic_number, charge, x, y, z (5 tokens, indices 2-4 finite)
431
- if (atom_line.length < 5 || !isFinite(atom_line[2]) || !isFinite(atom_line[3]) ||
434
+ if (atom_line.length < 5 ||
435
+ !isFinite(atom_line[2]) ||
436
+ !isFinite(atom_line[3]) ||
432
437
  !isFinite(atom_line[4])) {
433
438
  console.warn(`.cube atom ${atom_idx}: malformed line "${cur.line.trim()}", skipping`);
434
439
  continue;
@@ -438,7 +443,7 @@ export function parse_cube(content, options = {}) {
438
443
  // Convert Cartesian to fractional, accounting for origin offset.
439
444
  // Store lattice-frame xyz (shifted) so abc and xyz stay consistent.
440
445
  const xyz = math.subtract(raw_xyz, origin);
441
- const abc = math.mat3x3_vec3_multiply(lattice_inv, xyz);
446
+ const abc = cube_cart_to_frac(xyz);
442
447
  const element = atomic_number_to_symbol(atom_line[0]);
443
448
  sites.push({
444
449
  species: [{ element, occu: 1, oxidation_state: 0 }],
@@ -481,7 +486,8 @@ export function parse_cube(content, options = {}) {
481
486
  nz: n_grid[2],
482
487
  data_order: `z_fastest`,
483
488
  });
484
- const volumes = [{
489
+ const volumes = [
490
+ {
485
491
  grid,
486
492
  grid_dims: n_grid,
487
493
  lattice,
@@ -490,21 +496,20 @@ export function parse_cube(content, options = {}) {
490
496
  data_order: `z_fastest`,
491
497
  periodic: is_periodic, // periodic systems wrap; molecular .cube files include both endpoints
492
498
  label: `volumetric data`,
493
- }];
499
+ },
500
+ ];
494
501
  return { structure, volumes };
495
502
  }
496
503
  // Convert atomic number to element symbol using ELEM_SYMBOLS (1-indexed: H=1, He=2, ...)
497
504
  function atomic_number_to_symbol(atomic_number) {
498
505
  // ELEM_SYMBOLS is 0-indexed (H at index 0), atomic numbers are 1-indexed
499
506
  const idx = atomic_number - 1;
500
- return (idx >= 0 && idx < ELEM_SYMBOLS.length
501
- ? ELEM_SYMBOLS[idx]
502
- : `H`);
507
+ return (idx >= 0 && idx < ELEM_SYMBOLS.length ? ELEM_SYMBOLS[idx] : `H`);
503
508
  }
504
509
  // Auto-detect and parse volumetric file format based on filename and content
505
510
  export function parse_volumetric_file(content, filename) {
506
511
  // Strip compression suffixes so "CHGCAR.gz" and "molecule.cube.bz2" match correctly
507
- const lower_name = (filename ?? ``).toLowerCase().replace(/\.(gz|bz2|xz|zst)$/, ``);
512
+ const lower_name = (filename ?? ``).toLowerCase().replace(COMPRESSION_EXTENSIONS_REGEX, ``);
508
513
  // Extension-based detection
509
514
  if (lower_name.endsWith(`.cube`))
510
515
  return parse_cube(content);