matterviz 0.3.1 → 0.3.2

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 (257) hide show
  1. package/dist/FilePicker.svelte +37 -20
  2. package/dist/Icon.svelte +2 -2
  3. package/dist/app.css +29 -0
  4. package/dist/brillouin/BrillouinZone.svelte +19 -61
  5. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  6. package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
  7. package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
  8. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  9. package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
  10. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  11. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
  12. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  13. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
  14. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  15. package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
  16. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  17. package/dist/chempot-diagram/color.d.ts +10 -0
  18. package/dist/chempot-diagram/color.js +33 -0
  19. package/dist/chempot-diagram/compute.d.ts +38 -0
  20. package/dist/chempot-diagram/compute.js +650 -0
  21. package/dist/chempot-diagram/index.d.ts +5 -0
  22. package/dist/chempot-diagram/index.js +5 -0
  23. package/dist/chempot-diagram/pointer.d.ts +16 -0
  24. package/dist/chempot-diagram/pointer.js +40 -0
  25. package/dist/chempot-diagram/temperature.d.ts +15 -0
  26. package/dist/chempot-diagram/temperature.js +37 -0
  27. package/dist/chempot-diagram/types.d.ts +83 -0
  28. package/dist/chempot-diagram/types.js +27 -0
  29. package/dist/colors/index.d.ts +3 -1
  30. package/dist/colors/index.js +4 -0
  31. package/dist/composition/BarChart.svelte +13 -22
  32. package/dist/composition/BubbleChart.svelte +5 -3
  33. package/dist/composition/FormulaFilter.svelte +586 -94
  34. package/dist/composition/FormulaFilter.svelte.d.ts +35 -1
  35. package/dist/composition/PieChart.svelte +43 -18
  36. package/dist/composition/PieChart.svelte.d.ts +1 -1
  37. package/dist/convex-hull/ConvexHull.svelte +4 -2
  38. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  39. package/dist/convex-hull/ConvexHull2D.svelte +13 -44
  40. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  41. package/dist/convex-hull/ConvexHull3D.svelte +16 -7
  42. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  43. package/dist/convex-hull/ConvexHull4D.svelte +17 -7
  44. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  45. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHullStats.svelte +701 -226
  47. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
  48. package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
  49. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  50. package/dist/convex-hull/demo-temperature.js +36 -0
  51. package/dist/convex-hull/helpers.d.ts +1 -1
  52. package/dist/convex-hull/helpers.js +2 -4
  53. package/dist/convex-hull/index.d.ts +1 -0
  54. package/dist/convex-hull/index.js +1 -0
  55. package/dist/convex-hull/thermodynamics.d.ts +8 -21
  56. package/dist/convex-hull/thermodynamics.js +106 -17
  57. package/dist/convex-hull/types.d.ts +5 -0
  58. package/dist/convex-hull/types.js +5 -0
  59. package/dist/coordination/CoordinationBarPlot.svelte +29 -46
  60. package/dist/element/BohrAtom.svelte +1 -1
  61. package/dist/element/data.js +2 -14
  62. package/dist/element/data.json.gz +0 -0
  63. package/dist/element/types.d.ts +1 -0
  64. package/dist/fermi-surface/FermiSurface.svelte +20 -64
  65. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  66. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  67. package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
  68. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  69. package/dist/fermi-surface/parse.js +16 -22
  70. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
  71. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  72. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
  73. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
  74. package/dist/heatmap-matrix/index.d.ts +53 -0
  75. package/dist/heatmap-matrix/index.js +100 -0
  76. package/dist/heatmap-matrix/shared.d.ts +2 -0
  77. package/dist/heatmap-matrix/shared.js +4 -0
  78. package/dist/icons.d.ts +111 -0
  79. package/dist/icons.js +111 -0
  80. package/dist/index.d.ts +3 -1
  81. package/dist/index.js +3 -1
  82. package/dist/io/export.js +15 -3
  83. package/dist/io/file-drop.d.ts +7 -0
  84. package/dist/io/file-drop.js +43 -0
  85. package/dist/io/index.d.ts +2 -2
  86. package/dist/io/index.js +2 -112
  87. package/dist/io/types.d.ts +1 -0
  88. package/dist/io/url-drop.d.ts +2 -0
  89. package/dist/io/url-drop.js +118 -0
  90. package/dist/isosurface/Isosurface.svelte +101 -45
  91. package/dist/isosurface/IsosurfaceControls.svelte +19 -0
  92. package/dist/isosurface/parse.js +73 -30
  93. package/dist/isosurface/slice.d.ts +2 -1
  94. package/dist/isosurface/slice.js +3 -3
  95. package/dist/isosurface/types.d.ts +13 -1
  96. package/dist/isosurface/types.js +98 -0
  97. package/dist/labels.d.ts +2 -1
  98. package/dist/labels.js +1 -0
  99. package/dist/layout/InfoTag.svelte +62 -62
  100. package/dist/layout/SubpageGrid.svelte +74 -0
  101. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  102. package/dist/layout/index.d.ts +1 -0
  103. package/dist/layout/index.js +1 -0
  104. package/dist/layout/json-tree/JsonNode.svelte +83 -85
  105. package/dist/layout/json-tree/JsonTree.svelte +20 -19
  106. package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
  107. package/dist/layout/json-tree/JsonValue.svelte +196 -116
  108. package/dist/layout/json-tree/types.d.ts +10 -2
  109. package/dist/layout/json-tree/utils.d.ts +2 -0
  110. package/dist/layout/json-tree/utils.js +33 -0
  111. package/dist/math.d.ts +7 -0
  112. package/dist/math.js +358 -7
  113. package/dist/overlays/ContextMenu.svelte +3 -2
  114. package/dist/overlays/DraggablePane.svelte +163 -58
  115. package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
  116. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
  117. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
  118. package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
  119. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
  120. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
  121. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  122. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
  123. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
  124. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
  125. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
  126. package/dist/phase-diagram/index.d.ts +2 -0
  127. package/dist/phase-diagram/index.js +2 -0
  128. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  129. package/dist/phase-diagram/svg-to-diagram.js +865 -0
  130. package/dist/phase-diagram/types.d.ts +10 -0
  131. package/dist/phase-diagram/utils.d.ts +7 -4
  132. package/dist/phase-diagram/utils.js +149 -59
  133. package/dist/plot/AxisLabel.svelte +26 -0
  134. package/dist/plot/AxisLabel.svelte.d.ts +16 -0
  135. package/dist/plot/BarPlot.svelte +473 -228
  136. package/dist/plot/BarPlot.svelte.d.ts +3 -3
  137. package/dist/plot/BarPlotControls.svelte +3 -2
  138. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  139. package/dist/plot/ColorBar.svelte +54 -54
  140. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  141. package/dist/plot/ColorScaleSelect.svelte +1 -1
  142. package/dist/plot/ElementScatter.svelte +3 -2
  143. package/dist/plot/FillArea.svelte +4 -1
  144. package/dist/plot/Histogram.svelte +320 -230
  145. package/dist/plot/Histogram.svelte.d.ts +2 -2
  146. package/dist/plot/HistogramControls.svelte +29 -10
  147. package/dist/plot/HistogramControls.svelte.d.ts +6 -2
  148. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
  149. package/dist/plot/PlotControls.svelte +109 -27
  150. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  151. package/dist/plot/PlotLegend.svelte +1 -1
  152. package/dist/plot/PortalSelect.svelte +2 -1
  153. package/dist/plot/ReferenceLine.svelte +2 -1
  154. package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
  155. package/dist/plot/ReferencePlane.svelte +1 -3
  156. package/dist/plot/ScatterPlot.svelte +343 -209
  157. package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
  158. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  159. package/dist/plot/ScatterPlot3DControls.svelte +203 -250
  160. package/dist/plot/ScatterPlot3DScene.svelte +4 -7
  161. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  162. package/dist/plot/ScatterPlotControls.svelte +95 -55
  163. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  164. package/dist/plot/ZeroLines.svelte +44 -0
  165. package/dist/plot/ZeroLines.svelte.d.ts +32 -0
  166. package/dist/plot/ZoomRect.svelte +21 -0
  167. package/dist/plot/ZoomRect.svelte.d.ts +8 -0
  168. package/dist/plot/axis-utils.d.ts +1 -1
  169. package/dist/plot/index.d.ts +6 -2
  170. package/dist/plot/index.js +6 -2
  171. package/dist/plot/interactions.d.ts +8 -10
  172. package/dist/plot/interactions.js +2 -3
  173. package/dist/plot/layout.d.ts +7 -1
  174. package/dist/plot/layout.js +12 -4
  175. package/dist/plot/reference-line.d.ts +4 -21
  176. package/dist/plot/reference-line.js +7 -81
  177. package/dist/plot/types.d.ts +42 -17
  178. package/dist/plot/types.js +10 -0
  179. package/dist/plot/utils/label-placement.js +13 -10
  180. package/dist/plot/utils.d.ts +1 -0
  181. package/dist/plot/utils.js +14 -0
  182. package/dist/rdf/RdfPlot.svelte +55 -66
  183. package/dist/settings.d.ts +3 -0
  184. package/dist/settings.js +17 -3
  185. package/dist/spectral/Bands.svelte +515 -143
  186. package/dist/spectral/Bands.svelte.d.ts +22 -2
  187. package/dist/spectral/helpers.d.ts +23 -1
  188. package/dist/spectral/helpers.js +65 -9
  189. package/dist/spectral/types.d.ts +2 -0
  190. package/dist/structure/AtomLegend.svelte +29 -8
  191. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  192. package/dist/structure/CellSelect.svelte +92 -22
  193. package/dist/structure/Structure.svelte +108 -118
  194. package/dist/structure/Structure.svelte.d.ts +1 -1
  195. package/dist/structure/StructureControls.svelte +25 -22
  196. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  197. package/dist/structure/StructureInfoPane.svelte +7 -1
  198. package/dist/structure/StructureScene.svelte +104 -66
  199. package/dist/structure/StructureScene.svelte.d.ts +2 -1
  200. package/dist/structure/atom-properties.d.ts +6 -2
  201. package/dist/structure/atom-properties.js +38 -25
  202. package/dist/structure/export.js +10 -7
  203. package/dist/structure/ferrox-wasm-types.d.ts +3 -2
  204. package/dist/structure/ferrox-wasm-types.js +0 -3
  205. package/dist/structure/ferrox-wasm.d.ts +3 -2
  206. package/dist/structure/ferrox-wasm.js +1 -2
  207. package/dist/structure/index.d.ts +6 -0
  208. package/dist/structure/index.js +22 -0
  209. package/dist/structure/parse.js +19 -16
  210. package/dist/structure/partial-occupancy.d.ts +25 -0
  211. package/dist/structure/partial-occupancy.js +102 -0
  212. package/dist/structure/validation.js +6 -3
  213. package/dist/symmetry/SymmetryStats.svelte +18 -4
  214. package/dist/symmetry/WyckoffTable.svelte +18 -10
  215. package/dist/symmetry/index.d.ts +7 -4
  216. package/dist/symmetry/index.js +83 -18
  217. package/dist/table/HeatmapTable.svelte +425 -65
  218. package/dist/table/HeatmapTable.svelte.d.ts +12 -1
  219. package/dist/table/ToggleMenu.svelte +2 -0
  220. package/dist/table/index.d.ts +2 -0
  221. package/dist/trajectory/Trajectory.svelte +147 -145
  222. package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
  223. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
  224. package/dist/trajectory/constants.d.ts +6 -0
  225. package/dist/trajectory/constants.js +7 -0
  226. package/dist/trajectory/extract.js +3 -5
  227. package/dist/trajectory/format-detect.d.ts +9 -0
  228. package/dist/trajectory/format-detect.js +76 -0
  229. package/dist/trajectory/frame-reader.d.ts +17 -0
  230. package/dist/trajectory/frame-reader.js +339 -0
  231. package/dist/trajectory/helpers.d.ts +15 -0
  232. package/dist/trajectory/helpers.js +187 -0
  233. package/dist/trajectory/index.d.ts +1 -0
  234. package/dist/trajectory/index.js +11 -4
  235. package/dist/trajectory/parse/ase.d.ts +2 -0
  236. package/dist/trajectory/parse/ase.js +76 -0
  237. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  238. package/dist/trajectory/parse/hdf5.js +121 -0
  239. package/dist/trajectory/parse/index.d.ts +12 -0
  240. package/dist/trajectory/parse/index.js +304 -0
  241. package/dist/trajectory/parse/lammps.d.ts +5 -0
  242. package/dist/trajectory/parse/lammps.js +169 -0
  243. package/dist/trajectory/parse/vasp.d.ts +2 -0
  244. package/dist/trajectory/parse/vasp.js +65 -0
  245. package/dist/trajectory/parse/xyz.d.ts +2 -0
  246. package/dist/trajectory/parse/xyz.js +109 -0
  247. package/dist/trajectory/types.d.ts +11 -0
  248. package/dist/trajectory/types.js +1 -0
  249. package/dist/utils.d.ts +2 -0
  250. package/dist/utils.js +4 -0
  251. package/dist/xrd/XrdPlot.svelte +6 -4
  252. package/dist/xrd/calc-xrd.js +0 -1
  253. package/package.json +30 -24
  254. package/readme.md +4 -4
  255. package/dist/trajectory/parse.d.ts +0 -42
  256. package/dist/trajectory/parse.js +0 -1267
  257. /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
