matterviz 0.3.4 → 0.3.5

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 (125) hide show
  1. package/dist/FilePicker.svelte +1 -1
  2. package/dist/app.css +7 -0
  3. package/dist/brillouin/BrillouinZone.svelte +5 -2
  4. package/dist/brillouin/compute.js +8 -4
  5. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +6 -6
  6. package/dist/chempot-diagram/async-compute.svelte.js +5 -4
  7. package/dist/chempot-diagram/chempot-worker.js +2 -2
  8. package/dist/chempot-diagram/compute.js +16 -16
  9. package/dist/composition/FormulaFilter.svelte +3 -3
  10. package/dist/constants.js +2 -8
  11. package/dist/convex-hull/ConvexHull.svelte +2 -2
  12. package/dist/convex-hull/ConvexHull2D.svelte +11 -10
  13. package/dist/convex-hull/ConvexHull3D.svelte +16 -14
  14. package/dist/convex-hull/ConvexHull4D.svelte +26 -14
  15. package/dist/convex-hull/ConvexHullControls.svelte +1 -1
  16. package/dist/convex-hull/ConvexHullInfoPane.svelte +68 -61
  17. package/dist/convex-hull/ConvexHullStats.svelte +23 -6
  18. package/dist/convex-hull/GasPressureControls.svelte +3 -3
  19. package/dist/convex-hull/TemperatureSlider.svelte +1 -1
  20. package/dist/convex-hull/barycentric-coords.js +2 -2
  21. package/dist/convex-hull/helpers.js +45 -27
  22. package/dist/convex-hull/thermodynamics.js +2 -2
  23. package/dist/element/BohrAtom.svelte +25 -27
  24. package/dist/element/BohrAtom.svelte.d.ts +2 -2
  25. package/dist/element/data.d.ts +2 -3
  26. package/dist/fermi-surface/FermiSurface.svelte +5 -2
  27. package/dist/fermi-surface/compute.js +3 -3
  28. package/dist/fermi-surface/parse.js +2 -2
  29. package/dist/fermi-surface/symmetry.js +1 -1
  30. package/dist/heatmap-matrix/HeatmapMatrix.svelte +8 -8
  31. package/dist/icons.d.ts +6 -6
  32. package/dist/icons.js +6 -6
  33. package/dist/io/decompress.js +12 -7
  34. package/dist/io/export.js +20 -16
  35. package/dist/io/is-binary.js +19 -4
  36. package/dist/isosurface/parse.js +8 -8
  37. package/dist/isosurface/types.js +9 -9
  38. package/dist/layout/InfoTag.svelte +1 -1
  39. package/dist/layout/json-tree/JsonNode.svelte +1 -0
  40. package/dist/layout/json-tree/utils.js +2 -1
  41. package/dist/marching-cubes.js +1 -1
  42. package/dist/math.js +1 -1
  43. package/dist/overlays/CopyButton.svelte +45 -0
  44. package/dist/overlays/CopyButton.svelte.d.ts +8 -0
  45. package/dist/overlays/InfoPaneCards.svelte +149 -0
  46. package/dist/overlays/InfoPaneCards.svelte.d.ts +22 -0
  47. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +33 -35
  48. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +2 -2
  49. package/dist/phase-diagram/PhaseDiagramControls.svelte +27 -29
  50. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +2 -2
  51. package/dist/phase-diagram/parse.js +3 -3
  52. package/dist/phase-diagram/svg-to-diagram.js +10 -12
  53. package/dist/plot/BarPlot.svelte +24 -15
  54. package/dist/plot/BarPlot.svelte.d.ts +3 -2
  55. package/dist/plot/FillArea.svelte +2 -3
  56. package/dist/plot/FillArea.svelte.d.ts +3 -2
  57. package/dist/plot/Histogram.svelte +37 -19
  58. package/dist/plot/Line.svelte +2 -3
  59. package/dist/plot/Line.svelte.d.ts +2 -2
  60. package/dist/plot/PlotLegend.svelte +79 -8
  61. package/dist/plot/PlotLegend.svelte.d.ts +4 -0
  62. package/dist/plot/PortalSelect.svelte +5 -5
  63. package/dist/plot/ScatterPlot.svelte +47 -33
  64. package/dist/plot/ScatterPlot.svelte.d.ts +5 -4
  65. package/dist/plot/ScatterPlot3D.svelte +6 -3
  66. package/dist/plot/ScatterPoint.svelte +10 -4
  67. package/dist/plot/ScatterPoint.svelte.d.ts +4 -2
  68. package/dist/plot/SpacegroupBarPlot.svelte +5 -4
  69. package/dist/plot/data-cleaning.js +9 -9
  70. package/dist/plot/index.d.ts +0 -6
  71. package/dist/plot/scales.d.ts +3 -3
  72. package/dist/plot/scales.js +29 -29
  73. package/dist/plot/types.d.ts +5 -9
  74. package/dist/rdf/calc-rdf.js +1 -1
  75. package/dist/sanitize.js +22 -15
  76. package/dist/settings.d.ts +2 -0
  77. package/dist/settings.js +12 -3
  78. package/dist/spectral/Bands.svelte +6 -6
  79. package/dist/spectral/BandsAndDos.svelte +4 -4
  80. package/dist/spectral/BrillouinBandsDos.svelte +3 -3
  81. package/dist/spectral/Dos.svelte +2 -2
  82. package/dist/spectral/helpers.js +1 -1
  83. package/dist/structure/AtomLegend.svelte +4 -4
  84. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  85. package/dist/structure/Cylinder.svelte +7 -7
  86. package/dist/structure/Structure.svelte +169 -27
  87. package/dist/structure/Structure.svelte.d.ts +6 -2
  88. package/dist/structure/StructureControls.svelte +130 -16
  89. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  90. package/dist/structure/StructureInfoPane.svelte +519 -218
  91. package/dist/structure/StructureInfoPane.svelte.d.ts +2 -1
  92. package/dist/structure/StructureScene.svelte +399 -68
  93. package/dist/structure/StructureScene.svelte.d.ts +8 -4
  94. package/dist/structure/atom-properties.js +3 -1
  95. package/dist/structure/bond-order-perception.d.ts +13 -0
  96. package/dist/structure/bond-order-perception.js +367 -0
  97. package/dist/structure/bonding.d.ts +10 -1
  98. package/dist/structure/bonding.js +232 -11
  99. package/dist/structure/export.js +6 -4
  100. package/dist/structure/index.d.ts +19 -4
  101. package/dist/structure/index.js +3 -0
  102. package/dist/structure/label-placement.d.ts +14 -0
  103. package/dist/structure/label-placement.js +72 -0
  104. package/dist/structure/parse.d.ts +2 -1
  105. package/dist/structure/parse.js +25 -36
  106. package/dist/structure/supercell.js +35 -2
  107. package/dist/symmetry/SymmetryStats.svelte +1 -1
  108. package/dist/symmetry/cell-transform.js +15 -1
  109. package/dist/symmetry/index.js +3 -3
  110. package/dist/table/HeatmapTable.svelte +3 -3
  111. package/dist/table/ToggleMenu.svelte +1 -1
  112. package/dist/trajectory/Trajectory.svelte +2 -2
  113. package/dist/trajectory/TrajectoryInfoPane.svelte +14 -88
  114. package/dist/trajectory/extract.js +4 -4
  115. package/dist/trajectory/frame-reader.js +2 -2
  116. package/dist/trajectory/parse/ase.js +2 -6
  117. package/dist/trajectory/parse/hdf5.js +1 -3
  118. package/dist/trajectory/plotting.js +1 -1
  119. package/dist/utils.js +1 -1
  120. package/dist/xrd/calc-xrd.js +1 -1
  121. package/package.json +22 -37
  122. package/dist/structure/ferrox-wasm-types.d.ts +0 -46
  123. package/dist/structure/ferrox-wasm-types.js +0 -18
  124. package/dist/structure/ferrox-wasm.d.ts +0 -94
  125. package/dist/structure/ferrox-wasm.js +0 -249
