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
@@ -2,56 +2,194 @@
2
2
  // crystallographic plane defined by Miller indices, using trilinear interpolation.
3
3
  import { reciprocal_lattice } from '../brillouin';
4
4
  import * as math from '../math';
5
- const safe_mod = (val, dim) => ((val % dim) + dim) % dim;
6
- // Trilinear interpolation of a scalar 3D grid at fractional coordinates.
7
- // Periodic grids wrap with modulo; non-periodic return 0 for out-of-bounds.
8
- export function trilinear_interpolate(grid, fx, fy, fz, periodic) {
9
- const nx = grid.length;
10
- const ny = grid[0]?.length ?? 0;
11
- const nz = grid[0]?.[0]?.length ?? 0;
12
- if (nx === 0 || ny === 0 || nz === 0)
13
- return 0;
14
- // Convert fractional to grid coordinates
15
- const gx = periodic ? fx * nx : fx * (nx - 1);
16
- const gy = periodic ? fy * ny : fy * (ny - 1);
17
- const gz = periodic ? fz * nz : fz * (nz - 1);
18
- if (!periodic) {
19
- // Out-of-bounds check for non-periodic grids
20
- if (fx < 0 || fx > 1 || fy < 0 || fy > 1 || fz < 0 || fz > 1)
21
- return 0;
5
+ import { create_volume_sampler, sanitize_display_range } from './sampling';
6
+ const CELL_EDGES = [
7
+ [0, 1],
8
+ [0, 2],
9
+ [0, 4],
10
+ [1, 3],
11
+ [1, 5],
12
+ [2, 3],
13
+ [2, 6],
14
+ [3, 7],
15
+ [4, 5],
16
+ [4, 6],
17
+ [5, 7],
18
+ [6, 7],
19
+ ];
20
+ const PLANE_TOLERANCE = 1e-9;
21
+ const DEFAULT_MAX_PIXELS = 512 * 512;
22
+ const UNIT_CELL_RANGE = [
23
+ [0, 1],
24
+ [0, 1],
25
+ [0, 1],
26
+ ];
27
+ const cell_corners = (volume, bounds) => {
28
+ const frac_to_cart = math.create_frac_to_cart(volume.lattice);
29
+ return Array.from({ length: 8 }, (_, corner_idx) => math.add(volume.origin, frac_to_cart([
30
+ bounds[0][corner_idx & 1 ? 1 : 0],
31
+ bounds[1][corner_idx & 2 ? 1 : 0],
32
+ bounds[2][corner_idx & 4 ? 1 : 0],
33
+ ])));
34
+ };
35
+ const add_unique_point = (points, point) => {
36
+ if (points.some((existing) => (existing[0] - point[0]) ** 2 +
37
+ (existing[1] - point[1]) ** 2 +
38
+ (existing[2] - point[2]) ** 2 <
39
+ PLANE_TOLERANCE ** 2))
40
+ return;
41
+ points.push(point);
42
+ };
43
+ const plane_basis = (normal, up) => {
44
+ if (!up)
45
+ return math.compute_in_plane_basis(normal);
46
+ const normal_projection = math.dot(up, normal);
47
+ const projected = math.subtract(up, math.scale(normal, normal_projection));
48
+ if (Math.hypot(...projected) < PLANE_TOLERANCE) {
49
+ return math.compute_in_plane_basis(normal);
22
50
  }
23
- const x0 = periodic
24
- ? safe_mod(Math.floor(gx), nx)
25
- : Math.max(0, Math.min(Math.floor(gx), nx - 2));
26
- const y0 = periodic
27
- ? safe_mod(Math.floor(gy), ny)
28
- : Math.max(0, Math.min(Math.floor(gy), ny - 2));
29
- const z0 = periodic
30
- ? safe_mod(Math.floor(gz), nz)
31
- : Math.max(0, Math.min(Math.floor(gz), nz - 2));
32
- const x1 = periodic ? (x0 + 1) % nx : Math.min(x0 + 1, nx - 1);
33
- const y1 = periodic ? (y0 + 1) % ny : Math.min(y0 + 1, ny - 1);
34
- const z1 = periodic ? (z0 + 1) % nz : Math.min(z0 + 1, nz - 1);
35
- // deltas from clamped lower index (non-periodic x0 clamps to nx-2 so floor(gx) may != x0)
36
- const xd = periodic ? gx - Math.floor(gx) : gx - x0;
37
- const yd = periodic ? gy - Math.floor(gy) : gy - y0;
38
- const zd = periodic ? gz - Math.floor(gz) : gz - z0;
39
- // 8-point interpolation
40
- const c000 = grid[x0][y0][z0];
41
- const c001 = grid[x0][y0][z1];
42
- const c010 = grid[x0][y1][z0];
43
- const c011 = grid[x0][y1][z1];
44
- const c100 = grid[x1][y0][z0];
45
- const c101 = grid[x1][y0][z1];
46
- const c110 = grid[x1][y1][z0];
47
- const c111 = grid[x1][y1][z1];
48
- const c00 = c000 + (c100 - c000) * xd;
49
- const c01 = c001 + (c101 - c001) * xd;
50
- const c10 = c010 + (c110 - c010) * xd;
51
- const c11 = c011 + (c111 - c011) * xd;
52
- const c0 = c00 + (c10 - c00) * yd;
53
- const c1 = c01 + (c11 - c01) * yd;
54
- return c0 + (c1 - c0) * zd;
51
+ const u_axis = math.normalize_vec(projected);
52
+ return [u_axis, math.cross_3d(normal, u_axis)];
53
+ };
54
+ const intersect_plane_cell = (corners, point, normal, u_axis, v_axis) => {
55
+ const intersections = [];
56
+ for (const [start_idx, end_idx] of CELL_EDGES) {
57
+ const start = corners[start_idx];
58
+ const end = corners[end_idx];
59
+ const start_distance = math.dot(math.subtract(start, point), normal);
60
+ const end_distance = math.dot(math.subtract(end, point), normal);
61
+ if (Math.abs(start_distance) <= PLANE_TOLERANCE)
62
+ add_unique_point(intersections, start);
63
+ if (Math.abs(end_distance) <= PLANE_TOLERANCE)
64
+ add_unique_point(intersections, end);
65
+ if (start_distance * end_distance >= -(PLANE_TOLERANCE ** 2))
66
+ continue;
67
+ const fraction = start_distance / (start_distance - end_distance);
68
+ add_unique_point(intersections, [
69
+ start[0] + fraction * (end[0] - start[0]),
70
+ start[1] + fraction * (end[1] - start[1]),
71
+ start[2] + fraction * (end[2] - start[2]),
72
+ ]);
73
+ }
74
+ return math.convex_hull_2d(intersections.map((intersection) => {
75
+ const relative = math.subtract(intersection, point);
76
+ return [math.dot(relative, u_axis), math.dot(relative, v_axis)];
77
+ }), PLANE_TOLERANCE);
78
+ };
79
+ const point_in_convex_polygon = (point, polygon) => {
80
+ let orientation = 0;
81
+ for (let point_idx = 0; point_idx < polygon.length; point_idx++) {
82
+ const start = polygon[point_idx];
83
+ const end = polygon[(point_idx + 1) % polygon.length];
84
+ const cross = (end[0] - start[0]) * (point[1] - start[1]) - (end[1] - start[1]) * (point[0] - start[0]);
85
+ if (Math.abs(cross) <= PLANE_TOLERANCE)
86
+ continue;
87
+ const current_orientation = Math.sign(cross);
88
+ if (orientation && current_orientation !== orientation)
89
+ return false;
90
+ orientation = current_orientation;
91
+ }
92
+ return true;
93
+ };
94
+ const resolve_resolution = (resolution, u_span, v_span, max_grid_dim, max_pixels) => {
95
+ let counts;
96
+ if (Array.isArray(resolution)) {
97
+ counts = [Math.max(2, Math.round(resolution[0])), Math.max(2, Math.round(resolution[1]))];
98
+ }
99
+ else {
100
+ const longest_count = Math.max(2, Math.round(resolution ?? max_grid_dim));
101
+ const longest_span = Math.max(u_span, v_span, PLANE_TOLERANCE);
102
+ counts = [
103
+ Math.max(2, Math.round((longest_count * u_span) / longest_span)),
104
+ Math.max(2, Math.round((longest_count * v_span) / longest_span)),
105
+ ];
106
+ }
107
+ const pixel_budget = Number.isFinite(max_pixels)
108
+ ? Math.max(4, Math.floor(max_pixels))
109
+ : DEFAULT_MAX_PIXELS;
110
+ const shrink = Math.min(1, Math.sqrt(pixel_budget / (counts[0] * counts[1])));
111
+ if (shrink >= 1)
112
+ return counts;
113
+ counts = counts.map((count) => Math.max(2, Math.floor(count * shrink)));
114
+ if (counts[0] * counts[1] > pixel_budget) {
115
+ const axis = counts[0] >= counts[1] ? 0 : 1;
116
+ counts[axis] = Math.max(2, Math.floor(pixel_budget / counts[axis === 0 ? 1 : 0]));
117
+ }
118
+ return counts;
119
+ };
120
+ /** Sample a scalar volume on an arbitrary absolute Cartesian plane. */
121
+ export function sample_plane_slice(volume, plane, options = {}) {
122
+ if (!plane.point.every(Number.isFinite) || !plane.normal.every(Number.isFinite))
123
+ return null;
124
+ if (plane.up && !plane.up.every(Number.isFinite))
125
+ return null;
126
+ if (Math.hypot(...plane.normal) < PLANE_TOLERANCE)
127
+ return null; // degenerate normal
128
+ const normal = math.normalize_vec(plane.normal);
129
+ // In-plane basis vectors
130
+ const [u_axis, v_axis] = plane_basis(normal, plane.up);
131
+ const bounds = sanitize_display_range(options.fractional_bounds ?? UNIT_CELL_RANGE, volume.periodic);
132
+ const corners = cell_corners(volume, bounds);
133
+ const polygon = intersect_plane_cell(corners, plane.point, normal, u_axis, v_axis);
134
+ if (polygon.length < 3)
135
+ return null;
136
+ // Project all 8 unit cell corners onto the (u, v) plane to find sampling bounds.
137
+ // Corners are at fractional coords (0 or 1) for each axis.
138
+ const { min, max, width: u_span, height: v_span } = math.compute_bounding_box_2d(polygon);
139
+ const u_range = [min[0], max[0]];
140
+ const v_range = [min[1], max[1]];
141
+ if (u_span <= PLANE_TOLERANCE || v_span <= PLANE_TOLERANCE)
142
+ return null;
143
+ // Sampling resolution: caller-specified or default to max grid dimension
144
+ const [width, height] = resolve_resolution(options.resolution, u_span, v_span, Math.max(...volume.grid_dims), options.max_pixels ?? DEFAULT_MAX_PIXELS);
145
+ const data = new Float64Array(width * height);
146
+ data.fill(Number.NaN);
147
+ const mask = new Uint8Array(width * height);
148
+ let data_min = Infinity;
149
+ let data_max = -Infinity;
150
+ const sample = create_volume_sampler(volume, {
151
+ out_of_bounds: volume.periodic ? `clamp` : `fallback`,
152
+ });
153
+ const u_step = u_span / (width - 1);
154
+ const v_step = v_span / (height - 1);
155
+ const cartesian = [0, 0, 0];
156
+ for (let row = 0; row < height; row++) {
157
+ const v_value = v_range[0] + row * v_step;
158
+ for (let col = 0; col < width; col++) {
159
+ const u_value = u_range[0] + col * u_step;
160
+ if (!point_in_convex_polygon([u_value, v_value], polygon))
161
+ continue;
162
+ const data_idx = row * width + col;
163
+ mask[data_idx] = 1;
164
+ // Cartesian position on the plane
165
+ cartesian[0] = plane.point[0] + u_value * u_axis[0] + v_value * v_axis[0];
166
+ cartesian[1] = plane.point[1] + u_value * u_axis[1] + v_value * v_axis[1];
167
+ cartesian[2] = plane.point[2] + u_value * u_axis[2] + v_value * v_axis[2];
168
+ const value = sample(cartesian);
169
+ if (!Number.isFinite(value))
170
+ continue;
171
+ data[data_idx] = value;
172
+ if (value < data_min)
173
+ data_min = value;
174
+ if (value > data_max)
175
+ data_max = value;
176
+ }
177
+ }
178
+ return {
179
+ data,
180
+ mask,
181
+ width,
182
+ height,
183
+ min: data_min === Infinity ? 0 : data_min,
184
+ max: data_max === -Infinity ? 0 : data_max,
185
+ point: [...plane.point],
186
+ normal,
187
+ u_axis,
188
+ v_axis,
189
+ u_range,
190
+ v_range,
191
+ polygon,
192
+ };
55
193
  }
56
194
  // Sample a 2D slice through volumetric data along a Miller-index plane.
57
195
  // `miller_indices` [h,k,l] defines the plane normal in reciprocal space.
@@ -61,81 +199,27 @@ export function sample_hkl_slice(volume, miller_indices, distance, n_points) {
61
199
  const [h_idx, k_idx, l_idx] = miller_indices;
62
200
  if (h_idx === 0 && k_idx === 0 && l_idx === 0)
63
201
  return null;
64
- const { grid, grid_dims, lattice, periodic } = volume;
65
- const [nx, ny, nz] = grid_dims;
66
202
  // Plane normal G = h*b1 + k*b2 + l*b3 where b_i are reciprocal lattice rows
67
- const recip = reciprocal_lattice(lattice);
203
+ const recip = reciprocal_lattice(volume.lattice);
68
204
  const plane_normal = [
69
205
  h_idx * recip[0][0] + k_idx * recip[1][0] + l_idx * recip[2][0],
70
206
  h_idx * recip[0][1] + k_idx * recip[1][1] + l_idx * recip[2][1],
71
207
  h_idx * recip[0][2] + k_idx * recip[1][2] + l_idx * recip[2][2],
72
208
  ];
73
- if (Math.hypot(...plane_normal) < 1e-12)
209
+ if (Math.hypot(...plane_normal) < PLANE_TOLERANCE)
74
210
  return null; // degenerate normal
75
211
  const unit_normal = math.normalize_vec(plane_normal);
76
- // In-plane basis vectors
77
- const [u_vec, v_vec] = math.compute_in_plane_basis(unit_normal);
78
- const cart_to_frac = math.create_cart_to_frac(lattice);
79
- // Project all 8 unit cell corners onto the (u, v) plane to find sampling bounds.
80
- // Corners are at fractional coords (0 or 1) for each axis.
81
- let u_min = Infinity;
82
- let u_max = -Infinity;
83
- let v_min = Infinity;
84
- let v_max = -Infinity;
85
- let normal_min = Infinity;
86
- let normal_max = -Infinity;
87
- for (let ci = 0; ci < 8; ci++) {
88
- const fi = ci & 1 ? 1 : 0;
89
- const fj = ci & 2 ? 1 : 0;
90
- const fk = ci & 4 ? 1 : 0;
91
- // Corner in Cartesian: frac * lattice
92
- const corner = [
93
- fi * lattice[0][0] + fj * lattice[1][0] + fk * lattice[2][0],
94
- fi * lattice[0][1] + fj * lattice[1][1] + fk * lattice[2][1],
95
- fi * lattice[0][2] + fj * lattice[1][2] + fk * lattice[2][2],
96
- ];
97
- const u_proj = math.dot(corner, u_vec);
98
- const v_proj = math.dot(corner, v_vec);
99
- const n_proj = math.dot(corner, unit_normal);
100
- if (u_proj < u_min)
101
- u_min = u_proj;
102
- if (u_proj > u_max)
103
- u_max = u_proj;
104
- if (v_proj < v_min)
105
- v_min = v_proj;
106
- if (v_proj > v_max)
107
- v_max = v_proj;
108
- if (n_proj < normal_min)
109
- normal_min = n_proj;
110
- if (n_proj > normal_max)
111
- normal_max = n_proj;
112
- }
212
+ const corners = cell_corners(volume, UNIT_CELL_RANGE);
213
+ const projections = corners.map((corner) => math.dot(corner, unit_normal));
214
+ const normal_min = Math.min(...projections);
215
+ const normal_max = Math.max(...projections);
113
216
  // Plane position: fractional distance [0,1] along the normal extent
114
217
  const d_cartesian = normal_min + distance * (normal_max - normal_min);
115
- // Sampling resolution: caller-specified or default to max grid dimension
116
- const width = n_points ?? Math.max(nx, ny, nz);
117
- const height = width;
118
- const data = new Float64Array(width * height);
119
- let data_min = Infinity;
120
- let data_max = -Infinity;
121
- const u_step = (u_max - u_min) / (width - 1 || 1);
122
- const v_step = (v_max - v_min) / (height - 1 || 1);
123
- for (let row = 0; row < height; row++) {
124
- const v_val = v_min + row * v_step;
125
- for (let col = 0; col < width; col++) {
126
- const u_val = u_min + col * u_step;
127
- // Cartesian position on the plane
128
- const px = d_cartesian * unit_normal[0] + u_val * u_vec[0] + v_val * v_vec[0];
129
- const py = d_cartesian * unit_normal[1] + u_val * u_vec[1] + v_val * v_vec[1];
130
- const pz = d_cartesian * unit_normal[2] + u_val * u_vec[2] + v_val * v_vec[2];
131
- const [fx, fy, fz] = cart_to_frac([px, py, pz]);
132
- const val = trilinear_interpolate(grid, fx, fy, fz, periodic);
133
- data[row * width + col] = val;
134
- if (val < data_min)
135
- data_min = val;
136
- if (val > data_max)
137
- data_max = val;
138
- }
139
- }
140
- return { data, width, height, min: data_min, max: data_max };
218
+ const point = [
219
+ d_cartesian * unit_normal[0],
220
+ d_cartesian * unit_normal[1],
221
+ d_cartesian * unit_normal[2],
222
+ ];
223
+ const resolution = n_points ?? Math.max(...volume.grid_dims);
224
+ return sample_plane_slice(volume, { point, normal: unit_normal }, { resolution: [resolution, resolution] });
141
225
  }
@@ -1,4 +1,5 @@
1
- import type { Matrix3x3, Vec3 } from '../math';
1
+ import type { D3InterpolateName } from '../colors';
2
+ import type { Matrix3x3, Vec2, Vec3 } from '../math';
2
3
  import type { ParsedStructure } from '../structure/parse';
3
4
  export interface DataRange {
4
5
  min: number;
@@ -15,6 +16,7 @@ export interface VolumetricData {
15
16
  data_order?: `x_fastest` | `z_fastest`;
16
17
  periodic: boolean;
17
18
  label?: string;
19
+ source?: string;
18
20
  }
19
21
  export interface VolumetricFileData {
20
22
  structure: ParsedStructure;
@@ -27,6 +29,10 @@ export interface IsosurfaceLayer {
27
29
  visible: boolean;
28
30
  show_negative: boolean;
29
31
  negative_color: string;
32
+ volume_idx?: number;
33
+ color_volume_idx?: number;
34
+ colormap?: D3InterpolateName;
35
+ color_range?: Vec2;
30
36
  }
31
37
  export interface IsosurfaceSettings {
32
38
  isovalue: number;
@@ -37,6 +43,7 @@ export interface IsosurfaceSettings {
37
43
  wireframe: boolean;
38
44
  halo: number;
39
45
  layers?: IsosurfaceLayer[];
46
+ display_range?: [Vec2, Vec2, Vec2];
40
47
  }
41
48
  export declare const LAYER_COLORS: readonly ["#3b82f6", "#ef4444", "#22c55e", "#a855f7", "#f97316", "#06b6d4", "#eab308", "#ec4899"];
42
49
  export declare function grid_data_range(grid: number[][][]): DataRange;
@@ -45,6 +52,7 @@ export declare function pad_periodic_grid(grid: number[][][], dims: Vec3, pad_fr
45
52
  dims: Vec3;
46
53
  offset: Vec3;
47
54
  };
55
+ export declare const MAX_GRID_POINTS = 500000;
48
56
  export declare function downsample_grid(grid: number[][][], dims: Vec3, max_points?: number): {
49
57
  grid: number[][][];
50
58
  dims: Vec3;
@@ -53,4 +61,19 @@ export declare function downsample_grid(grid: number[][][], dims: Vec3, max_poin
53
61
  export declare const DEFAULT_ISOSURFACE_SETTINGS: IsosurfaceSettings;
54
62
  export declare function auto_isosurface_settings(data_range: DataRange): IsosurfaceSettings;
55
63
  export declare function generate_layers(data_range: DataRange, n_layers: number): IsosurfaceLayer[];
64
+ export declare function auto_volume_layer(volume: VolumetricData, volume_idx: number, color_offset?: number): IsosurfaceLayer;
65
+ export declare function materialize_layers(settings: IsosurfaceSettings, active_volume_idx: number): IsosurfaceLayer[];
66
+ export declare function remove_volume(volumes: VolumetricData[], layers: IsosurfaceLayer[], removed_idx: number, active_volume_idx?: number): {
67
+ volumes: VolumetricData[];
68
+ layers: IsosurfaceLayer[];
69
+ };
70
+ export declare function label_file_volumes(volumes: VolumetricData[], filename: string): VolumetricData[];
71
+ export declare const lattices_match: (lattice_a: readonly (readonly number[])[] | undefined, lattice_b: readonly (readonly number[])[] | undefined, tolerance?: number) => boolean;
72
+ export interface VolumeMergeResult {
73
+ volumes: VolumetricData[];
74
+ layers: IsosurfaceLayer[];
75
+ first_touched_idx: number;
76
+ n_added: number;
77
+ }
78
+ export declare function merge_imported_volumes(existing: VolumetricData[], existing_layers: IsosurfaceLayer[], incoming: VolumetricData[], active_volume_idx?: number): VolumeMergeResult;
56
79
  export declare function tile_volumetric_data(volume: VolumetricData, scaling: Vec3): VolumetricData;
@@ -75,7 +75,7 @@ export function pad_periodic_grid(grid, dims, pad_fraction) {
75
75
  }
76
76
  // Max total grid points before downsampling is applied for isosurface extraction.
77
77
  // 500K balances visual quality with interactive performance (<200ms marching cubes).
78
- const MAX_GRID_POINTS = 500_000;
78
+ export const MAX_GRID_POINTS = 500_000;
79
79
  // Downsample a 3D volumetric grid to keep total point count under a budget.
80
80
  // Uses block averaging to preserve data fidelity while reducing grid dimensions.
81
81
  // Returns original grid/dims if already within budget.
@@ -147,16 +147,17 @@ export const DEFAULT_ISOSURFACE_SETTINGS = {
147
147
  wireframe: false,
148
148
  halo: 0,
149
149
  };
150
+ const field_has_significant_negatives = ({ min, abs_max }) => min < -abs_max * 0.01;
151
+ const default_isovalue = ({ abs_max }) => abs_max > 0 ? abs_max * 0.2 : DEFAULT_ISOSURFACE_SETTINGS.isovalue;
150
152
  // Compute reasonable isosurface settings from a volume's data range.
151
153
  // Sets isovalue to 20% of abs_max and enables negative lobe when data has
152
154
  // significant negative values (>1% of max).
153
155
  export function auto_isosurface_settings(data_range) {
154
- const has_negatives = data_range.min < -data_range.abs_max * 0.01;
155
156
  return {
156
157
  ...DEFAULT_ISOSURFACE_SETTINGS,
157
158
  // Fall back to default isovalue for all-zero grids to keep controls usable
158
- isovalue: data_range.abs_max > 0 ? data_range.abs_max * 0.2 : DEFAULT_ISOSURFACE_SETTINGS.isovalue,
159
- show_negative: has_negatives,
159
+ isovalue: default_isovalue(data_range),
160
+ show_negative: field_has_significant_negatives(data_range),
160
161
  };
161
162
  }
162
163
  // Generate N evenly-spaced isosurface layers across a data range.
@@ -165,7 +166,7 @@ export function auto_isosurface_settings(data_range) {
165
166
  export function generate_layers(data_range, n_layers) {
166
167
  if (n_layers <= 0 || data_range.abs_max <= 0)
167
168
  return [];
168
- const has_negatives = data_range.min < -data_range.abs_max * 0.01;
169
+ const show_negative = field_has_significant_negatives(data_range);
169
170
  // Space isovalues from high (inner) to low (outer)
170
171
  return Array.from({ length: n_layers }, (_, idx) => {
171
172
  // Fraction from 0.8 (inner) to 0.1 (outer)
@@ -175,18 +176,129 @@ export function generate_layers(data_range, n_layers) {
175
176
  color: LAYER_COLORS[idx % LAYER_COLORS.length],
176
177
  opacity: n_layers === 1 ? 0.6 : 0.8 - idx * (0.5 / Math.max(n_layers - 1, 1)),
177
178
  visible: true,
178
- show_negative: has_negatives,
179
+ show_negative,
179
180
  negative_color: LAYER_COLORS[(idx + 1) % LAYER_COLORS.length],
180
181
  };
181
182
  });
182
183
  }
183
- // Tile (repeat) volumetric data to fill a supercell.
184
+ // Build a default isosurface layer for a newly added volume: isovalue at 20% of
185
+ // abs_max, next unused palette color, and negative lobe when the data is signed.
186
+ export function auto_volume_layer(volume, volume_idx, color_offset = 0) {
187
+ return {
188
+ isovalue: default_isovalue(volume.data_range),
189
+ color: LAYER_COLORS[color_offset % LAYER_COLORS.length],
190
+ opacity: 0.6,
191
+ visible: true,
192
+ show_negative: field_has_significant_negatives(volume.data_range),
193
+ negative_color: LAYER_COLORS[(color_offset + 1) % LAYER_COLORS.length],
194
+ volume_idx,
195
+ };
196
+ }
197
+ // Convert single-isovalue settings into an explicit layers array (no-op when
198
+ // layers already exist — an explicit empty array means zero surfaces and is
199
+ // preserved). Used when entering multi-volume mode so the implicit
200
+ // active-volume surface survives as an editable layer.
201
+ export function materialize_layers(settings, active_volume_idx) {
202
+ if (settings.layers) {
203
+ // Pin any layers still relying on the implicit active volume
204
+ return settings.layers.map((layer) => ({
205
+ ...layer,
206
+ volume_idx: layer.volume_idx ?? active_volume_idx,
207
+ }));
208
+ }
209
+ return [
210
+ {
211
+ isovalue: settings.isovalue,
212
+ color: settings.positive_color,
213
+ opacity: settings.opacity,
214
+ visible: true,
215
+ show_negative: settings.show_negative,
216
+ negative_color: settings.negative_color,
217
+ volume_idx: active_volume_idx,
218
+ },
219
+ ];
220
+ }
221
+ // Remove a volume from the registry: drops layers whose geometry references it,
222
+ // unsets color sources pointing at it, and shifts higher indices down by one.
223
+ // Layers without an explicit volume_idx implicitly reference `active_volume_idx`.
224
+ export function remove_volume(volumes, layers, removed_idx, active_volume_idx = 0) {
225
+ const remap = (idx) => {
226
+ if (idx === undefined || idx === removed_idx)
227
+ return undefined;
228
+ return idx > removed_idx ? idx - 1 : idx;
229
+ };
230
+ return {
231
+ volumes: volumes.filter((_vol, idx) => idx !== removed_idx),
232
+ layers: layers
233
+ .filter((layer) => (layer.volume_idx ?? active_volume_idx) !== removed_idx)
234
+ .map((layer) => ({
235
+ ...layer,
236
+ volume_idx: remap(layer.volume_idx ?? active_volume_idx) ?? 0,
237
+ color_volume_idx: remap(layer.color_volume_idx),
238
+ })),
239
+ };
240
+ }
241
+ // Label volumes parsed from a file with a stable `source` id (compression-
242
+ // stripped filename) and a display label (multi-block files like spin-polarized
243
+ // CHGCAR get "<file>: <block label>" labels).
244
+ export function label_file_volumes(volumes, filename) {
245
+ const source = filename.replace(/\.(?:gz|gzip|bz2|xz|zst)$/i, ``);
246
+ return volumes.map((vol, idx) => ({
247
+ ...vol,
248
+ source,
249
+ label: volumes.length > 1 ? `${source}: ${vol.label ?? idx + 1}` : source,
250
+ }));
251
+ }
252
+ // Two lattices describe the same cell when all matrix entries agree within
253
+ // tolerance — the signal that an imported file is another scalar field of the
254
+ // already-loaded system and should be appended rather than replace the scene.
255
+ export const lattices_match = (lattice_a, lattice_b, tolerance = 0.05) => Boolean(lattice_a && lattice_b) &&
256
+ lattice_a.every((row, row_idx) => row.every((val, col_idx) => Math.abs(val - lattice_b[row_idx][col_idx]) < tolerance));
257
+ // Merge volumes from a newly imported file (pre-labeled via label_file_volumes)
258
+ // into an existing registry. Reimporting a source with the same block count
259
+ // replaces its volumes in place, preserving user-tuned layers; a changed block
260
+ // count drops the stale group (remapping layer indices) before appending fresh
261
+ // volumes. New volumes each get an auto-generated layer.
262
+ // Layers without an explicit volume_idx implicitly reference `active_volume_idx`
263
+ // and are pinned to it up front so index remapping treats them correctly.
264
+ export function merge_imported_volumes(existing, existing_layers, incoming, active_volume_idx = 0) {
265
+ const source = incoming[0]?.source;
266
+ let volumes = [...existing];
267
+ let layers = existing_layers.map((layer) => ({
268
+ ...layer,
269
+ volume_idx: layer.volume_idx ?? active_volume_idx,
270
+ }));
271
+ const group_indices = volumes
272
+ .map((vol, idx) => (source !== undefined && vol.source === source ? idx : -1))
273
+ .filter((idx) => idx >= 0);
274
+ if (group_indices.length === incoming.length && incoming.length > 0) {
275
+ // Same source, same block count: replace in place, keep layer settings
276
+ for (const [incoming_idx, vol_idx] of group_indices.entries()) {
277
+ volumes[vol_idx] = incoming[incoming_idx];
278
+ }
279
+ return { volumes, layers, first_touched_idx: group_indices[0], n_added: 0 };
280
+ }
281
+ // Drop any stale volumes from the same source (block count changed)
282
+ for (let removed = group_indices.length - 1; removed >= 0; removed--) {
283
+ ;
284
+ ({ volumes, layers } = remove_volume(volumes, layers, group_indices[removed]));
285
+ }
286
+ const first_touched_idx = volumes.length;
287
+ for (const vol of incoming) {
288
+ layers.push(auto_volume_layer(vol, volumes.length, layers.length));
289
+ volumes.push(vol);
290
+ }
291
+ return { volumes, layers, first_touched_idx, n_added: incoming.length };
292
+ }
293
+ // Tile (repeat) periodic volumetric data to fill a supercell. Finite volumes
294
+ // remain unchanged: structure supercells must not implicitly repeat molecular
295
+ // or other bounded scalar fields.
184
296
  // Pre-downsamples the source grid when the tiled result would exceed MAX_GRID_POINTS
185
297
  // to avoid large temporary allocations. Returns the original volume unchanged for
186
298
  // [1,1,1] scaling.
187
299
  export function tile_volumetric_data(volume, scaling) {
188
300
  const [sx, sy, sz] = scaling;
189
- if (sx === 1 && sy === 1 && sz === 1)
301
+ if (!volume.periodic || (sx === 1 && sy === 1 && sz === 1))
190
302
  return volume;
191
303
  const total_cells = sx * sy * sz;
192
304
  let src_grid = volume.grid;
@@ -0,0 +1,4 @@
1
+ export declare const format_path: (segments: (string | number)[]) => string;
2
+ export declare const build_path: (parent_path: string, key: string | number) => string;
3
+ export declare function parse_path(path: string): (string | number)[];
4
+ export declare function resolve_path(root: unknown, path: string): unknown;
@@ -0,0 +1,89 @@
1
+ // Dot/bracket path codec for arbitrary JavaScript object keys.
2
+ // Special keys use JSON.stringify so quotes/backslashes/brackets round-trip.
3
+ const PATH_IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/u;
4
+ function format_path_segment(segment, is_first = false) {
5
+ if (typeof segment === `number`)
6
+ return `[${segment}]`;
7
+ if (PATH_IDENTIFIER_RE.test(segment))
8
+ return is_first ? segment : `.${segment}`;
9
+ return `[${JSON.stringify(segment)}]`;
10
+ }
11
+ export const format_path = (segments) => segments.map((segment, idx) => format_path_segment(segment, idx === 0)).join(``);
12
+ export const build_path = (parent_path, key) => parent_path ? parent_path + format_path_segment(key) : format_path_segment(key, true);
13
+ export function parse_path(path) {
14
+ if (!path)
15
+ return [];
16
+ const segments = [];
17
+ let pos = 0;
18
+ while (pos < path.length) {
19
+ const char = path[pos];
20
+ if (char === `.`) {
21
+ pos++;
22
+ continue;
23
+ }
24
+ if (char !== `[`) {
25
+ const start = pos;
26
+ while (pos < path.length && path[pos] !== `.` && path[pos] !== `[`)
27
+ pos++;
28
+ if (pos > start)
29
+ segments.push(path.slice(start, pos));
30
+ continue;
31
+ }
32
+ pos++; // skip `[`
33
+ if (path[pos] === `]`) {
34
+ pos++;
35
+ continue;
36
+ }
37
+ if (path[pos] === `"`) {
38
+ const json_start = pos;
39
+ let escaped = false;
40
+ pos++;
41
+ while (pos < path.length) {
42
+ const quote_char = path[pos];
43
+ if (escaped)
44
+ escaped = false;
45
+ else if (quote_char === `\\`)
46
+ escaped = true;
47
+ else if (quote_char === `"`)
48
+ break;
49
+ pos++;
50
+ }
51
+ if (pos >= path.length) {
52
+ segments.push(path.slice(json_start + 1));
53
+ break;
54
+ }
55
+ try {
56
+ segments.push(JSON.parse(path.slice(json_start, pos + 1)));
57
+ }
58
+ catch {
59
+ segments.push(path.slice(json_start + 1, pos));
60
+ }
61
+ pos++;
62
+ if (path[pos] === `]`)
63
+ pos++;
64
+ continue;
65
+ }
66
+ const start = pos;
67
+ while (pos < path.length && path[pos] !== `]`)
68
+ pos++;
69
+ const token = path.slice(start, pos);
70
+ if (token) {
71
+ const num = Number(token);
72
+ segments.push(Number.isNaN(num) ? token : num);
73
+ }
74
+ if (path[pos] === `]`)
75
+ pos++;
76
+ }
77
+ return segments;
78
+ }
79
+ export function resolve_path(root, path) {
80
+ if (!path)
81
+ return root;
82
+ let current = root;
83
+ for (const key of parse_path(path)) {
84
+ if (current == null || typeof current !== `object`)
85
+ return undefined;
86
+ current = current[key];
87
+ }
88
+ return current;
89
+ }