@@ -4,7 +4,7 @@
4
4
  import { marching_cubes } from '../marching-cubes';
5
5
  import { T } from '@threlte/core';
6
6
  import { BackSide, BufferAttribute, BufferGeometry, DoubleSide, FrontSide, Uint32BufferAttribute, } from 'three';
7
- import { DEFAULT_ISOSURFACE_SETTINGS } from './types';
7
+ import { DEFAULT_ISOSURFACE_SETTINGS, downsample_grid, pad_periodic_grid, } from './types';
8
8
  let { volume, settings = DEFAULT_ISOSURFACE_SETTINGS, } = $props();
9
9
  // Resolve layers: use explicit layers array if provided, else build from single-isovalue settings
10
10
  let resolved_layers = $derived.by(() => {
@@ -48,22 +48,34 @@ function build_geometry(vertices, faces) {
48
48
  geometry.computeBoundingSphere();
49
49
  return geometry;
50
50
  }
51
- // Run marching cubes at the given isovalue.
52
- // Uses volume.periodic for correct coordinate scaling:
53
- // periodic grids (CHGCAR) use 1/N spacing, non-periodic (.cube) use 1/(N-1).
54
- function extract_surface(isovalue) {
55
- if (!volume || isovalue === 0)
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)
56
60
  return null;
57
- const result = marching_cubes(volume.grid, isovalue, volume.lattice, {
58
- periodic: volume.periodic,
61
+ const result = marching_cubes(mc_grid, isovalue, mc_lattice, {
62
+ periodic: false,
59
63
  interpolate: true,
60
64
  centered: false,
61
65
  normals: false,
62
66
  });
67
+ 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
+ }
73
+ }
63
74
  return build_geometry(result.vertices, result.faces);
64
75
  }
65
76
  let active_geometries = $state([]);
66
77
  let raf_id = 0;
78
+ let debounce_id = 0;
67
79
  // Dispose all current geometries
68
80
  function dispose_all() {
69
81
  for (const entry of active_geometries)
@@ -72,50 +84,94 @@ function dispose_all() {
72
84
  }
73
85
  // Dispose on unmount
74
86
  $effect(() => () => dispose_all());
75
- // Rebuild all layer geometries on next animation frame when layers or volume change
76
- $effect(() => {
77
- const layers = resolved_layers;
78
- const vol = volume;
79
- if (!vol) {
80
- dispose_all();
87
+ function rebuild_geometries(layers) {
88
+ if (!volume || !ds_result)
81
89
  return;
90
+ const old = active_geometries;
91
+ const entries = [];
92
+ // Prepare grid/lattice/shift once for all layers.
93
+ // When halo > 0 for periodic volumes, the downsampled grid is padded with
94
+ // halo cells from the opposite face so isosurfaces extend beyond the unit
95
+ // 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;
99
+ 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
+ ];
82
120
  }
83
- raf_id = requestAnimationFrame(() => {
84
- const old = active_geometries;
85
- const entries = [];
86
- for (let layer_idx = 0; layer_idx < layers.length; layer_idx++) {
87
- const layer = layers[layer_idx];
88
- if (!layer.visible || layer.isovalue <= 0)
89
- continue;
90
- // Render order: inner shells (higher isovalue) first for correct transparency.
91
- // Each layer gets 4 slots (positive back/front + negative back/front).
92
- const base_order = (layers.length - 1 - layer_idx) * 4;
93
- const pos_geo = extract_surface(layer.isovalue);
94
- if (pos_geo) {
121
+ const surface_at = (isovalue) => extract_surface(isovalue, mc_grid, mc_lattice, origin_shift);
122
+ // Render lower-isovalue (outer) shells earlier so per-layer back/front passes
123
+ // interleave back-to-front across shells and reduce transparency artefacts.
124
+ const layer_render_rank = new Map(layers
125
+ .map((layer, layer_idx) => ({ layer_idx, isovalue: layer.isovalue }))
126
+ .sort((layer_a, layer_b) => layer_a.isovalue - layer_b.isovalue)
127
+ .map(({ layer_idx }, rank) => [layer_idx, rank]));
128
+ 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) {
95
146
  entries.push({
96
- geometry: pos_geo,
97
- color: layer.color,
147
+ geometry: neg_geo,
148
+ color: layer.negative_color,
98
149
  opacity: layer.opacity,
99
- render_order: base_order,
150
+ render_order: base_order + 2,
100
151
  });
101
152
  }
102
- if (layer.show_negative) {
103
- const neg_geo = extract_surface(-layer.isovalue);
104
- if (neg_geo) {
105
- entries.push({
106
- geometry: neg_geo,
107
- color: layer.negative_color,
108
- opacity: layer.opacity,
109
- render_order: base_order + 2,
110
- });
111
- }
112
- }
113
153
  }
114
- active_geometries = entries;
115
- for (const entry of old)
116
- entry.geometry.dispose();
117
- });
118
- return () => cancelAnimationFrame(raf_id);
154
+ }
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;
164
+ if (!ds_result) {
165
+ dispose_all();
166
+ return;
167
+ }
168
+ debounce_id = window.setTimeout(() => {
169
+ raf_id = requestAnimationFrame(() => rebuild_geometries(layers));
170
+ }, 50);
171
+ return () => {
172
+ clearTimeout(debounce_id);
173
+ cancelAnimationFrame(raf_id);
174
+ };
119
175
  });
120
176
  </script>
121
177
 
@@ -39,6 +39,7 @@ function update_layer(idx, updates) {
39
39
  opacity: settings.opacity,
40
40
  show_negative: settings.show_negative,
41
41
  wireframe: settings.wireframe,
42
+ halo: settings.halo,
42
43
  layers: n_layers,
43
44
  }}