@@ -1,10 +1,24 @@
1
1
  <script lang="ts">
2
+ import InfoPaneCards from '../overlays/InfoPaneCards.svelte'
2
3
  import DraggablePane from '../overlays/DraggablePane.svelte'
4
+ import { format_num } from '../labels'
3
5
  import type { ComponentProps } from 'svelte'
4
6
  import type { HTMLAttributes } from 'svelte/elements'
5
7
  import ConvexHullStats from './ConvexHullStats.svelte'
6
8
  import type { ConvexHullEntry, PhaseStats } from './types'
7
9
 
10
+ const usage_tips = [
11
+ { label: `Single click`, value: `Select point`, key: `tip-click` },
12
+ { label: `Double click`, value: `Copy info`, key: `tip-double-click` },
13
+ { label: `Drag`, value: `Rotate view`, key: `tip-drag` },
14
+ { label: `Scroll`, value: `Zoom in/out`, key: `tip-scroll` },
15
+ { label: `Key r`, value: `Reset camera`, key: `tip-reset` },
16
+ { label: `Key b`, value: `Toggle color mode`, key: `tip-color-mode` },
17
+ { label: `Key s`, value: `Toggle stable points`, key: `tip-stable` },
18
+ { label: `Key u`, value: `Toggle unstable points`, key: `tip-unstable` },
19
+ { label: `Key l`, value: `Toggle labels`, key: `tip-labels` },
20
+ ]
21
+
8
22
  let {
9
23
  phase_stats,
10
24
  stable_entries,
@@ -27,6 +41,51 @@
27
41
  toggle_props?: ComponentProps<typeof DraggablePane>[`toggle_props`]
28
42
  pane_props?: ComponentProps<typeof DraggablePane>[`pane_props`]
29
43
  } = $props()
