matterviz 0.3.3 → 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 (126) 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 +6 -5
  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/element/data.js +1 -1
  27. package/dist/fermi-surface/FermiSurface.svelte +5 -2
  28. package/dist/fermi-surface/compute.js +3 -3
  29. package/dist/fermi-surface/parse.js +2 -2
  30. package/dist/fermi-surface/symmetry.js +1 -1
  31. package/dist/heatmap-matrix/HeatmapMatrix.svelte +8 -8
  32. package/dist/icons.d.ts +6 -6
  33. package/dist/icons.js +6 -6
  34. package/dist/io/decompress.js +12 -7
  35. package/dist/io/export.js +20 -16
  36. package/dist/io/is-binary.js +19 -4
  37. package/dist/isosurface/parse.js +8 -8
  38. package/dist/isosurface/types.js +9 -9
  39. package/dist/layout/InfoTag.svelte +1 -1
  40. package/dist/layout/json-tree/JsonNode.svelte +1 -0
  41. package/dist/layout/json-tree/utils.js +2 -1
  42. package/dist/marching-cubes.js +1 -1
  43. package/dist/math.js +1 -1
  44. package/dist/overlays/CopyButton.svelte +45 -0
  45. package/dist/overlays/CopyButton.svelte.d.ts +8 -0
  46. package/dist/overlays/InfoPaneCards.svelte +149 -0
  47. package/dist/overlays/InfoPaneCards.svelte.d.ts +22 -0
  48. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +33 -35
  49. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +2 -2
  50. package/dist/phase-diagram/PhaseDiagramControls.svelte +27 -29
  51. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +2 -2
  52. package/dist/phase-diagram/parse.js +3 -3
  53. package/dist/phase-diagram/svg-to-diagram.js +10 -12
  54. package/dist/plot/BarPlot.svelte +24 -15
  55. package/dist/plot/BarPlot.svelte.d.ts +3 -2
  56. package/dist/plot/FillArea.svelte +2 -3
  57. package/dist/plot/FillArea.svelte.d.ts +3 -2
  58. package/dist/plot/Histogram.svelte +37 -19
  59. package/dist/plot/Line.svelte +2 -3
  60. package/dist/plot/Line.svelte.d.ts +2 -2
  61. package/dist/plot/PlotLegend.svelte +79 -8
  62. package/dist/plot/PlotLegend.svelte.d.ts +4 -0
  63. package/dist/plot/PortalSelect.svelte +5 -5
  64. package/dist/plot/ScatterPlot.svelte +47 -33
  65. package/dist/plot/ScatterPlot.svelte.d.ts +5 -4
  66. package/dist/plot/ScatterPlot3D.svelte +6 -3
  67. package/dist/plot/ScatterPoint.svelte +10 -4
  68. package/dist/plot/ScatterPoint.svelte.d.ts +4 -2
  69. package/dist/plot/SpacegroupBarPlot.svelte +5 -4
  70. package/dist/plot/data-cleaning.js +9 -9
  71. package/dist/plot/index.d.ts +0 -6
  72. package/dist/plot/scales.d.ts +3 -3
  73. package/dist/plot/scales.js +29 -29
  74. package/dist/plot/types.d.ts +5 -9
  75. package/dist/rdf/calc-rdf.js +1 -1
  76. package/dist/sanitize.js +22 -15
  77. package/dist/settings.d.ts +2 -0
  78. package/dist/settings.js +12 -3
  79. package/dist/spectral/Bands.svelte +6 -6
  80. package/dist/spectral/BandsAndDos.svelte +4 -4
  81. package/dist/spectral/BrillouinBandsDos.svelte +3 -3
  82. package/dist/spectral/Dos.svelte +2 -2
  83. package/dist/spectral/helpers.js +1 -1
  84. package/dist/structure/AtomLegend.svelte +4 -4
  85. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  86. package/dist/structure/Cylinder.svelte +7 -7
  87. package/dist/structure/Structure.svelte +169 -27
  88. package/dist/structure/Structure.svelte.d.ts +6 -2
  89. package/dist/structure/StructureControls.svelte +130 -16
  90. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  91. package/dist/structure/StructureInfoPane.svelte +519 -218
  92. package/dist/structure/StructureInfoPane.svelte.d.ts +2 -1
  93. package/dist/structure/StructureScene.svelte +399 -68
  94. package/dist/structure/StructureScene.svelte.d.ts +8 -4
  95. package/dist/structure/atom-properties.js +3 -1
  96. package/dist/structure/bond-order-perception.d.ts +13 -0
  97. package/dist/structure/bond-order-perception.js +367 -0
  98. package/dist/structure/bonding.d.ts +10 -1
  99. package/dist/structure/bonding.js +232 -11
  100. package/dist/structure/export.js +6 -4
  101. package/dist/structure/index.d.ts +19 -4
  102. package/dist/structure/index.js +3 -0
  103. package/dist/structure/label-placement.d.ts +14 -0
  104. package/dist/structure/label-placement.js +72 -0
  105. package/dist/structure/parse.d.ts +2 -1
  106. package/dist/structure/parse.js +25 -36
  107. package/dist/structure/supercell.js +35 -2
  108. package/dist/symmetry/SymmetryStats.svelte +1 -1
  109. package/dist/symmetry/cell-transform.js +15 -1
  110. package/dist/symmetry/index.js +3 -3
  111. package/dist/table/HeatmapTable.svelte +3 -3
  112. package/dist/table/ToggleMenu.svelte +1 -1
  113. package/dist/trajectory/Trajectory.svelte +2 -2
  114. package/dist/trajectory/TrajectoryInfoPane.svelte +14 -88
  115. package/dist/trajectory/extract.js +4 -4
  116. package/dist/trajectory/frame-reader.js +2 -2
  117. package/dist/trajectory/parse/ase.js +2 -6
  118. package/dist/trajectory/parse/hdf5.js +1 -3
  119. package/dist/trajectory/plotting.js +1 -1
  120. package/dist/utils.js +1 -1
  121. package/dist/xrd/calc-xrd.js +1 -1
  122. package/package.json +23 -38
  123. package/dist/structure/ferrox-wasm-types.d.ts +0 -46
  124. package/dist/structure/ferrox-wasm-types.js +0 -18
  125. package/dist/structure/ferrox-wasm.d.ts +0 -94
  126. 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;