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
@@ -250,7 +250,7 @@
250
250
 
251
251
  let num_structures = $derived(Object.keys(band_structs_dict).length)
252
252
  let all_segment_keys = $derived(Object.keys(all_segments))
253
- let common_segment_keys = $derived.by(() =>
253
+ let common_segment_keys = $derived(
254
254
  all_segment_keys.filter(
255
255
  (segment_key) => all_segments[segment_key].length === num_structures,
256
256
  ),
@@ -337,124 +337,145 @@
337
337
  x_positions = positions
338
338
  })
339
339
 
340
+ // Plottable branch segments with rescaled x coordinates, shared by the band-line
341
+ // series and fat-band ribbon computations below
342
+ interface PlotSegment {
343
+ bs_idx: number
344
+ label: string
345
+ bs: BaseBandStructure
346
+ start_idx: number
347
+ end_idx: number // exclusive
348
+ segment_key: string
349
+ scaled_distances: number[]
350
+ }
351
+ let plot_segments = $derived.by((): PlotSegment[] => {
352
+ const segments: PlotSegment[] = []
353
+ for (const [bs_idx, [label, bs]] of Object.entries(band_structs_dict).entries()) {
354
+ for (const branch of bs.branches) {
355
+ // Skip discontinuous segments (consecutive labeled points)
356
+ if (branch.end_index - branch.start_index === 1) continue
357
+
358
+ const start_idx = branch.start_index
359
+ const end_idx = branch.end_index + 1
360
+ const segment_key = helpers.get_segment_key(
361
+ bs.qpoints[start_idx]?.label ?? undefined,
362
+ bs.qpoints[end_idx - 1]?.label ?? undefined,
363
+ )
364
+ if (!segments_to_plot.has(segment_key)) continue
365
+
366
+ const [x_start, x_end] = x_positions?.[segment_key] || [0, 1]
367
+ const scaled_distances = helpers.scale_segment_distances(
368
+ bs.distance.slice(start_idx, end_idx),
369
+ x_start,
370
+ x_end,
371
+ )
372
+ segments.push({ bs_idx, label, bs, start_idx, end_idx, segment_key, scaled_distances })
373
+ }
374
+ }
375
+ return segments
376
+ })
377
+
340
378
  // Convert band structures to scatter plot series + track max slope in one pass
