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
@@ -81,7 +81,7 @@ export const IMAGINARY_MODE_NOISE_THRESHOLD = 0.005; // Clamp negatives < 0.5% a
81
81
  export function pretty_sym_point(symbol) {
82
82
  if (!symbol)
83
83
  return ``;
84
- // Remove underscores (htmlify maps S0 S<sub>0</sub> but leaves S_0 as is)
84
+ // Remove underscores so S_0 and S0 are treated alike
85
85
  // Replace common symmetry point names with Greek letters
86
86
  // Handle both plain names (GAMMA) and LaTeX notation (\Gamma) from pymatgen
87
87
  // Handle subscripts: convert S0 to S₀, K1 to K₁, Γ1 to Γ₁, etc.
@@ -336,35 +336,15 @@ const SPIN_DOWN_KEYS = [`-1`, `Spin.down`];
336
336
  export function extract_spin_channels(data) {
337
337
  if (Array.isArray(data))
338
338
  return { up: data, down: null };
339
- if (data && typeof data === `object`) {
340
- const record = data;
341
- let spin_up = null;
342
- let spin_down = null;
343
- // Extract spin-up channel
344
- for (const key of SPIN_UP_KEYS) {
345
- if (key in record) {
346
- spin_up = record[key];
347
- break;
348
- }
349
- }
350
- // Extract spin-down channel
351
- for (const key of SPIN_DOWN_KEYS) {
352
- if (key in record) {
353
- spin_down = record[key];
354
- break;
355
- }
356
- }
357
- // Fall back to first key if no spin-up key found
358
- if (spin_up === null) {
359
- const keys = Object.keys(record);
360
- if (keys.length > 0)
361
- spin_up = record[keys[0]];
362
- }
363
- if (spin_up === null)
364
- return null;
365
- return { up: spin_up, down: spin_down };
366
- }
367
- return null;
339
+ if (!data || typeof data !== `object`)
340
+ return null;
341
+ const record = data;
342
+ const up_key = SPIN_UP_KEYS.find((key) => key in record);
343
+ const down_key = SPIN_DOWN_KEYS.find((key) => key in record);
344
+ // No spin-up key: do not fall back to Object.keys()[0] (could be spin-down)
345
+ if (up_key === undefined)
346
+ return null;
347
+ return { up: record[up_key], down: down_key !== undefined ? record[down_key] : null };
368
348
  }
369
349
  // Convert pymatgen PhononBandStructureSymmLine or BandStructure to matterviz format
370
350
  function convert_pymatgen_band_structure(pmg) {
@@ -640,6 +620,8 @@ export function find_qpoint_at_distance(band_struct, target) {
640
620
  return null;
641
621
  return distance.reduce((closest, dist, idx) => Math.abs(dist - target) < Math.abs(distance[closest] - target) ? idx : closest, 0);
642
622
  }
623
+ // Look up a branch's x-range in the plot via its start/end q-point labels
624
+ const branch_x_range = (bs, branch, x_positions) => x_positions[get_segment_key(bs.qpoints[branch.start_index]?.label ?? undefined, bs.qpoints[branch.end_index]?.label ?? undefined)];
643
625
  // Rescaled x-position of a q-point index along the band plot path. Inverse of
644
626
  // find_qpoint_at_rescaled_x, used to highlight a q-point hovered in the Brillouin zone.
645
627
  // Returns null if the index doesn't fall on a plotted (non-discontinuity) branch.
@@ -649,9 +631,7 @@ export function qpoint_x_position(band_struct, qpoint_index, x_positions) {
649
631
  for (const branch of band_struct.branches) {
650
632
  if (qpoint_index < branch.start_index || qpoint_index > branch.end_index)
651
633
  continue;
652
- const start_label = band_struct.qpoints[branch.start_index]?.label ?? undefined;
653
- const end_label = band_struct.qpoints[branch.end_index]?.label ?? undefined;
654
- const range = x_positions[get_segment_key(start_label, end_label)];
634
+ const range = branch_x_range(band_struct, branch, x_positions);
655
635
  if (!range)
656
636
  continue;
657
637
  const [x_start, x_end] = range;
@@ -671,74 +651,50 @@ export function find_qpoint_at_rescaled_x(band_struct, rescaled_x, x_positions)
671
651
  return null;
672
652
  // Find which segment contains this x coordinate
673
653
  for (const branch of band_struct.branches) {
674
- const start_idx = branch.start_index;
675
- const end_idx = branch.end_index;
676
- const start_label = band_struct.qpoints[start_idx]?.label ?? undefined;
677
- const end_label = band_struct.qpoints[end_idx]?.label ?? undefined;
678
- const segment_key = get_segment_key(start_label, end_label);
679
- const segment_range = x_positions[segment_key];
654
+ const { start_index: start_idx, end_index: end_idx } = branch;
655
+ const segment_range = branch_x_range(band_struct, branch, x_positions);
680
656
  if (!segment_range)
681
657
  continue;
682
658
  const [x_start, x_end] = segment_range;
683
- // Check if discontinuity (zero-length segment)
684
- const is_discontinuity = Math.abs(x_end - x_start) < 1e-6;
685
- if (is_discontinuity) {
686
- // For discontinuities, check if x is exactly at this point
687
- if (Math.abs(rescaled_x - x_start) < 1e-6) {
659
+ // Discontinuity (zero-length segment): match only if x is exactly at this point
660
+ if (Math.abs(x_end - x_start) < 1e-6) {
661
+ if (Math.abs(rescaled_x - x_start) < 1e-6)
688
662
  return start_idx;
689
- }
690
663
  continue;
691
664
  }
692
665
  // Check if x is within this segment (with small tolerance for edges)
693
666
  if (rescaled_x >= x_start - 1e-6 && rescaled_x <= x_end + 1e-6) {
694
667
  // Map from rescaled x back to original distance
695
- const segment_distances = band_struct.distance.slice(start_idx, end_idx + 1);
696
- const dist_min = segment_distances[0];
697
- const dist_max = segment_distances.at(-1) ?? dist_min;
698
- const dist_range = dist_max - dist_min;
699
- // Handle zero-length segments
700
- if (dist_range === 0) {
668
+ const dist_min = band_struct.distance[start_idx];
669
+ const dist_range = band_struct.distance[end_idx] - dist_min;
670
+ if (dist_range === 0)
701
671
  return start_idx;
702
- }
703
672
  // Inverse of the scaling: x = x_start + ((dist - dist_min) / dist_range) * (x_end - x_start)
704
- // Solving for dist: dist = dist_min + ((x - x_start) / (x_end - x_start)) * dist_range
705
673
  const normalized_x = (rescaled_x - x_start) / (x_end - x_start);
706
674
  const target_dist = dist_min + normalized_x * dist_range;
707
675
  // Find closest qpoint in this branch to the target distance
708
- let closest_idx = start_idx;
709
- let min_diff = Math.abs(band_struct.distance[start_idx] - target_dist);
676
+ let [closest_idx, min_diff] = [start_idx, Infinity];
710
677
  for (let idx = start_idx; idx <= end_idx; idx++) {
711
678
  const diff = Math.abs(band_struct.distance[idx] - target_dist);
712
- if (diff < min_diff) {
713
- min_diff = diff;
714
- closest_idx = idx;
715
- }
679
+ if (diff < min_diff)
680
+ [closest_idx, min_diff] = [idx, diff];
716
681
  }
717
682
  return closest_idx;
718
683
  }
719
684
  }
720
685
  // Fallback: find closest labeled point
721
- let closest_idx = 0;
722
- let min_dist = Infinity;
686
+ let [closest_idx, min_dist] = [0, Infinity];
723
687
  for (const branch of band_struct.branches) {
724
- const start_idx = branch.start_index;
725
- const end_idx = branch.end_index;
726
- const start_label = band_struct.qpoints[start_idx]?.label ?? undefined;
727
- const end_label = band_struct.qpoints[end_idx]?.label ?? undefined;
728
- const segment_key = get_segment_key(start_label, end_label);
729
- const segment_range = x_positions[segment_key];
688
+ const segment_range = branch_x_range(band_struct, branch, x_positions);
730
689
  if (!segment_range)
731
690
  continue;
732
- const [x_start, x_end] = segment_range;
733
691
  for (const [x_pos, idx] of [
734
- [x_start, start_idx],
735
- [x_end, end_idx],
692
+ [segment_range[0], branch.start_index],
693
+ [segment_range[1], branch.end_index],
736
694
  ]) {
737
695
  const dist = Math.abs(rescaled_x - x_pos);
738
- if (dist < min_dist) {
739
- min_dist = dist;
740
- closest_idx = idx;
741
- }
696
+ if (dist < min_dist)
697
+ [closest_idx, min_dist] = [idx, dist];
742
698
  }
743
699
  }
744
700
  return closest_idx;
@@ -791,24 +747,15 @@ const shift_dos_energies = (dos, shift) => ({
791
747
  // Recursively shifts nested DOS in atom_dos and spd_dos for consistency
792
748
  export function shift_to_fermi(dos) {
793
749
  const shift = dos.efermi;
794
- // Shift root DOS energies using the shared helper
795
- const shifted_root = shift_dos_energies(dos, shift);
796
- // Shift nested atom_dos if present
797
- const atom_dos = dos.atom_dos
798
- ? Object.fromEntries(Object.entries(dos.atom_dos).map(([key, nested_dos]) => [
799
- key,
800
- shift_dos_energies(nested_dos, shift),
801
- ]))
802
- : undefined;
803
- // Shift nested spd_dos if present
804
- const spd_dos = dos.spd_dos
805
- ? Object.fromEntries(Object.entries(dos.spd_dos).map(([key, nested_dos]) => [
750
+ const shift_nested = (nested) => nested &&
751
+ Object.fromEntries(Object.entries(nested).map(([key, nested_dos]) => [
806
752
  key,
807
753
  shift_dos_energies(nested_dos, shift),
808
- ]))
809
- : undefined;
754
+ ]));
755
+ const atom_dos = shift_nested(dos.atom_dos);
756
+ const spd_dos = shift_nested(dos.spd_dos);
810
757
  return {
811
- ...shifted_root,
758
+ ...shift_dos_energies(dos, shift),
812
759
  efermi: 0, // Explicitly set to 0 (shift_dos_energies would give efermi - shift)
813
760
  ...(atom_dos && { atom_dos }),
814
761
  ...(spd_dos && { spd_dos }),
@@ -905,26 +852,14 @@ export function compute_frequency_range(band_structs, doses, padding_factor = 0.
905
852
  const all_freqs = [];
906
853
  // Check raw band_structs for electronic markers before normalization
907
854
  // (normalized structures always have qpoints, so we can't detect from them)
908
- let has_electronic_bs = false;
909
855
  // Support both qpoints (phonon) and kpoints (electronic) to detect single vs dict
910
856
  const is_single_bs = band_structs &&
911
857
  typeof band_structs === `object` &&
912
858
  (`qpoints` in band_structs || `kpoints` in band_structs);
913
- if (band_structs && typeof band_structs === `object`) {
914
- // Single structure check
915
- if (is_electronic_band_struct(band_structs)) {
916
- has_electronic_bs = true;
917
- }
918
- else if (!is_single_bs) {
919
- // Dict of band structures - check each value
920
- for (const bs_val of Object.values(band_structs)) {
921
- if (is_electronic_band_struct(bs_val)) {
922
- has_electronic_bs = true;
923
- break;
924
- }
925
- }
926
- }
927
- }
859
+ const has_electronic_bs = band_structs &&
860
+ typeof band_structs === `object` &&
861
+ (is_electronic_band_struct(band_structs) ||
862
+ (!is_single_bs && Object.values(band_structs).some(is_electronic_band_struct)));
928
863
  const bs_list = band_structs
929
864
  ? is_single_bs
930
865
  ? [normalize_band_structure(band_structs)]
@@ -1,8 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { contrast_color, default_element_colors } from '../colors'
3
3
  import type { CompositionType } from '../composition'
4
- import type { ElementSymbol } from '../element'
5
- import { element_data } from '../element'
4
+ import { element_by_symbol, is_elem_symbol, type ElementSymbol } from '../element'
6
5
  import Icon from '../Icon.svelte'
7
6
  import { ELEM_SYMBOLS, format_num } from '../labels'
8
7
  import { ColorBar } from '../plot'
@@ -26,7 +25,6 @@
26
25
  property_colors = null,
27
26
  elements,
28
27
  elem_color_picker_title = `Double click to reset color`,
29
- labels = $bindable([]),
30
28
  amount_format = `.3~f`,
31
29
  show_amounts = true,
32
30
  get_element_label,
@@ -49,7 +47,6 @@
49
47
  property_colors?: AtomPropertyColors | null
50
48
  elements?: CompositionType
51
49
  elem_color_picker_title?: string
52
- labels?: HTMLLabelElement[]
53
50
  amount_format?: string // Float formatting for element amounts (default: 3 significant digits)
54
51
  show_amounts?: boolean // Whether to show element amounts
55
52
  get_element_label?: (element: string, amount: number) => string // Custom label function
@@ -161,12 +158,11 @@
161
158
  if (!remap_search) return ELEM_SYMBOLS
162
159
  const query = remap_search.toLowerCase()
163
160
  return ELEM_SYMBOLS.filter((elem) => {
164
- const data = element_data?.find((el) => el.symbol === elem)
161
+ const data = element_by_symbol.get(elem)
165
162
  return elem.toLowerCase().includes(query) || data?.name?.toLowerCase().includes(query)
166
163
  })
167
164
  })
168
165
 
169
- const known_element_symbols = new Set(ELEM_SYMBOLS)
170
166
  let sorted_element_entries = $derived.by(() => {
171
167
  if (!elements) return []
172
168
  const element_amounts = elements as Record<string, number>
@@ -175,9 +171,7 @@
175
171
  return amount === undefined ? [] : [[element_symbol, amount] as const]
176
172
  })
177
173
  const unknown_entries = Object.entries(element_amounts)
178
- .filter(
179
- ([element_symbol]) => !known_element_symbols.has(element_symbol as ElementSymbol),
180
- )
174
+ .filter(([element_symbol]) => !is_elem_symbol(element_symbol))
181
175
  .sort(([element_a], [element_b]) => element_a.localeCompare(element_b))
182
176
  return [...ordered_known_entries, ...unknown_entries]
183
177
  })
@@ -326,13 +320,12 @@
326
320
  {#if show_element_legend}
327
321
  <div {...rest} class={[`atom-legend element-legend`, rest.class]}>
328
322
  {@render mode_selector_snippet()}
329
- {#each sorted_element_entries as [elem, amt], idx (elem)}
323
+ {#each sorted_element_entries as [elem, amt] (elem)}
330
324
  {@const is_hidden = hidden_elements.has(elem as ElementSymbol)}
331
325
  {@const displayed_elem = element_mapping?.[elem as ElementSymbol] || elem}
332
326
  <div class="legend-item">
333
327
  <label
334
- bind:this={labels[idx]}
335
- title="{element_data?.find((el) => el.symbol === displayed_elem)?.name ??
328
+ title="{element_by_symbol.get(displayed_elem as ElementSymbol)?.name ??
336
329
  ``}{displayed_elem !== elem ? ` (remapped from ${elem})` : ``}"
337
330
  {@attach tooltip()}
338
331
  style:background-color={colors.element[displayed_elem]}
@@ -445,7 +438,7 @@
445
438
  </button>
446
439
  {/if}
447
440
  {#each filtered_elements as target_elem (target_elem)}
448
- {@const elem_info = element_data?.find((el) => el.symbol === target_elem)}
441
+ {@const elem_info = element_by_symbol.get(target_elem)}
449
442
  <button
450
443
  class="remap-option"
451
444
  class:selected={displayed_elem === target_elem}
@@ -550,6 +543,8 @@
550
543
  .atom-legend {
551
544
  position: absolute;
552
545
  z-index: var(--legend-z-index, 1);
546
+ /* own compositing layer, or WKWebView paints the canvas over this (see app.css) */
547
+ will-change: transform;
553
548
  pointer-events: auto;
554
549
  visibility: visible;
555
550
  filter: var(--legend-filter, grayscale(10%) brightness(0.95) saturate(0.8));
@@ -1,5 +1,5 @@
1
1
  import type { CompositionType } from '../composition';
2
- import type { ElementSymbol } from '../element';
2
+ import { type ElementSymbol } from '../element';
3
3
  import type { AnyStructure } from './';
4
4
  import type { AtomColorConfig, AtomPropertyColors } from './atom-properties';
5
5
  import type { MoyoDataset } from '@spglib/moyo-wasm';
@@ -11,7 +11,6 @@ type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, `children`> & {
11
11
  property_colors?: AtomPropertyColors | null;
12
12
  elements?: CompositionType;
13
13
  elem_color_picker_title?: string;
14
- labels?: HTMLLabelElement[];
15
14
  amount_format?: string;
16
15
  show_amounts?: boolean;
17
16
  get_element_label?: (element: string, amount: number) => string;
@@ -30,6 +29,6 @@ type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, `children`> & {
30
29
  structure?: AnyStructure | null;
31
30
  }]>;
32
31
  };
33
- declare const AtomLegend: import("svelte").Component<$$ComponentProps, {}, "labels" | "hidden_elements" | "hidden_prop_vals" | "element_radius_overrides" | "site_radius_overrides" | "atom_color_config" | "element_mapping">;
32
+ declare const AtomLegend: import("svelte").Component<$$ComponentProps, {}, "hidden_elements" | "hidden_prop_vals" | "element_radius_overrides" | "site_radius_overrides" | "atom_color_config" | "element_mapping">;
34
33
  type AtomLegend = ReturnType<typeof AtomLegend>;
35
34
  export default AtomLegend;
@@ -29,6 +29,26 @@
29
29
  let input_value = $state(supercell_scaling)
30
30
  let input_valid = $derived(is_valid_supercell_input(input_value))
31
31
 
32
+ // Hover-intent gate: opening instantly on mouseenter made the menu pop open
33
+ // when merely scrubbing the pointer across compact embeds (carousel cards).
34
+ // Deliberate hovers still open it; keyboard focus and clicks open instantly.
35
+ const hover_open_delay_ms = 200
36
+ let hover_timer: ReturnType<typeof setTimeout> | undefined
37
+
38
+ const schedule_hover_open = () => {
39
+ if (suppress_hover) return
40
+ clearTimeout(hover_timer)
41
+ // re-check suppression at fire time: a sibling popover may have opened meanwhile
42
+ hover_timer = setTimeout(() => (menu_open = !suppress_hover), hover_open_delay_ms)
43
+ }
44
+
45
+ const close_menu = () => {
46
+ clearTimeout(hover_timer)
47
+ menu_open = false
48
+ }
49
+
50
+ $effect(() => () => clearTimeout(hover_timer))
51
+
32
52
  const supercell_presets = [`1x1x1`, `2x2x2`, `3x3x3`, `2x2x1`, `3x3x1`, `2x1x1`]
33
53
 
34
54
  // Always show all 3 cell types - Prim/Conv disabled without sym_data
@@ -51,13 +71,13 @@
51
71
  function apply_preset(preset: string) {
52
72
  supercell_scaling = preset
53
73
  input_value = preset
54
- menu_open = false
74
+ close_menu()
55
75
  }
56
76
 
57
77
  function handle_input_submit() {
58
78
  if (input_valid && input_value !== supercell_scaling) {
59
79
  supercell_scaling = input_value
60
- menu_open = false
80
+ close_menu()
61
81
  }
62
82
  }
63
83
 
@@ -69,11 +89,11 @@
69
89
  !(next_target instanceof Node) ||
70
90
  !current_target.contains(next_target)
71
91
  )
72
- menu_open = false
92
+ close_menu()
73
93
  }
74
94
 
75
95
  function handle_key_down(event: KeyboardEvent, submit_on_enter: boolean = false) {
76
- if (event.key === `Escape`) menu_open = false
96
+ if (event.key === `Escape`) close_menu()
77
97
  if (submit_on_enter && event.key === `Enter`) handle_input_submit()
78
98
  }
79
99
 
@@ -87,22 +107,25 @@
87
107
  // Close + keep closed while suppressed so the menu can't obscure a sibling popover
88
108
  // (e.g. the atom color-mode dropdown) the user is actively interacting with
89
109
  $effect(() => {
90
- if (suppress_hover) menu_open = false
110
+ if (suppress_hover) close_menu()
91
111
  })
92
112
  </script>
93
113
 
94
114
  <div
95
115
  class="cell-select"
96
116
  role="group"
97
- {@attach click_outside({ callback: () => (menu_open = false) })}
98
- onmouseenter={() => (menu_open = !suppress_hover)}
99
- onmouseleave={() => (menu_open = false)}
117
+ {@attach click_outside({ callback: close_menu })}
118
+ onmouseenter={schedule_hover_open}
119
+ onmouseleave={close_menu}
100
120
  onfocusin={() => (menu_open = !suppress_hover)}
101
121
  onfocusout={handle_focus_out}
102
122
  >
103
123
  <button
104
124
  type="button"
105
- onclick={() => (menu_open = !suppress_hover && !menu_open)}
125
+ onclick={() => {
126
+ clearTimeout(hover_timer)
127
+ menu_open = !suppress_hover && !menu_open
128
+ }}
106
129
  onkeydown={handle_key_down}
107
130
  class="toggle-btn"
108
131
  class:active={menu_open}
@@ -224,7 +247,9 @@
224
247
  display: flex;
225
248
  flex-direction: column;
226
249
  gap: 5px;
227
- z-index: 100;
250
+ z-index: var(--z-index-viewer-dropdown, 100);
251
+ /* own compositing layer, or WKWebView paints the canvas over this (see app.css) */
252
+ will-change: transform;
228
253
  font-size: var(--struct-cell-select-dropdown-font, max(10px, 1em));
229
254
  min-width: 118px;
230
255
  }