44
+
45
+ const count_visible = (entries: ConvexHullEntry[]): number =>
46
+ entries.reduce((count, entry) => count + Number(entry.visible), 0)
47
+
48
+ let settings_rows = $derived([
49
+ {
50
+ label: `Visible stable`,
51
+ value: `${count_visible(stable_entries)} / ${stable_entries.length}`,
52
+ key: `hull-visible-stable`,
53
+ },
54
+ {
55
+ label: `Visible unstable`,
56
+ value: `${count_visible(unstable_entries)} / ${unstable_entries.length}`,
57
+ key: `hull-visible-unstable`,
58
+ },
59
+ {
60
+ label: `Points threshold`,
61
+ value: `${format_num(max_hull_dist_show_phases, `.3~f`)} eV/atom`,
62
+ key: `hull-show-threshold`,
63
+ },
64
+ {
65
+ label: `Label threshold`,
66
+ value: `${format_num(max_hull_dist_show_labels, `.3~f`)} eV/atom`,
67
+ key: `hull-label-threshold`,
68
+ },
69
+ {
70
+ label: `Entry limit for labels`,
71
+ value: `${label_threshold} entries`,
72
+ key: `hull-entry-limit-labels`,
73
+ },
74
+ ])
75
+
76
+ let info_cards = $derived([
77
+ { title: `Visualization Settings`, rows: settings_rows },
78
+ { title: `Usage Tips`, rows: usage_tips },
79
+ ])
80
+
81
+ const info_card_style = [
82
+ `--info-card-padding: 3pt`,
83
+ `--info-card-bg: var(--pane-bg, white)`,
84
+ `--info-card-heading-gap: 6px`,
85
+ `--info-row-padding: 1pt`,
86
+ `--row-label-max: 1fr`,
87
+ `--info-row-label-color: var(--text-color-muted, #666)`,
88
+ ].join(`; `)
30
89
  </script>
31
90
 
32
91
  <DraggablePane
@@ -49,67 +108,15 @@
49
108
  {phase_stats}
50
109
  {stable_entries}
51
110
  {unstable_entries}
52
- style="padding: 3pt; background: var(--pane-bg)"
111
+ style="padding: 3pt; background: var(--pane-bg); --hull-stats-table-height: 30rem"
53
112
  />
54
113
 
55
- <section class="vis-settings">
56
- <h5 id="visualization-settings">Visualization Settings</h5>
57
- <div class="setting-item" data-testid="hull-visible-stable">
58
- <span>Visible stable:</span>
59
- <span>{stable_entries.filter((entry) => entry.visible).length} / {
60
- stable_entries.length
61
- }</span>
62
- </div>
63
- <div class="setting-item" data-testid="hull-visible-unstable">
64
- <span>Visible unstable:</span>
65
- <span>{unstable_entries.filter((entry) => entry.visible).length} / {
66
- unstable_entries.length
67
- }</span>
68
- </div>
69
- <div class="setting-item" data-testid="hull-show-threshold">
70
- <span>Points threshold:</span>
71
- <span>{max_hull_dist_show_phases.toFixed(3)} eV/atom</span>
72
- </div>
73
- <div class="setting-item" data-testid="hull-label-threshold">
74
- <span>Label threshold:</span>
75
- <span>{max_hull_dist_show_labels.toFixed(3)} eV/atom</span>
76
- </div>
77
- <div class="setting-item" data-testid="hull-entry-limit-labels">
78
- <span>Entry limit for labels:</span>
79
- <span>{label_threshold} entries</span>
80
- </div>
81
- </section>
82
-
83
- <section class="usage-tips">
84
- <h5 id="usage-tips">Usage Tips</h5>
85
- <div class="tips-item"><span>Single click:</span><span>Select point</span></div>
86
- <div class="tips-item"><span>Double click:</span><span>Copy info</span></div>
87
- <div class="tips-item"><span>Drag:</span><span>Rotate view</span></div>
88
- <div class="tips-item"><span>Scroll:</span><span>Zoom in/out</span></div>
89
- <div class="tips-item"><span>Key 'r':</span><span>Reset camera</span></div>
90
- <div class="tips-item"><span>Key 'b':</span><span>Toggle color mode</span></div>
91
- <div class="tips-item"><span>Key 's':</span><span>Toggle stable points</span></div>
92
- <div class="tips-item"><span>Key 'u':</span><span>Toggle unstable points</span></div>
93
- <div class="tips-item"><span>Key 'l':</span><span>Toggle labels</span></div>
94
- </section>
114
+ <InfoPaneCards
115
+ cards={info_cards}
116
+ filter_placeholder="Filter hull info"
117
+ empty_label="hull info"
118
+ heading_level={5}
119
+ row_label_min="7em"
120
+ style={info_card_style}
121
+ />
95
122
  </DraggablePane>