341
379
  let { series_data, max_abs_slope } = $derived.by(
342
380
  (): {
343
381
  series_data: DataSeries[]
344
382
  max_abs_slope: number
345
383
  } => {
346
- if (Object.keys(band_structs_dict).length === 0 || segments_to_plot.size === 0) {
347
- return { series_data: [], max_abs_slope: 1 }
348
- }
349
-
350
384
  const all_series: DataSeries[] = []
351
385
  let max_slope = 0
352
-
353
- for (const [bs_idx, [label, bs]] of Object.entries(band_structs_dict).entries()) {
386
+ // Cache per band structure: segments share their bs, so avoid re-scanning
387
+ // all q-points once per plotted segment
388
+ const gamma_cache = new Map<BaseBandStructure, number[]>()
389
+
390
+ for (const {
391
+ bs_idx,
392
+ label,
393
+ bs,
394
+ start_idx,
395
+ end_idx,
396
+ scaled_distances,
397
+ } of plot_segments) {
354
398
  const color = PLOT_COLORS[bs_idx % PLOT_COLORS.length]
355
399
  const structure_label = label || `Structure ${bs_idx + 1}`
356
- const gamma_indices =
357
- detected_band_type === `phonon` ? helpers.find_gamma_indices(bs) : []
358
-
359
- for (const branch of bs.branches) {
360
- const start_idx = branch.start_index
361
- const end_idx = branch.end_index + 1
362
- const start_label = bs.qpoints[start_idx]?.label ?? undefined
363
- const end_label = bs.qpoints[end_idx - 1]?.label ?? undefined
364
- const segment_key = helpers.get_segment_key(start_label, end_label)
365
-
366
- if (!segments_to_plot.has(segment_key)) continue
367
-
368
- // Skip discontinuous segments (consecutive labeled points)
369
- const is_discontinuity = branch.end_index - branch.start_index === 1
370
- if (is_discontinuity) continue
371
-
372
- const [x_start, x_end] = x_positions?.[segment_key] || [0, 1]
373
-
374
- // Scale distances for this segment
375
- const segment_distances = bs.distance.slice(start_idx, end_idx)
376
- const scaled_distances = helpers.scale_segment_distances(
377
- segment_distances,
378
- x_start,
379
- x_end,
380
- )
400
+ let gamma_indices = gamma_cache.get(bs)
401
+ if (!gamma_indices) {
402
+ gamma_indices = detected_band_type === `phonon` ? helpers.find_gamma_indices(bs) : []
403
+ gamma_cache.set(bs, gamma_indices)
404
+ }
381
405
 
382
- // Create series for each band (and spin channel for electronic structures)
383
- for (let band_idx = 0; band_idx < bs.nb_bands; band_idx++) {
384
- const frequencies = convert_band_values(
385
- bs.bands[band_idx].slice(start_idx, end_idx),
386
- )
387
- const is_acoustic = helpers.classify_acoustic(bs, band_idx, gamma_indices)
406
+ // Create series for each band (and spin channel for electronic structures)
407
+ for (let band_idx = 0; band_idx < bs.nb_bands; band_idx++) {
408
+ const frequencies = convert_band_values(bs.bands[band_idx].slice(start_idx, end_idx))
409
+ const is_acoustic = helpers.classify_acoustic(bs, band_idx, gamma_indices)
388
410
 
389
- const line_style_up = get_line_style(
390
- color,
391
- is_acoustic === true,
392
- frequencies,
393
- band_idx,
394
- )
411
+ const line_style_up = get_line_style(
412
+ color,
413
+ is_acoustic === true,
414
+ frequencies,
415
+ band_idx,
416
+ )
395
417
 
396
- const spin_down_band = bs.spin_down_bands?.[band_idx]
397
- const has_spin_down_channel =
398
- detected_band_type === `electronic` &&
399
- Array.isArray(spin_down_band) &&
400
- spin_down_band.length >= end_idx
401
-
402
- const track_max_slope = (meta: helpers.BandPointMeta[]) => {
403
- for (const pt of meta) {
404
- if (typeof pt.slope === `number` && Number.isFinite(pt.slope)) {
405
- max_slope = Math.max(max_slope, Math.abs(pt.slope))
406
- }
418
+ const spin_down_band = bs.spin_down_bands?.[band_idx]
419
+ const has_spin_down_channel =
420
+ detected_band_type === `electronic` &&
421
+ Array.isArray(spin_down_band) &&
422
+ spin_down_band.length >= end_idx
423
+
424
+ const track_max_slope = (meta: helpers.BandPointMeta[]) => {
425
+ for (const pt of meta) {
426
+ if (typeof pt.slope === `number` && Number.isFinite(pt.slope)) {
427
+ max_slope = Math.max(max_slope, Math.abs(pt.slope))
407
428
  }
408
429
  }
430
+ }
409
431
 
410
- if (effective_spin_mode !== `down_only`) {
411
- const meta = helpers.build_point_metadata({
412
- x_vals: scaled_distances,
413
- y_vals: frequencies,
414
- band_idx,
415
- spin: `up`,
416
- is_acoustic,
417
- bs,
418
- start_idx,
419
- })
420
- track_max_slope(meta)
421
- all_series.push({
422
- x: scaled_distances,
423
- y: frequencies,
424
- markers: `line`,
425
- label: has_spin_down_channel ? `${structure_label} (↑)` : structure_label,
426
- line_style: line_style_up,
427
- metadata: meta,
428
- })
429
- }
432
+ if (effective_spin_mode !== `down_only`) {
433
+ const meta = helpers.build_point_metadata({
434
+ x_vals: scaled_distances,
435
+ y_vals: frequencies,
436
+ band_idx,
437
+ spin: `up`,
438
+ is_acoustic,
439
+ bs,
440
+ start_idx,
441
+ })
442
+ track_max_slope(meta)
443
+ all_series.push({
444
+ x: scaled_distances,
445
+ y: frequencies,
446
+ markers: `line`,
447
+ label: has_spin_down_channel ? `${structure_label} (↑)` : structure_label,
448
+ line_style: line_style_up,
449
+ metadata: meta,
450
+ })
451
+ }
430
452
 
431
- if (has_spin_down_channel && effective_spin_mode !== `up_only`) {
432
- const spin_down_frequencies = convert_band_values(
433
- spin_down_band.slice(start_idx, end_idx),
434
- )
435
- const meta = helpers.build_point_metadata({
436
- x_vals: scaled_distances,
437
- y_vals: spin_down_frequencies,
438
- band_idx,
439
- spin: `down`,
440
- is_acoustic,
441
- bs,
442
- start_idx,
443
- })
444
- track_max_slope(meta)
445
- all_series.push({
446
- x: scaled_distances,
447
- y: spin_down_frequencies,
448
- markers: `line`,
449
- label: `${structure_label} (↓)`,
450
- line_style: {
451
- ...line_style_up,
452
- line_dash: `4,2`,
453
- stroke_width: Math.max(1, line_style_up.stroke_width - 0.1),
454
- },
455
- metadata: meta,
456
- })
457
- }
453
+ if (has_spin_down_channel && effective_spin_mode !== `up_only`) {
454
+ const spin_down_frequencies = convert_band_values(
455
+ spin_down_band.slice(start_idx, end_idx),
456
+ )
457
+ const meta = helpers.build_point_metadata({
458
+ x_vals: scaled_distances,
459
+ y_vals: spin_down_frequencies,
460
+ band_idx,
461
+ spin: `down`,
462
+ is_acoustic,
463
+ bs,
464
+ start_idx,
465
+ })
466
+ track_max_slope(meta)
467
+ all_series.push({
468
+ x: scaled_distances,
469
+ y: spin_down_frequencies,
470
+ markers: `line`,
471
+ label: `${structure_label} (↓)`,
472
+ line_style: {
473
+ ...line_style_up,
474
+ line_dash: `4,2`,
475
+ stroke_width: Math.max(1, line_style_up.stroke_width - 0.1),
476
+ },
477
+ metadata: meta,
478
+ })
458
479
  }
459
480
  }
460
481
  }
@@ -465,67 +486,39 @@
465
486
 
466
487
  // Compute ribbon data for bands with width information
467
488
  let ribbon_data = $derived.by((): RibbonData[] => {
468
- if (Object.keys(band_structs_dict).length === 0 || segments_to_plot.size === 0) {
469
- return []
470
- }
471
-
472
489
  const all_ribbons: RibbonData[] = []
473
490
 
474
- for (const [bs_idx, [label, bs]] of Object.entries(band_structs_dict).entries()) {
491
+ for (const segment of plot_segments) {
492
+ const { bs_idx, label, bs, start_idx, end_idx, segment_key, scaled_distances } = segment
475
493
  // Skip if this band structure has no width data
476
- if (!bs.band_widths || bs.band_widths.length === 0) continue
494
+ if (!bs.band_widths?.length) continue
477
495
 
478
- const color = PLOT_COLORS[bs_idx % PLOT_COLORS.length]
479
- const structure_label = label || `Structure ${bs_idx + 1}`
480
496
  const config = helpers.get_ribbon_config(ribbon_config, label)
497
+ const structure_label = label || `Structure ${bs_idx + 1}`
481
498
 
482
- for (const branch of bs.branches) {
483
- const start_idx = branch.start_index
484
- const end_idx = branch.end_index + 1
485
- const start_label = bs.qpoints[start_idx]?.label ?? undefined
486
- const end_label = bs.qpoints[end_idx - 1]?.label ?? undefined
487
- const segment_key = helpers.get_segment_key(start_label, end_label)
488
-
489
- if (!segments_to_plot.has(segment_key)) continue
490
-
491
- // Skip discontinuous segments
492
- const is_discontinuity = branch.end_index - branch.start_index === 1
493
- if (is_discontinuity) continue
494
-
495
- const [x_start, x_end] = x_positions?.[segment_key] || [0, 1]
496
-
497
- // Scale distances for this segment
498
- const segment_distances = bs.distance.slice(start_idx, end_idx)
499
- const scaled_distances = helpers.scale_segment_distances(
500
- segment_distances,
501
- x_start,
502
- x_end,
503
- )
504
-
505
- // Create ribbon data for each band that has width data
506
- for (let band_idx = 0; band_idx < bs.nb_bands; band_idx++) {
507
- const band_widths = bs.band_widths[band_idx]
508
- if (!band_widths) continue
509
-
510
- const width_values = band_widths.slice(start_idx, end_idx)
511
- // Skip if all widths are zero or missing
512
- if (width_values.every((wv) => !wv || wv <= 0)) continue
513
-
514
- const y_values = convert_band_values(bs.bands[band_idx].slice(start_idx, end_idx))
515
-
516
- all_ribbons.push({
517
- x_values: scaled_distances,
518
- y_values,
519
- width_values,
520
- color: config.color ?? color,
521
- opacity: config.opacity ?? 0.3,
522
- max_width: config.max_width ?? 6,
523
- scale: config.scale ?? 1,
524
- band_idx,
525
- structure_label,
526
- segment_key,
527
- })
528
- }
499
+ // Create ribbon data for each band that has width data
500
+ for (let band_idx = 0; band_idx < bs.nb_bands; band_idx++) {
501
+ const band_widths = bs.band_widths[band_idx]
502
+ if (!band_widths) continue
503
+
504
+ const width_values = band_widths.slice(start_idx, end_idx)
505
+ // Skip if all widths are zero or missing
506
+ if (width_values.every((wv) => !wv || wv <= 0)) continue
507
+
508
+ const y_values = convert_band_values(bs.bands[band_idx].slice(start_idx, end_idx))
509
+
510
+ all_ribbons.push({
511
+ x_values: scaled_distances,
512
+ y_values,
513
+ width_values,
514
+ color: config.color ?? PLOT_COLORS[bs_idx % PLOT_COLORS.length],
515
+ opacity: config.opacity ?? 0.3,
516
+ max_width: config.max_width ?? 6,
517
+ scale: config.scale ?? 1,
518
+ band_idx,
519
+ structure_label,
520
+ segment_key,
521
+ })
529
522
  }
530
523
  }
531
524
 
@@ -642,7 +635,7 @@
642
635
  ]
643
636
  })
644
637
 
645
- let custom_highlight_regions = $derived.by((): FillRegion[] =>
638
+ let custom_highlight_regions = $derived(
646
639
  (highlight_regions ?? [])
647
640
  .filter((region) => Number.isFinite(region.y_min) && Number.isFinite(region.y_max))
648
641
  .map((region) => ({
@@ -47,18 +47,9 @@
47
47
  let shared_frequency_range = $derived(helpers.compute_frequency_range(band_structs, doses))
48
48
 
49
49
  // Extract Fermi level from electronic band structure or DOS data
50
- let fermi_level = $derived.by((): number | undefined => {
51
- // Check band structures for efermi
52
- const bs_source =
53
- `efermi` in (band_structs as object) ? band_structs : Object.values(band_structs)[0]
54
- const bs_efermi = (bs_source as Record<string, unknown>)?.efermi
55
- if (typeof bs_efermi === `number`) return bs_efermi
56
-
57
- // Check DOS for efermi
58
- const dos_source = `efermi` in (doses as object) ? doses : Object.values(doses)[0]
59
- const dos_efermi = (dos_source as Record<string, unknown>)?.efermi
60
- return typeof dos_efermi === `number` ? dos_efermi : undefined
61
- })
50
+ let fermi_level = $derived(
51
+ helpers.extract_efermi(band_structs) ?? helpers.extract_efermi(doses),
52
+ )
62
53
 
63
54
  // Convert fractional k-point coordinates to Cartesian reciprocal space
64
55
  // using the structure's reciprocal lattice (consistent with BZ computation)
@@ -91,13 +82,7 @@
91
82
  let clientWidth = $state(desktop_width)
92
83
  let is_desktop = $derived(clientWidth >= desktop_width)
93
84
  let is_mobile = $derived(clientWidth < tablet_width)
94
- let screen_class = $derived(
95
- clientWidth >= desktop_width
96
- ? `desktop`
97
- : clientWidth >= tablet_width
98
- ? `tablet`
99
- : `phone`,
100
- )
85
+ let screen_class = $derived(is_desktop ? `desktop` : is_mobile ? `phone` : `tablet`)
101
86
 
102
87
  const bands_default_axis = (range = shared_frequency_range): AxisConfig =>
103
88
  helpers.axis_with_range(bands_props.y_axis, range)
@@ -5,6 +5,7 @@
5
5
  import type { Vec2 } from '../math'
6
6
  import ScatterPlot from '../plot/scatter/ScatterPlot.svelte'
7
7
  import type { AxisConfig, DataSeries } from '../plot/core/types'
8
+ import { extent } from 'd3-array'
8
9
  import type { ComponentProps } from 'svelte'
9
10
  import { tooltip as attach_tooltip } from 'svelte-multiselect/attachments'
10
11
  import {
@@ -299,34 +300,35 @@
299
300
  let clamp_to_zero = $derived(
300
301
  is_phonon &&
301
302
  all_freqs.length > 0 &&
302
- Math.min(...all_freqs) < 0 &&
303
+ (extent(all_freqs)[0] ?? 0) < 0 &&
303
304
  negative_fraction(all_freqs) < IMAGINARY_MODE_NOISE_THRESHOLD,
304
305
  )
305
306
 
306
307
  // Check if we have mirrored spin-down data (negative densities)
307
308
  let has_mirrored_spin = $derived(effective_spin_mode === `mirror` && has_spin_polarized)
308
309
 
309
- let x_range = $derived.by((): Vec2 | undefined => {
310
- if (series_data.length === 0) return undefined
311
- const all_x = series_data.flatMap((srs) => srs.x)
312
- const min_x = Math.min(...all_x),
313
- max_x = Math.max(...all_x)
314
- // For horizontal orientation with mirror mode, allow negative values (mirrored densities)
315
- if (is_horizontal && has_mirrored_spin) return [min_x, max_x]
316
- if (is_horizontal || clamp_to_zero) return [0, max_x]
317
- return [min_x, max_x]
318
- })
319
-
320
- let y_range = $derived.by((): Vec2 | undefined => {
321
- if (series_data.length === 0) return undefined
322
- const all_y = series_data.flatMap((srs) => srs.y)
323
- const min_y = Math.min(...all_y),
324
- max_y = Math.max(...all_y)
325
- // For vertical orientation with mirror mode, allow negative values (mirrored densities)
326
- if (!is_horizontal && has_mirrored_spin) return [min_y, max_y]
327
- if (!is_horizontal || clamp_to_zero) return [0, max_y]
328
- return [min_y, max_y]
329
- })
310
+ // Density axis starts at 0 (unless mirror mode needs negative values); frequency axis
311
+ // clamps to 0 only when negative phonon frequencies are numerical noise.
312
+ // Prefer d3 extent over Math.min/max(...arr) large DOS grids can blow the call stack.
313
+ const compute_range = (values: number[], is_density_axis: boolean): Vec2 | undefined => {
314
+ if (values.length === 0) return undefined
315
+ const [min_val, max_val] = extent(values) as [number, number]
316
+ if (is_density_axis && has_mirrored_spin) return [min_val, max_val]
317
+ if (is_density_axis || clamp_to_zero) return [0, max_val]
318
+ return [min_val, max_val]
319
+ }
320
+ let x_range = $derived(
321
+ compute_range(
322
+ series_data.flatMap((srs) => srs.x),
323
+ is_horizontal,
324
+ ),
325
+ )
326
+ let y_range = $derived(
327
+ compute_range(
328
+ series_data.flatMap((srs) => srs.y),
329
+ !is_horizontal,
330
+ ),
331
+ )
330
332
 
331
333
  // Get axis labels based on orientation
332
334
  let x_label = $derived(
@@ -374,9 +376,9 @@
374
376
  let effective_sigma_range = $derived.by((): Vec2 => {
375
377
  if (sigma_range) return sigma_range
376
378
  if (all_freqs.length === 0) return [0, 1]
377
- const freq_range = Math.max(...all_freqs) - Math.min(...all_freqs)
379
+ const [min_freq, max_freq] = extent(all_freqs) as [number, number]
378
380
  // Reasonable sigma range: 0 to ~5% of total range
379
- const max_sigma = Math.max(0.1, freq_range * 0.05)
381
+ const max_sigma = Math.max(0.1, (max_freq - min_freq) * 0.05)
380
382
  return [0, max_sigma]
381
383
  })
382
384