44
45
  on_reset={() => {
@@ -205,6 +206,24 @@ function update_layer(idx, updates) {
205
206
  </div>
206
207
  {/if}
207
208
 
209
+ {#if volumes?.[active_volume_idx]?.periodic}
210
+ <label
211
+ {@attach tooltip({
212
+ content:
213
+ `Extend isosurface beyond cell boundaries to close partial spheres (fraction of cell)`,
214
+ })}
215
+ >
216
+ Halo: {format_num(settings.halo, `.2f`)}
217
+ <input
218
+ type="range"
219
+ min={0}
220
+ max={0.5}
221
+ step={0.01}
222
+ bind:value={settings.halo}
223
+ />
224
+ </label>
225
+ {/if}
226
+
208
227
  {#if volumes[active_volume_idx]}
209
228
  <div class="grid-info">
210
229
  {volumes[active_volume_idx].grid_dims.join(` × `)} grid &nbsp;|&nbsp; [{
@@ -87,13 +87,11 @@ function read_lines(text, pos, count) {
87
87
  }
88
88
  return { lines: result, next: pos };
89
89
  }
90
- // Build 3D grid directly from Float64Array, computing data_range in the same pass.
91
- function build_grid(data, nx, ny, nz, divisor = 1) {
90
+ function build_grid({ data, nx, ny, nz, divisor = 1, data_order = `z_fastest` }) {
92
91
  const grid = new Array(nx);
93
92
  let min_val = Infinity;
94
93
  let max_val = -Infinity;
95
94
  let sum = 0;
96
- const ny_nz = ny * nz;
97
95
  const total = nx * ny * nz;
98
96
  const data_len = Math.min(data.length, total);
99
97
  if (data_len === 0) {
@@ -106,35 +104,65 @@ function build_grid(data, nx, ny, nz, divisor = 1) {
106
104
  }
107
105
  return { grid, data_range: { min: 0, max: 0, abs_max: 0, mean: 0 } };
108
106
  }
109
- for (let ix = 0; ix < nx; ix++) {
110
- const plane = new Array(ny);
111
- for (let iy = 0; iy < ny; iy++) {
112
- const row = new Array(nz).fill(0);
113
- const base = ix * ny_nz + iy * nz;
114
- const row_end = Math.min(base + nz, data_len);
115
- for (let flat_idx = base; flat_idx < row_end; flat_idx++) {
116
- const val = data[flat_idx] / divisor;
117
- row[flat_idx - base] = val;
118
- if (val < min_val)
119
- min_val = val;
120
- if (val > max_val)
121
- max_val = val;
122
- sum += val;
107
+ if (data_order === `z_fastest`) {
108
+ // .cube convention: z varies fastest, then y, then x.
109
+ const ny_nz = ny * nz;
110
+ for (let ix = 0; ix < nx; ix++) {
111
+ const plane = new Array(ny);
112
+ for (let iy = 0; iy < ny; iy++) {
113
+ const row = new Array(nz).fill(0);
114
+ const base = ix * ny_nz + iy * nz;
115
+ const row_end = Math.min(base + nz, data_len);
116
+ for (let flat_idx = base; flat_idx < row_end; flat_idx++) {
117
+ const val = data[flat_idx] / divisor;
118
+ row[flat_idx - base] = val;
119
+ if (val < min_val)
120
+ min_val = val;
121
+ if (val > max_val)
122
+ max_val = val;
123
+ sum += val;
124
+ }
125
+ plane[iy] = row;
126
+ }
127
+ grid[ix] = plane;
128
+ }
129
+ }
130
+ else {
131
+ // VASP CHGCAR/ELFCAR/LOCPOT convention: x varies fastest, then y, then z.
132
+ for (let ix = 0; ix < nx; ix++) {
133
+ const plane = new Array(ny);
134
+ for (let iy = 0; iy < ny; iy++)
135
+ plane[iy] = new Array(nz).fill(0);
136
+ grid[ix] = plane;
137
+ }
138
+ let flat_idx = 0;
139
+ let data_exhausted = false;
140
+ for (let iz = 0; iz < nz; iz++) {
141
+ for (let iy = 0; iy < ny; iy++) {
142
+ for (let ix = 0; ix < nx; ix++) {
143
+ if (flat_idx >= data_len) {
144
+ data_exhausted = true;
145
+ break;
146
+ }
147
+ const val = data[flat_idx] / divisor;
148
+ grid[ix][iy][iz] = val;
149
+ if (val < min_val)
150
+ min_val = val;
151
+ if (val > max_val)
152
+ max_val = val;
153
+ sum += val;
154
+ flat_idx++;
155
+ }
156
+ if (data_exhausted)
157
+ break;
123
158
  }
124
- plane[iy] = row;
159
+ if (data_exhausted)
160
+ break;
125
161
  }
126
- grid[ix] = plane;
127
162
  }
128
163
  const abs_max = Math.max(Math.abs(min_val), Math.abs(max_val));
129
- return {
130
- grid,
131
- data_range: {
132
- min: min_val,
133
- max: max_val,
134
- abs_max,
135
- mean: sum / data_len,
136
- },
137
- };
164
+ const data_range = { min: min_val, max: max_val, abs_max, mean: sum / data_len };
165
+ return { grid, data_range };
138
166
  }
139
167
  // === CHGCAR Parser ===
140
168
  // Parse VASP CHGCAR/AECCAR/ELFCAR/LOCPOT file format.
@@ -289,13 +317,21 @@ export function parse_chgcar(content) {
289
317
  // Use Math.abs to guard against negative determinant (left-handed lattice).
290
318
  const cell_volume = Math.abs(lattice_params.volume);
291
319
  const divisor = cell_volume > 1e-30 ? cell_volume : 1;
292
- const { grid, data_range } = build_grid(data.subarray(0, parsed_count), ngx, ngy, ngz, divisor);
320
+ const { grid, data_range } = build_grid({
321
+ data: data.subarray(0, parsed_count),
322
+ nx: ngx,
323
+ ny: ngy,
324
+ nz: ngz,
325
+ divisor,
326
+ data_order: `x_fastest`,
327
+ });
293
328
  volumes.push({
294
329
  grid,
295
330
  grid_dims: [ngx, ngy, ngz],
296
331
  lattice,
297
332
  origin: [0, 0, 0],
298
333
  data_range,
334
+ data_order: `x_fastest`,
299
335
  periodic: true, // VASP grids span [0,1) with N points, wrapping at boundaries
300
336
  label: volume_labels[vol_idx],
301
337
  });
@@ -438,13 +474,20 @@ export function parse_cube(content, options = {}) {
438
474
  return null;
439
475
  }
440
476
  }
441
- const { grid, data_range } = build_grid(data.subarray(0, parsed_count), n_grid[0], n_grid[1], n_grid[2]);
477
+ const { grid, data_range } = build_grid({
478
+ data: data.subarray(0, parsed_count),
479
+ nx: n_grid[0],
480
+ ny: n_grid[1],
481
+ nz: n_grid[2],
482
+ data_order: `z_fastest`,
483
+ });
442
484
  const volumes = [{
443
485
  grid,
444
486
  grid_dims: n_grid,
445
487
  lattice,
446
488
  origin,
447
489
  data_range,
490
+ data_order: `z_fastest`,
448
491
  periodic: is_periodic, // periodic systems wrap; molecular .cube files include both endpoints
449
492
  label: `volumetric data`,
450
493
  }];
@@ -1,3 +1,4 @@
1
+ import type { Vec3 } from '../math';
1
2
  import type { VolumetricData } from './types';
2
3
  export interface SliceResult {
3
4
  data: Float64Array;
@@ -7,4 +8,4 @@ export interface SliceResult {
7
8
  max: number;
8
9
  }
9
10
  export declare function trilinear_interpolate(grid: number[][][], fx: number, fy: number, fz: number, periodic: boolean): number;
10
- export declare function sample_hkl_slice(volume: VolumetricData, miller_indices: [number, number, number], distance: number): SliceResult | null;
11
+ export declare function sample_hkl_slice(volume: VolumetricData, miller_indices: Vec3, distance: number, n_points?: number): SliceResult | null;
@@ -56,7 +56,7 @@ export function trilinear_interpolate(grid, fx, fy, fz, periodic) {
56
56
  // `miller_indices` [h,k,l] defines the plane normal in reciprocal space.
57
57
  // `distance` is fractional [0,1] along the normal direction within the cell.
58
58
  // Returns null if indices are all zero.
59
- export function sample_hkl_slice(volume, miller_indices, distance) {
59
+ export function sample_hkl_slice(volume, miller_indices, distance, n_points) {
60
60
  const [h_idx, k_idx, l_idx] = miller_indices;
61
61
  if (h_idx === 0 && k_idx === 0 && l_idx === 0)
62
62
  return null;
@@ -113,8 +113,8 @@ export function sample_hkl_slice(volume, miller_indices, distance) {
113
113
  }
114
114
  // Plane position: fractional distance [0,1] along the normal extent
115
115
  const d_cartesian = normal_min + distance * (normal_max - normal_min);
116
- // Sampling resolution: square grid using max dimension
117
- const width = Math.max(nx, ny, nz);
116
+ // Sampling resolution: caller-specified or default to max grid dimension
117
+ const width = n_points ?? Math.max(nx, ny, nz);
118
118
  const height = width;
119
119
  const data = new Float64Array(width * height);
120
120
  let data_min = Infinity;
@@ -8,10 +8,11 @@ export interface DataRange {
8
8
  }
9
9
  export interface VolumetricData {
10
10
  grid: number[][][];
11
- grid_dims: [number, number, number];
11
+ grid_dims: Vec3;
12
12
  lattice: Matrix3x3;
13
13
  origin: Vec3;
14
14
  data_range: DataRange;
15
+ data_order?: `x_fastest` | `z_fastest`;
15
16
  periodic: boolean;
16
17
  label?: string;
17
18
  }
@@ -34,10 +35,21 @@ export interface IsosurfaceSettings {
34
35
  negative_color: string;
35
36
  show_negative: boolean;
36
37
  wireframe: boolean;
38
+ halo: number;
37
39
  layers?: IsosurfaceLayer[];
38
40
  }
39
41
  export declare const LAYER_COLORS: readonly ["#3b82f6", "#ef4444", "#22c55e", "#a855f7", "#f97316", "#06b6d4", "#eab308", "#ec4899"];
40
42
  export declare function grid_data_range(grid: number[][][]): DataRange;
43
+ export declare function pad_periodic_grid(grid: number[][][], dims: Vec3, pad_fraction: number): {
44
+ grid: number[][][];
45
+ dims: Vec3;
46
+ offset: Vec3;
47
+ };
48
+ export declare function downsample_grid(grid: number[][][], dims: Vec3): {
49
+ grid: number[][][];
50
+ dims: Vec3;
51
+ factor: number;
52
+ };
41
53
  export declare const DEFAULT_ISOSURFACE_SETTINGS: IsosurfaceSettings;
42
54
  export declare function auto_isosurface_settings(data_range: DataRange): IsosurfaceSettings;
43
55
  export declare function generate_layers(data_range: DataRange, n_layers: number): IsosurfaceLayer[];
@@ -34,6 +34,103 @@ export function grid_data_range(grid) {
34
34
  const abs_max = Math.max(Math.abs(min_val), Math.abs(max_val));
35
35
  return { min: min_val, max: max_val, abs_max, mean: count > 0 ? sum / count : 0 };
36
36
  }
37
+ // Pad a periodic 3D grid with halo cells from the opposite face so isosurfaces
38
+ // extend beyond the unit cell and close into complete enclosed shapes.
39
+ // Returns a larger grid with dims [nx+2*pad, ny+2*pad, nz+2*pad] and the
40
+ // fractional offset that the padded grid's origin has shifted by.
41
+ export function pad_periodic_grid(grid, dims, pad_fraction) {
42
+ const [nx, ny, nz] = dims;
43
+ const frac = Math.max(0, pad_fraction);
44
+ const px = Math.min(Math.ceil(nx * frac), Math.floor(nx / 2));
45
+ const py = Math.min(Math.ceil(ny * frac), Math.floor(ny / 2));
46
+ const pz = Math.min(Math.ceil(nz * frac), Math.floor(nz / 2));
47
+ if (px === 0 && py === 0 && pz === 0)
48
+ return { grid, dims, offset: [0, 0, 0] };
49
+ const out_nx = nx + 2 * px;
50
+ const out_ny = ny + 2 * py;
51
+ const out_nz = nz + 2 * pz;
52
+ const wrap = (val, size) => ((val % size) + size) % size;
53
+ const out = new Array(out_nx);
54
+ for (let ix = 0; ix < out_nx; ix++) {
55
+ const plane = new Array(out_ny);
56
+ const src_x = wrap(ix - px, nx);
57
+ for (let iy = 0; iy < out_ny; iy++) {
58
+ const row = new Array(out_nz);
59
+ const src_y = wrap(iy - py, ny);
60
+ for (let iz = 0; iz < out_nz; iz++) {
61
+ row[iz] = grid[src_x][src_y][wrap(iz - pz, nz)];
62
+ }
63
+ plane[iy] = row;
64
+ }
65
+ out[ix] = plane;
66
+ }
67
+ // Fractional offset: the padded grid starts at -pad/n in each axis
68
+ const offset = [-px / nx, -py / ny, -pz / nz];
69
+ return { grid: out, dims: [out_nx, out_ny, out_nz], offset };
70
+ }
71
+ // Max total grid points before downsampling is applied for isosurface extraction.
72
+ // 500K balances visual quality with interactive performance (<200ms marching cubes).
73
+ const MAX_GRID_POINTS = 500_000;
74
+ // Downsample a 3D volumetric grid to keep total point count under MAX_GRID_POINTS.
75
+ // Uses block averaging to preserve data fidelity while reducing grid dimensions.
76
+ // Returns original grid/dims if already within budget.
77
+ export function downsample_grid(grid, dims) {
78
+ const [nx, ny, nz] = dims;
79
+ const total = nx * ny * nz;
80
+ if (total <= MAX_GRID_POINTS)
81
+ return { grid, dims, factor: 1 };
82
+ // Increase factor until the clamped output fits within budget.
83
+ // A single cbrt step can overshoot for anisotropic grids where max(2,...)
84
+ // clamping prevents a small axis from shrinking below 2.
85
+ // clamp_dim: returns 1 for single-cell axes, otherwise clamps to [2, src]
86
+ const clamp_dim = (src, fac) => Math.min(src, Math.max(2, Math.ceil(src / fac)));
87
+ let factor = Math.ceil(Math.cbrt(total / MAX_GRID_POINTS));
88
+ let new_nx = clamp_dim(nx, factor);
89
+ let new_ny = clamp_dim(ny, factor);
90
+ let new_nz = clamp_dim(nz, factor);
91
+ while (new_nx * new_ny * new_nz > MAX_GRID_POINTS) {
92
+ factor++;
93
+ new_nx = clamp_dim(nx, factor);
94
+ new_ny = clamp_dim(ny, factor);
95
+ new_nz = clamp_dim(nz, factor);
96
+ }
97
+ // Proportional partitioning: evenly divides [0, n) into new_n non-empty blocks.
98
+ // Unlike fixed-stride (ix * factor), this is safe when max(2,...) clamping
99
+ // produces more output cells than ceil(n/factor) would — no empty blocks.
100
+ const partition = (n_out, n_src) => Array.from({ length: n_out }, (_, idx) => [
101
+ Math.round(idx * n_src / n_out),
102
+ Math.round((idx + 1) * n_src / n_out),
103
+ ]);
104
+ const x_ranges = partition(new_nx, nx);
105
+ const y_ranges = partition(new_ny, ny);
106
+ const z_ranges = partition(new_nz, nz);
107
+ const out = new Array(new_nx);
108
+ for (let ix = 0; ix < new_nx; ix++) {
109
+ const plane = new Array(new_ny);
110
+ const [sx_start, sx_end] = x_ranges[ix];
111
+ for (let iy = 0; iy < new_ny; iy++) {
112
+ const row = new Array(new_nz);
113
+ const [sy_start, sy_end] = y_ranges[iy];
114
+ for (let iz = 0; iz < new_nz; iz++) {
115
+ let sum = 0;
116
+ const [sz_start, sz_end] = z_ranges[iz];
117
+ for (let sx = sx_start; sx < sx_end; sx++) {
118
+ const src_plane = grid[sx];
119
+ for (let sy = sy_start; sy < sy_end; sy++) {
120
+ const src_row = src_plane[sy];
121
+ for (let sz = sz_start; sz < sz_end; sz++) {
122
+ sum += src_row[sz];
123
+ }
124
+ }
125
+ }
126
+ row[iz] = sum / ((sx_end - sx_start) * (sy_end - sy_start) * (sz_end - sz_start));
127
+ }
128
+ plane[iy] = row;
129
+ }
130
+ out[ix] = plane;
131
+ }
132
+ return { grid: out, dims: [new_nx, new_ny, new_nz], factor };
133
+ }
37
134
  // Default isosurface rendering settings
38
135
  export const DEFAULT_ISOSURFACE_SETTINGS = {
39
136
  isovalue: 0.05,
@@ -42,6 +139,7 @@ export const DEFAULT_ISOSURFACE_SETTINGS = {
42
139
  negative_color: `#ef4444`, // red
43
140
  show_negative: false,
44
141
  wireframe: false,
142
+ halo: 0,
45
143
  };
46
144
  // Compute reasonable isosurface settings from a volume's data range.
47
145
  // Sets isovalue to 20% of abs_max and enables negative lobe when data has
package/dist/labels.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ChemicalElement, ElementCategory } from './element/types';
2
+ import type { Vec3 } from './math';
2
3
  import type { SymbolType } from 'd3-shape';
3
4
  import * as d3_symbols from 'd3-shape';
4
5
  export type D3Symbol = keyof typeof d3_symbols & `symbol${Capitalize<string>}`;
@@ -12,7 +13,7 @@ export declare const ELEM_HEATMAP_LABELS: Partial<Record<string, keyof ChemicalE
12
13
  export declare const DEFAULT_FMT: [string, string];
13
14
  export declare const FRACTION_GLYPHS: ReadonlyArray<readonly [number, string]>;
14
15
  export declare const format_num: (num: number, fmt?: string | number) => string;
15
- export declare const format_vec3: (vec: readonly [number, number, number], fmt_spec?: string) => string;
16
+ export declare const format_vec3: (vec: Readonly<Vec3>, fmt_spec?: string) => string;
16
17
  export declare const format_bytes: (bytes?: number) => string;
17
18
  export declare function format_fractional(value: number): string;
18
19
  export declare function parse_si_float<T extends string | number | null | undefined>(value: T): T | number | string;
package/dist/labels.js CHANGED
@@ -56,6 +56,7 @@ export const ELEM_PROPERTY_LABELS = {
56
56
  electronegativity: [`Electronegativity`, null],
57
57
  first_ionization: [`First Ionization Energy`, `eV`],
58
58
  melting_point: [`Melting Point`, `K`],
59
+ mendeleev_number: [`Mendeleev Number`, null],
59
60
  // molar_heat: [`Molar Heat`, `J/(mol·K)`],
60
61
  n_shells: [`Number of Shells`, null],
61
62
  n_valence: [`Electron Valency`, null],