96
-
97
- <style>
98
- .vis-settings, .usage-tips {
99
- padding: 3pt;
100
- background: var(--pane-bg, white);
101
- }
102
- .vis-settings h5, .usage-tips h5 {
103
- margin: 0 0 6px 0;
104
- }
105
- .setting-item, .tips-item {
106
- display: flex;
107
- justify-content: space-between;
108
- gap: 6pt;
109
- padding: 1pt;
110
- line-height: 1.5;
111
- }
112
- .setting-item span:first-child, .tips-item span:first-child {
113
- color: var(--text-color-muted, #666);
114
- }
115
- </style>
@@ -49,6 +49,16 @@
49
49
  // Formula filter: when set, table shows only entries with this reduced formula
50
50
  let formula_filter = $state(``)
51
51
  let show_export_dropdown = $state(false)
52
+ const table_scroll_height =
53
+ `var(--hull-stats-table-height, calc(var(--hull-stats-table-row-height, 2.35rem) * 10 + var(--hull-stats-table-header-height, 3.5rem)))`
54
+ const table_scroll_style = $derived(layout === `side-by-side`
55
+ ? `flex: 1 1 0; height: ${table_scroll_height}; min-height: ${table_scroll_height}; max-width: 100%; overflow: auto`
56
+ : `height: ${table_scroll_height}; min-height: ${table_scroll_height}; max-height: var(--hull-stats-max-height, 70vh); max-width: 100%; overflow: auto`
57
+ )
58
+ const table_root_style = $derived(layout === `side-by-side`
59
+ ? `flex: 1 1 0; min-height: ${table_scroll_height}; margin-inline: 0`
60
+ : `min-width: 0; margin-inline: 0`
61
+ )
52
62
 
53
63
  async function copy_to_clipboard(label: string, value: string, key: string) {
54
64
  try {
@@ -638,13 +648,9 @@
638
648
  data={table_data}
639
649
  columns={table_columns}
640
650
  initial_sort={{ column: `E<sub>hull</sub>`, direction: `asc` }}
641
- scroll_style={layout === `side-by-side`
642
- ? `flex: 1 1 0; max-width: 100%; overflow: auto`
643
- : `max-height: var(--hull-stats-max-height, 500px)`}
651
+ scroll_style={table_scroll_style}
644
652
  style="width: 100%"
645
- root_style={layout === `side-by-side`
646
- ? `flex: 1 1 0; min-height: 0; margin-inline: 0`
647
- : undefined}
653
+ root_style={table_root_style}
648
654
  onrowclick={on_entry_click ? handle_row_click : undefined}
649
655
  export_data={false}
650
656
  />
@@ -681,6 +687,9 @@
681
687
  .convex-hull-stats {
682
688
  background: var(--hull-stats-bg, var(--hull-bg));
683
689
  border-radius: var(--hull-border-radius, var(--border-radius, 3pt));
690
+ box-sizing: border-box;
691
+ max-width: 100%;
692
+ overflow: hidden;
684
693
  padding: var(--hull-stats-padding, 1em);
685
694
  }
686
695
  .convex-hull-stats.side-by-side {
@@ -700,6 +709,7 @@
700
709
  .table-pane {
701
710
  flex: 1 1 0;
702
711
  max-width: 100%;
712
+ min-height: var(--hull-stats-table-height, calc(var(--hull-stats-table-row-height, 2.35rem) * 10 + var(--hull-stats-table-header-height, 3.5rem)));
703
713
  min-width: 0;
704
714
  overflow: auto;
705
715
  display: flex;
@@ -747,9 +757,15 @@
747
757
  .view-toggle {
748
758
  display: flex;
749
759
  margin-bottom: 8pt;
760
+ max-width: 100%;
761
+ min-width: 0;
750
762
  }
751
763
  .view-toggle button {
764
+ display: inline-flex;
765
+ align-items: center;
766
+ justify-content: center;
752
767
  flex: 1;
768
+ min-width: 0;
753
769
  padding: 2pt 8pt;
754
770
  border: 1px solid
755
771
  var(--hull-stats-border-color, color-mix(in srgb, currentColor 20%, transparent));
@@ -757,6 +773,7 @@
757
773
  color: inherit;
758
774
  cursor: pointer;
759
775
  font-size: 0.85em;
776
+ text-align: center;
760
777
  }
761
778
  .view-toggle button:first-child {
762
779
  border-radius: 4pt 0 0 4pt;
@@ -5,7 +5,7 @@
5
5
  GasThermodynamicsConfig,
6
6
  } from './types'
7
7
  import { sanitize_html } from '../sanitize'
8
- import { tooltip } from 'svelte-multiselect'
8
+ import { tooltip } from 'svelte-multiselect/attachments'
9
9
  import type { HTMLAttributes } from 'svelte/elements'
10
10
  import {
11
11
  compute_gas_chemical_potential,
@@ -94,8 +94,8 @@
94
94
  const P = slider_to_pressure(+(event.currentTarget as HTMLInputElement).value)
95
95
  pressures = { ...pressures, [gas]: P }
96
96
  // Clear only this gas's preview (don't reset other sliders being dragged simultaneously)
97
- const { [gas]: _, ...rest } = preview_pressures
98
- preview_pressures = rest
97
+ const { [gas]: _removed_preview, ...remaining_previews } = preview_pressures
98
+ preview_pressures = remaining_previews
99
99
  }
100
100
 
101
101
  function set_pressure_direct(gas: GasSpecies, value: number): void {
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { tooltip } from 'svelte-multiselect'
2
+ import { tooltip } from 'svelte-multiselect/attachments'
3
3
  import type { HTMLAttributes } from 'svelte/elements'
4
4
 
5
5
  let {
@@ -144,7 +144,7 @@ export const TETRAHEDRON_VERTICES = [
144
144
  ];
145
145
  export function composition_to_barycentric_4d(composition, elements) {
146
146
  if (elements.length !== 4) {
147
- throw new Error(`Quaternary barycentric coordinates require exactly ${4} elements`);
147
+ throw new Error(`Quaternary barycentric coordinates require exactly 4 elements`);
148
148
  }
149
149
  const amounts = elements.map((el) => composition[el] || 0);
150
150
  const total = amounts.reduce((sum, amount) => sum + amount, 0);
@@ -168,7 +168,7 @@ export function barycentric_to_tetrahedral(barycentric) {
168
168
  }
169
169
  export function compute_4d_coords(entries, elements) {
170
170
  if (elements.length !== 4) {
171
- throw new Error(`Quaternary convex hull requires exactly ${4} elements`);
171
+ throw new Error(`Quaternary convex hull requires exactly 4 elements`);
172
172
  }
173
173
  // Use Set for O(1) lookups instead of O(n) includes
174
174
  const element_set = new Set(elements);
@@ -26,13 +26,17 @@ export const is_unary_entry = (entry) => get_arity(entry) === 1;
26
26
  export function get_energy_color_scale(color_mode, color_scale, plot_entries) {
27
27
  if (color_mode !== `energy` || plot_entries.length === 0)
28
28
  return null;
29
- const hull_distances = plot_entries
30
- .map((entry) => entry.e_above_hull)
31
- .filter((val) => typeof val === `number`);
32
- if (hull_distances.length === 0)
29
+ let lo = Number.POSITIVE_INFINITY;
30
+ let hi_raw = 0.1;
31
+ for (const entry of plot_entries) {
32
+ const val = entry.e_above_hull;
33
+ if (typeof val !== `number` || !Number.isFinite(val))
34
+ continue;
35
+ lo = Math.min(lo, val);
36
+ hi_raw = Math.max(hi_raw, val);
37
+ }
38
+ if (!Number.isFinite(lo))
33
39
  return null;
34
- const lo = Math.min(...hull_distances);
35
- const hi_raw = Math.max(...hull_distances, 0.1);
36
40
  const hi = Math.max(hi_raw, lo + 1e-6);
37
41
  const interpolator = get_d3_interpolator(color_scale);
38
42
  return scaleSequential(interpolator).domain([lo, hi]);
@@ -70,10 +74,14 @@ export async function parse_hull_entries_from_drop(event) {
70
74
  export function calc_max_hull_dist_in_data(processed_entries) {
71
75
  if (processed_entries.length === 0)
72
76
  return 0.5;
73
- const hull_distances = processed_entries
74
- .map((entry) => entry.e_above_hull)
75
- .filter((val) => typeof val === `number` && Number.isFinite(val));
76
- const max_val = (hull_distances.length ? Math.max(...hull_distances) : 0) + 0.001;
77
+ let max_hull_dist = 0;
78
+ for (const entry of processed_entries) {
79
+ const val = entry.e_above_hull;
80
+ if (typeof val === `number` && Number.isFinite(val)) {
81
+ max_hull_dist = Math.max(max_hull_dist, val);
82
+ }
83
+ }
84
+ const max_val = max_hull_dist + 0.001;
77
85
  return Math.max(0.1, max_val);
78
86
  }
79
87
  // Smart threshold for showing unstable entries based on entry count.
@@ -157,8 +165,18 @@ energy_source_mode) {
157
165
  const has_precomputed_e_form = entries.length > 0 && entries.every((entry) => typeof entry.e_form_per_atom === `number`);
158
166
  const has_precomputed_hull = entries.length > 0 && entries.every((entry) => typeof entry.e_above_hull === `number`);
159
167
  const unary_refs = find_lowest_energy_unary_refs_fn(entries);
160
- const elements_in_entries = Array.from(new Set(entries.flatMap((entry) => Object.keys(entry.composition))));
161
- const can_compute_e_form = elements_in_entries.every((el) => Boolean(unary_refs[el]));
168
+ const elements_in_entries = new Set();
169
+ for (const entry of entries) {
170
+ for (const el of Object.keys(entry.composition))
171
+ elements_in_entries.add(el);
172
+ }
173
+ let can_compute_e_form = true;
174
+ for (const el of elements_in_entries) {
175
+ if (!unary_refs[el]) {
176
+ can_compute_e_form = false;
177
+ break;
178
+ }
179
+ }
162
180
  const can_compute_hull = can_compute_e_form;
163
181
  // Resolve mode to avoid inconsistent states:
164
182
  // - If full precomputed available, honor user toggle
@@ -467,30 +485,30 @@ export function get_canvas_text_color(dark_mode, element) {
467
485
  }
468
486
  // Create a Path2D for a marker symbol. Uses d3-shape for consistent rendering with ScatterPlot.
469
487
  export function create_marker_path(size, marker = `circle`) {
488
+ const safe_size = Number.isFinite(size) ? size : 0;
489
+ const rounded_size = Math.max(0, Number(safe_size.toFixed(3)));
470
490
  // Capitalize first letter to get D3 symbol name (e.g. 'circle' -> 'Circle')
471
491
  const d3_name = marker.charAt(0).toUpperCase() + marker.slice(1);
472
492
  const symbol_type = symbol_map[d3_name];
473
- if (!symbol_type) {
474
- const path = new Path2D();
475
- path.arc(0, 0, size, 0, 2 * Math.PI);
476
- return path;
477
- }
478
- const symbol_area = Math.PI * size * size;
479
- const path_data = symbol().type(symbol_type).size(symbol_area)();
493
+ const symbol_area = Math.PI * rounded_size * rounded_size;
494
+ const path_data = symbol_type ? symbol().type(symbol_type).size(symbol_area)() : null;
495
+ const path = new Path2D(path_data ?? undefined);
480
496
  if (!path_data) {
481
- const path = new Path2D();
482
- path.arc(0, 0, size, 0, 2 * Math.PI);
483
- return path;
497
+ path.arc(0, 0, rounded_size, 0, 2 * Math.PI);
484
498
  }
485
- return new Path2D(path_data);
499
+ return path;
486
500
  }
487
501
  // Analyze entries for temperature-dependent free energy data.
488
502
  // Returns available temperatures (union of all T values across entries) if any entries have temp data.
489
503
  export function analyze_temperature_data(entries) {
490
- const valid_temps = entries
491
- .filter(entry_has_temp_data)
492
- .flatMap((entry) => entry.temperatures ?? []);
493
- const available_temperatures = [...new Set(valid_temps)].sort((a, b) => a - b);
504
+ const unique_temperatures = new Set();
505
+ for (const entry of entries) {
506
+ if (!entry_has_temp_data(entry))
507
+ continue;
508
+ for (const temperature of entry.temperatures ?? [])
509
+ unique_temperatures.add(temperature);
510
+ }
511
+ const available_temperatures = [...unique_temperatures].sort((a, b) => a - b);
494
512
  return {
495
513
  has_temp_data: available_temperatures.length > 0,
496
514
  available_temperatures,
@@ -311,7 +311,7 @@ export function calculate_e_above_hull(input, reference_entries) {
311
311
  }
312
312
  // Ensure corner points (pure elements default to e_form = 0)
313
313
  for (let el_idx = 0; el_idx < arity; el_idx++) {
314
- const corner = new Array(arity + 1).fill(0);
314
+ const corner = Array(arity + 1).fill(0);
315
315
  corner[el_idx] = 1; // ith barycentric coord = 1
316
316
  if (!ref_points.some((pt) => norm_nd(subtract_nd(pt, corner)) < EPS)) {
317
317
  ref_points.push(corner);
@@ -1511,7 +1511,7 @@ function solve_linear_system(matrix_a, vec_b) {
1511
1511
  }
1512
1512
  }
1513
1513
  // Back substitution
1514
- const result = new Array(n).fill(0);
1514
+ const result = Array(n).fill(0);
1515
1515
  for (let row = n - 1; row >= 0; row--) {
1516
1516
  let sum = aug[row][n];
1517
1517
  for (let col = row + 1; col < n; col++) {
@@ -1,7 +1,23 @@
1
1
  <script lang="ts">
2
2
  import type { SVGAttributes } from 'svelte/elements'
3
3
 
4
- type Props = SVGAttributes<SVGSVGElement> & {
4
+ let {
5
+ symbol = ``,
6
+ name = ``,
7
+ shells,
8
+ adapt_size = true,
9
+ shell_width = 20,
10
+ size = adapt_size ? (shells.length + 1) * 2 * shell_width + 30 : 270,
11
+ base_fill = `var(--text-color)`,
12
+ orbital_period = 3,
13
+ nucleus_props = {},
14
+ shell_props = {},
15
+ electron_props = {},
16
+ highlight_shell = null,
17
+ number_electrons = false,
18
+ electron_label_props = {},
19
+ ...rest
20
+ }: SVGAttributes<SVGSVGElement> & {
5
21
  // https://svelte.dev/repl/17d71b590f554b5a9eba6e04023dd41c
6
22
  symbol?: string // usually H, He, etc. but can be anything
7
23
  name?: string // usually Hydrogen, Helium, etc. but can be anything
@@ -23,41 +39,23 @@
23
39
  | `sequential`
24
40
  | ((idx: number) => string)
25
41
  electron_label_props?: Record<string, string | number>
26
- }
27
-
28
- let {
29
- symbol = ``,
30
- name = ``,
31
- shells,
32
- adapt_size = true,
33
- shell_width = 20,
34
- size = adapt_size ? (shells.length + 1) * 2 * shell_width + 30 : 270,
35
- base_fill = `var(--text-color)`,
36
- orbital_period = 3,
37
- nucleus_props = {},
38
- shell_props = {},
39
- electron_props = {},
40
- highlight_shell = null,
41
- number_electrons = false,
42
- electron_label_props = {},
43
- ...rest
44
- }: Props = $props()
42
+ } = $props()
45
43
 
46
44
  // Bohr atom electron orbital period is given by
47
45
  // T = (n^3 h^3) / (4pi^2 m K e^4 Z^2) = 1.52 * 10^-16 * n^3 / Z^2 s
48
46
  // with n the shell number, Z the atomic number, m the mass of the electron
49
- let _nucleus_props = $derived({
47
+ let nucleus_svg_props = $derived({
50
48
  r: 20,
51
49
  fill: base_fill,
52
50
  'fill-opacity': `0.3`,
53
51
  ...nucleus_props,
54
52
  })
55
- let _shell_props = $derived({
53
+ let shell_svg_props = $derived({
56
54
  stroke: base_fill,
57
55
  fill: `none`,
58
56
  ...shell_props,
59
57
  })
60
- let _electron_props = $derived({
58
+ let electron_svg_props = $derived({
61
59
  r: 3,
62
60
  stroke: base_fill,
63
61
  fill: `blue`,
@@ -75,7 +73,7 @@
75
73
  {...rest}
76
74
  >
77
75
  <!-- nucleus -->
78
- <circle class="nucleus" {..._nucleus_props}>
76
+ <circle class="nucleus" {...nucleus_svg_props}>
79
77
  {#if name}
80
78
  <title>{name}</title>
81
79
  {/if}
@@ -87,12 +85,12 @@
87
85
  <!-- electron orbitals -->
88
86
  {#each shells as electrons, shell_idx (`${shell_idx}-${electrons}`)}
89
87
  {@const n = shell_idx + 1}
90
- {@const shell_radius = _nucleus_props.r + n * shell_width}
88
+ {@const shell_radius = nucleus_svg_props.r + n * shell_width}
91
89
  {@const active = n === highlight_shell}
92
90
  <g class="shell" style:animation-duration="{orbital_period * n ** 1.5}s">
93
91
  <circle
94
92
  r={shell_radius}
95
- {..._shell_props}
93
+ {...shell_svg_props}
96
94
  style:stroke-width={active ? 2 : 1}
97
95
  style:stroke={active ? `yellow` : base_fill}
98
96
  />
@@ -101,7 +99,7 @@
101
99
  {#each Array(electrons) as _, elec_idx (elec_idx)}
102
100
  {@const elec_x = Math.cos((2 * Math.PI * elec_idx) / electrons) * shell_radius}
103
101
  {@const elec_y = Math.sin((2 * Math.PI * elec_idx) / electrons) * shell_radius}
104
- <circle class="electron" cx={elec_x} cy={elec_y} {..._electron_props}>
102
+ <circle class="electron" cx={elec_x} cy={elec_y} {...electron_svg_props}>
105
103
  <title>Electron {elec_idx + 1}</title>
106
104
  </circle>
107
105
  {#if number_electrons}
@@ -1,5 +1,5 @@
1
1
  import type { SVGAttributes } from 'svelte/elements';
2
- type Props = SVGAttributes<SVGSVGElement> & {
2
+ type $$ComponentProps = SVGAttributes<SVGSVGElement> & {
3
3
  symbol?: string;
4
4
  name?: string;
5
5
  shells: number[];
@@ -15,6 +15,6 @@ type Props = SVGAttributes<SVGSVGElement> & {
15
15
  number_electrons?: boolean | `hierarchical` | `sequential` | ((idx: number) => string);
16
16
  electron_label_props?: Record<string, string | number>;
17
17
  };
18
- declare const BohrAtom: import("svelte").Component<Props, {}, "">;
18
+ declare const BohrAtom: import("svelte").Component<$$ComponentProps, {}, "">;
19
19
  type BohrAtom = ReturnType<typeof BohrAtom>;
20
20
  export default BohrAtom;
@@ -1,3 +1,2 @@
1
- import type { ChemicalElement } from './types';
2
- declare const _default: ChemicalElement[];
3
- export default _default;
1
+ import data from './data.json.gz';
2
+ export default data;
@@ -492,7 +492,7 @@
492
492
  offset={{ x: 12, y: -12 }}
493
493
  bg_color={hover_data.surface_color}
494
494
  fixed
495
- style="z-index: 100000001; backdrop-filter: blur(4px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3)"
495
+ style="z-index: var(--z-index-overlay-nav, 100000001); backdrop-filter: blur(4px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3)"
496
496
  >
497
497
  <FermiSurfaceTooltip {hover_data} tooltip={tooltip_config} />
498
498
  </PlotTooltip>
@@ -555,7 +555,10 @@
555
555
  top: var(--fermi-buttons-top, var(--ctrl-btn-top, 1ex));
556
556
  right: var(--fermi-buttons-right, var(--ctrl-btn-right, 1ex));
557
557
  gap: clamp(6pt, 1cqmin, 9pt);
558
- z-index: var(--fermi-buttons-z-index, 100000000);
558
+ z-index: var(
559
+ --fermi-buttons-z-index,
560
+ var(--z-index-overlay-controls, 100000000)
561
+ );
559
562
  opacity: 0;
560
563
  pointer-events: none;
561
564
  transition: opacity 0.2s ease;
@@ -71,10 +71,10 @@ function upsample_grid(grid, factor) {
71
71
  for (let iz = 0; iz < new_nz; iz++)
72
72
  fz_arr[iz] = (iz / new_nz) * nz;
73
73
  // Preallocate output grid
74
- const new_grid = new Array(new_nx);
74
+ const new_grid = Array(new_nx);
75
75
  for (let ix = 0; ix < new_nx; ix++) {
76
76
  const fx = fx_arr[ix];
77
- const iy_arr = new Array(new_ny);
77
+ const iy_arr = Array(new_ny);
78
78
  for (let iy = 0; iy < new_ny; iy++) {
79
79
  const fy = fy_arr[iy];
80
80
  const iz_arr = new Float64Array(new_nz);
@@ -492,7 +492,7 @@ function slice_surface_with_plane(surface, plane_normal, plane_distance, in_plan
492
492
  const last = contour_points[contour_points.length - 1];
493
493
  const is_closed = math.euclidean_dist(first, last) < CLOSED_CONTOUR_TOLERANCE;
494
494
  // Project to 2D (inlined dot product for speed)
495
- const points_2d = new Array(contour_points.length);
495
+ const points_2d = Array(contour_points.length);
496
496
  for (let idx = 0; idx < contour_points.length; idx++) {
497
497
  const point = contour_points[idx];
498
498
  points_2d[idx] = [
@@ -97,10 +97,10 @@ function parse_bxsf(content) {
97
97
  throw new Error(`Band ${band_idx}: expected ${total_points} values, got ${energy_values.length}`);
98
98
  }
99
99
  // Reshape into 3D grid [kx][ky][kz] (preallocated for speed)
100
- const band_grid = new Array(nx);
100
+ const band_grid = Array(nx);
101
101
  let val_idx = 0;
102
102
  for (let ix = 0; ix < nx; ix++) {
103
- const iy_arr = new Array(ny);
103
+ const iy_arr = Array(ny);
104
104
  for (let iy = 0; iy < ny; iy++) {
105
105
  const iz_arr = energy_values.slice(val_idx, val_idx + nz);
106
106
  val_idx += nz;
@@ -29,7 +29,7 @@ function generate_oh_symmetry_matrices() {
29
29
  for (const perm of AXIS_PERMUTATIONS) {
30
30
  for (const sign of SIGN_COMBINATIONS) {
31
31
  // Column-major 4x4 matrix: element at (row, col) is at index col*4 + row
32
- const mat = new Array(16).fill(0);
32
+ const mat = Array(16).fill(0);
33
33
  mat[15] = 1; // w component
34
34
  // Build 3x3 rotation part: M[row][col] = sign[row] if perm[row] === col
35
35
  for (let col = 0; col < 3; col++) {
@@ -286,8 +286,8 @@
286
286
  }
287
287
  }
288
288
 
289
- const col_has_data = new Array(x_items.length).fill(false)
290
- const row_has_data = new Array(y_items.length).fill(false)
289
+ const col_has_data = Array(x_items.length).fill(false)
290
+ const row_has_data = Array(y_items.length).fill(false)
291
291
  for (let y_idx = 0; y_idx < y_items.length; y_idx++) {
292
292
  for (let x_idx = 0; x_idx < x_items.length; x_idx++) {
293
293
  if (get_value(x_idx, y_idx) !== null) {
@@ -437,7 +437,7 @@
437
437
  let n_x = $derived(x_items.length)
438
438
  let bg_flat = $derived.by(() => {
439
439
  const n_y = y_items.length
440
- const colors = new Array<string | null>(n_x * n_y)
440
+ const colors: (string | null)[] = Array(n_x * n_y)
441
441
  for (let y_idx = 0; y_idx < n_y; y_idx++) {
442
442
  const row_offset = y_idx * n_x
443
443
  for (let x_idx = 0; x_idx < n_x; x_idx++) {
@@ -1010,11 +1010,11 @@
1010
1010
  grid_offset_top = first_rendered_cell.offsetTop - vis_row * tile_stride_px
1011
1011
  }
1012
1012
 
1013
- function compute_summary(values: number[]): number | null {
1014
- if (!values.length) return null
1015
- if (summary_fn) return summary_fn(values)
1016
- const total = values.reduce((sum, value) => sum + value, 0)
1017
- return total / values.length
1013
+ function compute_summary(summary_values: number[]): number | null {
1014
+ if (!summary_values.length) return null
1015
+ if (summary_fn) return summary_fn(summary_values)
1016
+ const total = summary_values.reduce((sum, value) => sum + value, 0)
1017
+ return total / summary_values.length
1018
1018
  }
1019
1019
 
1020
1020
  function summarize_axis_values(