matterviz 0.3.5 → 0.3.7

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 (229) hide show
  1. package/dist/MillerIndexInput.svelte +5 -5
  2. package/dist/api/optimade.js +3 -3
  3. package/dist/brillouin/BrillouinZone.svelte +5 -2
  4. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  5. package/dist/brillouin/BrillouinZoneExportPane.svelte +1 -3
  6. package/dist/brillouin/BrillouinZoneInfoPane.svelte +1 -1
  7. package/dist/brillouin/BrillouinZoneScene.svelte +5 -5
  8. package/dist/brillouin/compute.js +21 -21
  9. package/dist/brillouin/index.d.ts +1 -1
  10. package/dist/brillouin/index.js +0 -1
  11. package/dist/brillouin/types.d.ts +8 -13
  12. package/dist/chempot-diagram/ChemPotDiagram.svelte +3 -3
  13. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +3 -4
  14. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +33 -34
  15. package/dist/chempot-diagram/compute.js +1 -7
  16. package/dist/chempot-diagram/temperature.d.ts +1 -1
  17. package/dist/chempot-diagram/temperature.js +1 -3
  18. package/dist/chempot-diagram/types.d.ts +4 -9
  19. package/dist/colors/index.js +5 -5
  20. package/dist/composition/Composition.svelte +2 -1
  21. package/dist/composition/Formula.svelte +7 -4
  22. package/dist/composition/FormulaFilter.svelte +1 -3
  23. package/dist/composition/format.js +4 -4
  24. package/dist/composition/parse.d.ts +2 -1
  25. package/dist/composition/parse.js +61 -46
  26. package/dist/convex-hull/ConvexHull2D.svelte +62 -51
  27. package/dist/convex-hull/ConvexHull3D.svelte +101 -90
  28. package/dist/convex-hull/ConvexHull4D.svelte +70 -58
  29. package/dist/convex-hull/ConvexHullControls.svelte +24 -35
  30. package/dist/convex-hull/ConvexHullInfoPane.svelte +8 -5
  31. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +2 -0
  32. package/dist/convex-hull/ConvexHullStats.svelte +9 -2
  33. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +2 -0
  34. package/dist/convex-hull/GasPressureControls.svelte +7 -7
  35. package/dist/convex-hull/StructurePopup.svelte +65 -30
  36. package/dist/convex-hull/StructurePopup.svelte.d.ts +6 -6
  37. package/dist/convex-hull/TemperatureSlider.svelte +8 -5
  38. package/dist/convex-hull/barycentric-coords.d.ts +2 -2
  39. package/dist/convex-hull/barycentric-coords.js +2 -2
  40. package/dist/convex-hull/gas-thermodynamics.js +2 -4
  41. package/dist/convex-hull/helpers.d.ts +13 -2
  42. package/dist/convex-hull/helpers.js +37 -16
  43. package/dist/convex-hull/index.d.ts +1 -0
  44. package/dist/convex-hull/index.js +1 -0
  45. package/dist/convex-hull/thermodynamics.d.ts +2 -1
  46. package/dist/convex-hull/thermodynamics.js +7 -7
  47. package/dist/convex-hull/types.d.ts +15 -15
  48. package/dist/effects.svelte.d.ts +12 -0
  49. package/dist/effects.svelte.js +37 -0
  50. package/dist/element/BohrAtom.svelte +4 -4
  51. package/dist/element/data.json.gz.d.ts +3 -1
  52. package/dist/element/index.d.ts +1 -1
  53. package/dist/element/index.js +0 -1
  54. package/dist/fermi-surface/FermiSurface.svelte +4 -4
  55. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  56. package/dist/fermi-surface/FermiSurfaceControls.svelte +15 -19
  57. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  58. package/dist/fermi-surface/FermiSurfaceScene.svelte +8 -6
  59. package/dist/fermi-surface/compute.js +2 -2
  60. package/dist/fermi-surface/export.js +13 -26
  61. package/dist/fermi-surface/parse.js +8 -12
  62. package/dist/fermi-surface/types.d.ts +2 -5
  63. package/dist/heatmap-matrix/HeatmapMatrix.svelte +21 -3
  64. package/dist/heatmap-matrix/index.js +6 -6
  65. package/dist/io/decompress.d.ts +2 -1
  66. package/dist/io/decompress.js +1 -1
  67. package/dist/io/export.js +1 -1
  68. package/dist/io/index.d.ts +1 -1
  69. package/dist/io/index.js +0 -1
  70. package/dist/io/url-drop.js +7 -1
  71. package/dist/isosurface/IsosurfaceControls.svelte +11 -25
  72. package/dist/isosurface/slice.js +1 -1
  73. package/dist/isosurface/types.js +12 -12
  74. package/dist/labels.d.ts +1 -1
  75. package/dist/labels.js +14 -11
  76. package/dist/layout/InfoTag.svelte +6 -4
  77. package/dist/layout/PropertyFilter.svelte +4 -2
  78. package/dist/layout/json-tree/JsonTree.svelte +22 -14
  79. package/dist/layout/json-tree/JsonValue.svelte +2 -2
  80. package/dist/layout/json-tree/types.d.ts +3 -2
  81. package/dist/layout/json-tree/types.js +0 -1
  82. package/dist/layout/json-tree/utils.d.ts +4 -4
  83. package/dist/layout/json-tree/utils.js +12 -20
  84. package/dist/marching-cubes.js +13 -15
  85. package/dist/math.d.ts +11 -1
  86. package/dist/math.js +15 -6
  87. package/dist/overlays/DragControlTab.svelte +98 -0
  88. package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
  89. package/dist/overlays/DraggablePane.svelte +7 -84
  90. package/dist/overlays/index.d.ts +1 -0
  91. package/dist/overlays/index.js +1 -0
  92. package/dist/periodic-table/PeriodicTable.svelte +11 -11
  93. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +4 -2
  94. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  95. package/dist/phase-diagram/PhaseDiagramControls.svelte +4 -9
  96. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  97. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +2 -10
  98. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +2 -3
  99. package/dist/phase-diagram/TdbInfoPanel.svelte +3 -3
  100. package/dist/phase-diagram/build-diagram.js +11 -18
  101. package/dist/phase-diagram/diagram-input.d.ts +5 -9
  102. package/dist/phase-diagram/index.d.ts +2 -2
  103. package/dist/phase-diagram/index.js +0 -2
  104. package/dist/phase-diagram/parse.d.ts +2 -2
  105. package/dist/phase-diagram/parse.js +6 -10
  106. package/dist/phase-diagram/svg-to-diagram.js +15 -15
  107. package/dist/phase-diagram/types.d.ts +5 -11
  108. package/dist/phase-diagram/utils.d.ts +2 -2
  109. package/dist/phase-diagram/utils.js +9 -11
  110. package/dist/plot/BarPlot.svelte +162 -314
  111. package/dist/plot/BarPlot.svelte.d.ts +5 -4
  112. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  113. package/dist/plot/BinnedScatterPlot.svelte +1114 -0
  114. package/dist/plot/BinnedScatterPlot.svelte.d.ts +66 -0
  115. package/dist/plot/ColorBar.svelte +19 -17
  116. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  117. package/dist/plot/FillArea.svelte +2 -4
  118. package/dist/plot/FillArea.svelte.d.ts +1 -1
  119. package/dist/plot/Histogram.svelte +167 -281
  120. package/dist/plot/Histogram.svelte.d.ts +1 -1
  121. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  122. package/dist/plot/InteractiveAxisLabel.svelte +5 -3
  123. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  124. package/dist/plot/PlotAxis.svelte +169 -0
  125. package/dist/plot/PlotAxis.svelte.d.ts +24 -0
  126. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  127. package/dist/plot/ReferenceLine3D.svelte +53 -51
  128. package/dist/plot/ReferencePlane.svelte +39 -42
  129. package/dist/plot/ScatterPlot.svelte +300 -367
  130. package/dist/plot/ScatterPlot.svelte.d.ts +8 -5
  131. package/dist/plot/ScatterPlot3D.svelte +33 -6
  132. package/dist/plot/ScatterPlot3D.svelte.d.ts +3 -2
  133. package/dist/plot/ScatterPlot3DControls.svelte +9 -9
  134. package/dist/plot/ScatterPlotControls.svelte +3 -4
  135. package/dist/plot/ScatterPoint.svelte +18 -27
  136. package/dist/plot/ScatterPoint.svelte.d.ts +4 -3
  137. package/dist/plot/Surface3D.svelte +4 -7
  138. package/dist/plot/ZeroLines.svelte +2 -1
  139. package/dist/plot/ZeroLines.svelte.d.ts +2 -1
  140. package/dist/plot/ZoomRect.svelte +2 -2
  141. package/dist/plot/ZoomRect.svelte.d.ts +3 -3
  142. package/dist/plot/adaptive-density.d.ts +69 -0
  143. package/dist/plot/adaptive-density.js +191 -0
  144. package/dist/plot/auto-place.d.ts +43 -0
  145. package/dist/plot/auto-place.js +122 -0
  146. package/dist/plot/axis-utils.js +3 -5
  147. package/dist/plot/binned-scatter-types.d.ts +59 -0
  148. package/dist/plot/binned-scatter-types.js +1 -0
  149. package/dist/plot/data-cleaning.js +1 -1
  150. package/dist/plot/data-transform.js +1 -1
  151. package/dist/plot/fill-utils.d.ts +4 -9
  152. package/dist/plot/fill-utils.js +29 -44
  153. package/dist/plot/index.d.ts +4 -0
  154. package/dist/plot/index.js +2 -0
  155. package/dist/plot/interactions.d.ts +4 -4
  156. package/dist/plot/interactions.js +4 -3
  157. package/dist/plot/layout.d.ts +20 -2
  158. package/dist/plot/layout.js +59 -16
  159. package/dist/plot/reference-line.d.ts +1 -1
  160. package/dist/plot/reference-line.js +9 -11
  161. package/dist/plot/scales.d.ts +1 -1
  162. package/dist/plot/scales.js +20 -23
  163. package/dist/plot/types.d.ts +30 -58
  164. package/dist/plot/types.js +2 -6
  165. package/dist/plot/utils/label-placement.d.ts +24 -3
  166. package/dist/plot/utils/label-placement.js +82 -12
  167. package/dist/plot/utils/series-visibility.d.ts +8 -2
  168. package/dist/plot/utils/series-visibility.js +23 -5
  169. package/dist/rdf/RdfPlot.svelte +5 -5
  170. package/dist/rdf/calc-rdf.js +3 -3
  171. package/dist/sanitize.d.ts +2 -0
  172. package/dist/sanitize.js +2 -0
  173. package/dist/spectral/Bands.svelte +1 -1
  174. package/dist/spectral/BandsAndDos.svelte +22 -16
  175. package/dist/spectral/BrillouinBandsDos.svelte +20 -16
  176. package/dist/spectral/Dos.svelte +1 -1
  177. package/dist/spectral/helpers.d.ts +4 -2
  178. package/dist/spectral/helpers.js +44 -35
  179. package/dist/spectral/index.d.ts +1 -1
  180. package/dist/spectral/index.js +0 -1
  181. package/dist/structure/AtomLegend.svelte +23 -6
  182. package/dist/structure/AtomLegend.svelte.d.ts +1 -0
  183. package/dist/structure/CanvasTooltip.svelte +9 -9
  184. package/dist/structure/CanvasTooltip.svelte.d.ts +1 -1
  185. package/dist/structure/CellSelect.svelte +14 -16
  186. package/dist/structure/Structure.svelte +317 -68
  187. package/dist/structure/Structure.svelte.d.ts +4 -2
  188. package/dist/structure/StructureControls.svelte +20 -45
  189. package/dist/structure/StructureExportPane.svelte +2 -1
  190. package/dist/structure/StructureInfoPane.svelte +10 -8
  191. package/dist/structure/StructureScene.svelte +527 -177
  192. package/dist/structure/StructureScene.svelte.d.ts +5 -2
  193. package/dist/structure/atom-properties.js +4 -4
  194. package/dist/structure/bond-order-perception.js +115 -98
  195. package/dist/structure/bonding.d.ts +27 -1
  196. package/dist/structure/bonding.js +187 -16
  197. package/dist/structure/export.js +1 -1
  198. package/dist/structure/index.d.ts +3 -2
  199. package/dist/structure/index.js +0 -2
  200. package/dist/structure/parse.js +88 -59
  201. package/dist/symmetry/WyckoffTable.svelte +7 -0
  202. package/dist/symmetry/index.js +13 -14
  203. package/dist/table/HeatmapTable.svelte +45 -66
  204. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  205. package/dist/table/ToggleMenu.svelte +19 -10
  206. package/dist/theme/themes.mjs +12 -0
  207. package/dist/tooltip/index.d.ts +1 -1
  208. package/dist/tooltip/index.js +0 -1
  209. package/dist/trajectory/Trajectory.svelte +43 -15
  210. package/dist/trajectory/TrajectoryInfoPane.svelte +2 -2
  211. package/dist/trajectory/extract.js +1 -1
  212. package/dist/trajectory/frame-reader.js +4 -4
  213. package/dist/trajectory/helpers.d.ts +5 -4
  214. package/dist/trajectory/helpers.js +9 -17
  215. package/dist/trajectory/index.d.ts +2 -2
  216. package/dist/trajectory/index.js +2 -2
  217. package/dist/trajectory/parse/ase.js +4 -4
  218. package/dist/trajectory/parse/hdf5.js +1 -1
  219. package/dist/trajectory/parse/index.js +2 -3
  220. package/dist/trajectory/parse/lammps.js +1 -1
  221. package/dist/trajectory/parse/vasp.js +1 -1
  222. package/dist/trajectory/plotting.d.ts +1 -1
  223. package/dist/trajectory/plotting.js +38 -38
  224. package/dist/trajectory/types.d.ts +1 -1
  225. package/dist/utils.d.ts +1 -0
  226. package/dist/utils.js +9 -0
  227. package/dist/xrd/calc-xrd.js +3 -4
  228. package/dist/xrd/parse.js +1 -1
  229. package/package.json +42 -22
@@ -16,12 +16,15 @@ export function compute_hull_stability(raw_distance, exclude_from_hull, tol = HU
16
16
  const e_above_hull = Math.abs(raw_distance) < tol ? 0 : Math.max(0, raw_distance);
17
17
  return { e_above_hull, is_stable: e_above_hull <= tol };
18
18
  }
19
+ export const entry_is_stable = (entry, tol = HULL_STABILITY_TOL) => entry.is_stable === true ||
20
+ (entry.is_stable !== false && Math.abs(entry.e_above_hull ?? Infinity) <= tol);
19
21
  // Check if entry is on the convex hull (stable or e_above_hull ≈ 0)
20
- export const is_on_hull = (entry, tol = HULL_STABILITY_TOL) => !entry.exclude_from_hull &&
21
- (entry.is_stable === true ||
22
- (typeof entry.e_above_hull === `number` && entry.e_above_hull < tol));
22
+ export const is_on_hull = (entry, tol = HULL_STABILITY_TOL) => !entry.exclude_from_hull && entry_is_stable(entry, tol);
23
23
  export const get_arity = (entry) => Object.values(entry.composition).filter((count) => count > 0).length;
24
24
  export const is_unary_entry = (entry) => get_arity(entry) === 1;
25
+ export const entry_is_unstable = (entry) => !entry_is_stable(entry);
26
+ export const entry_is_visible = (entry, show_stable, show_unstable) => (entry_is_stable(entry) ? show_stable : show_unstable);
27
+ export const visible_entries = (entries, show_stable, show_unstable) => entries.filter((entry) => entry_is_visible(entry, show_stable, show_unstable));
25
28
  // Energy color scale factory (shared)
26
29
  export function get_energy_color_scale(color_mode, color_scale, plot_entries) {
27
30
  if (color_mode !== `energy` || plot_entries.length === 0)
@@ -43,7 +46,7 @@ export function get_energy_color_scale(color_mode, color_scale, plot_entries) {
43
46
  }
44
47
  // Point color resolver (shared)
45
48
  export function get_point_color_for_entry(entry, color_mode, colors, energy_scale) {
46
- const is_stable = Boolean(entry.is_stable) || entry.e_above_hull === 0;
49
+ const is_stable = entry_is_stable(entry);
47
50
  if (color_mode === `stability`) {
48
51
  return is_stable ? colors?.stable || `#0072B2` : colors?.unstable || `#E69F00`;
49
52
  }
@@ -95,6 +98,28 @@ export function compute_auto_hull_dist_threshold(n_entries, max_hull_dist_in_dat
95
98
  const t = (n_entries - LOW) / (HIGH - LOW);
96
99
  return max_hull_dist_in_data * (1 - t) + static_default * t;
97
100
  }
101
+ export function auto_threshold_reset(default_threshold) {
102
+ let source;
103
+ let auto_threshold = default_threshold;
104
+ let initialized = false;
105
+ return (next_source, current_threshold, next_auto_threshold) => {
106
+ if (initialized && next_source === source)
107
+ return undefined;
108
+ const user_changed = initialized && Math.abs(current_threshold - auto_threshold) > 0.001;
109
+ source = next_source;
110
+ auto_threshold = next_auto_threshold;
111
+ initialized = true;
112
+ return user_changed ? undefined : next_auto_threshold;
113
+ };
114
+ }
115
+ export function current_entry(entry, entries) {
116
+ if (!entry)
117
+ return null;
118
+ if (entry.entry_id) {
119
+ return entries.find((candidate) => candidate.entry_id === entry.entry_id) ?? null;
120
+ }
121
+ return entries.includes(entry) ? entry : null;
122
+ }
98
123
  // Build a tooltip text for any phase entry (shared)
99
124
  export function build_entry_tooltip_text(entry) {
100
125
  const is_element = is_unary_entry(entry);
@@ -119,7 +144,7 @@ export function build_entry_tooltip_text(entry) {
119
144
  text += `E<sub>above hull</sub>: ${e_hull_str} eV/atom\n`;
120
145
  }
121
146
  // Fallback to energy_per_atom if e_form_per_atom is absent
122
- const e_form_display = entry.e_form_per_atom !== undefined ? entry.e_form_per_atom : entry.energy_per_atom;
147
+ const e_form_display = entry.e_form_per_atom ?? entry.energy_per_atom;
123
148
  if (e_form_display !== undefined) {
124
149
  const e_form_str = format_num(e_form_display, `.3~`);
125
150
  text += `E<sub>form</sub>: ${e_form_str} eV/atom`;
@@ -137,11 +162,9 @@ export function find_hull_entry_at_mouse(canvas, event, plot_entries, project_po
137
162
  const mouse_y = event.clientY - rect.top;
138
163
  const container_scale = Math.min(canvas.clientWidth || 600, canvas.clientHeight || 600) / 600;
139
164
  for (const entry of plot_entries) {
140
- if (!entry.visible)
141
- continue;
142
165
  const projected = project_point(entry.x, entry.y, entry.z);
143
166
  const distance = Math.hypot(mouse_x - projected.x, mouse_y - projected.y);
144
- const base = entry.size ?? (entry.is_stable || entry.e_above_hull === 0 ? 6 : 4);
167
+ const base = entry.size ?? (entry_is_stable(entry) ? 6 : 4);
145
168
  if (distance < base * container_scale + 5)
146
169
  return entry;
147
170
  }
@@ -421,10 +444,10 @@ export function draw_highlight_effect(ctx, projected, size, container_scale, pul
421
444
  if (effect === `pulse`) {
422
445
  // Smooth pulsating effect with moderate size and opacity changes
423
446
  const pulse_val = 0.5 + 0.5 * Math.sin(pulse_time * pulse_speed);
424
- const hl_size = size * (size_multiplier + 0.5 * pulse_val); // Moderate pulse amplitude
425
- const hl_opacity = opacity * (0.5 + 0.5 * pulse_val); // Smooth opacity variation
447
+ const hl_size = size * (size_multiplier + 0.5 * pulse_val);
448
+ const hl_opacity = opacity * (0.5 + 0.5 * pulse_val);
426
449
  // Draw pulsating ring
427
- ctx.lineWidth = (1.5 + 1 * pulse_val) * container_scale;
450
+ ctx.lineWidth = (1.5 + pulse_val) * container_scale;
428
451
  ctx.beginPath();
429
452
  ctx.arc(projected.x, projected.y, hl_size, 0, 2 * Math.PI);
430
453
  ctx.fillStyle = apply_alpha_to_color(hl_color, hl_opacity * 0.3);
@@ -522,9 +545,7 @@ function entry_has_temp_data(entry) {
522
545
  temperatures.length === free_energies.length);
523
546
  }
524
547
  // Check if entry has data at exact temperature T
525
- export function entry_has_temperature(entry, T) {
526
- return entry_has_temp_data(entry) && (entry.temperatures?.includes(T) ?? false);
527
- }
548
+ export const entry_has_temperature = (entry, T) => entry_has_temp_data(entry) && (entry.temperatures?.includes(T) ?? false);
528
549
  // Get energy at temperature T (throws if T not found - validate with entry_has_temperature first)
529
550
  export function get_energy_at_temperature(entry, T) {
530
551
  const temps = entry.temperatures ?? [];
@@ -625,7 +646,7 @@ export function filter_entries_at_temperature(entries, T, options = {}) {
625
646
  // Analyze entries for gas-dependent elements (safe wrapper with optional config)
626
647
  // Returns information about which gases are relevant for the chemical system.
627
648
  export function safe_analyze_gas_data(entries, config) {
628
- if (!config || !config.enabled_gases?.length) {
649
+ if (!config?.enabled_gases?.length) {
629
650
  return {
630
651
  has_gas_dependent_elements: false,
631
652
  gas_elements: [],
@@ -638,7 +659,7 @@ export function safe_analyze_gas_data(entries, config) {
638
659
  // This adjusts formation energies based on gas atmosphere conditions (T, P).
639
660
  // Should be applied after temperature filtering.
640
661
  export function safe_apply_gas_corrections(entries, config, T) {
641
- if (!config || !config.enabled_gases?.length)
662
+ if (!config?.enabled_gases?.length)
642
663
  return entries;
643
664
  return _apply_gas_corrections(entries, config, T);
644
665
  }
@@ -11,6 +11,7 @@ export { default as ConvexHullControls } from './ConvexHullControls.svelte';
11
11
  export { default as ConvexHullInfoPane } from './ConvexHullInfoPane.svelte';
12
12
  export { default as ConvexHullStats } from './ConvexHullStats.svelte';
13
13
  export { default as ConvexHullTooltip } from './ConvexHullTooltip.svelte';
14
+ export { default as StructurePopup } from './StructurePopup.svelte';
14
15
  export * from './demo-temperature';
15
16
  export * from './gas-thermodynamics';
16
17
  export { default as GasPressureControls } from './GasPressureControls.svelte';
@@ -7,6 +7,7 @@ export { default as ConvexHullControls } from './ConvexHullControls.svelte';
7
7
  export { default as ConvexHullInfoPane } from './ConvexHullInfoPane.svelte';
8
8
  export { default as ConvexHullStats } from './ConvexHullStats.svelte';
9
9
  export { default as ConvexHullTooltip } from './ConvexHullTooltip.svelte';
10
+ export { default as StructurePopup } from './StructurePopup.svelte';
10
11
  export * from './demo-temperature';
11
12
  export * from './gas-thermodynamics';
12
13
  export { default as GasPressureControls } from './GasPressureControls.svelte';
@@ -1,5 +1,6 @@
1
1
  import type { ElementSymbol } from '../element';
2
- import type { ConvexHullEntry, ConvexHullTriangle, PhaseData, PhaseStats, Point2D, Point3D, ProcessedPhaseData } from './types';
2
+ import type { Point2D, Point3D } from '../math';
3
+ import type { ConvexHullEntry, ConvexHullTriangle, PhaseData, PhaseStats, ProcessedPhaseData } from './types';
3
4
  export declare function normalize_hull_composition_keys(composition: Record<string, number>): Partial<Record<ElementSymbol, number>>;
4
5
  export declare function process_hull_entries(entries: PhaseData[]): ProcessedPhaseData;
5
6
  export declare function compute_e_form_per_atom(entry: PhaseData, el_refs: Record<string, PhaseData>): number | null;
@@ -4,6 +4,8 @@ import { barycentric_to_ternary_xyz, barycentric_to_tetrahedral, composition_to_
4
4
  import { get_arity, HULL_STABILITY_TOL, is_on_hull, is_unary_entry } from './helpers';
5
5
  // Track warned keys to avoid log spam on large datasets with repeated invalid keys
6
6
  const warned_keys = new Set();
7
+ const cross_point_2d = (origin, point_a, point_b) => (point_a.x - origin.x) * (point_b.y - origin.y) -
8
+ (point_a.y - origin.y) * (point_b.x - origin.x);
7
9
  // Normalize convex hull composition keys by stripping oxidation states (e.g. "V4+" -> "V")
8
10
  // and merging amounts for keys that map to the same element. Filters non-positive amounts.
9
11
  // Only extracts FIRST valid element from each key (e.g. "Fe2O3" -> "Fe", not both Fe and O).
@@ -22,7 +24,7 @@ export function normalize_hull_composition_keys(composition) {
22
24
  }
23
25
  continue;
24
26
  }
25
- normalized[elem] = (normalized[elem] || 0) + amount;
27
+ normalized[elem] = (normalized[elem] ?? 0) + amount;
26
28
  }
27
29
  return normalized;
28
30
  }
@@ -153,7 +155,7 @@ export function calculate_e_above_hull(input, reference_entries) {
153
155
  const total = count_atoms_in_composition(ref.composition);
154
156
  if (total <= 0)
155
157
  continue;
156
- const x = (ref.composition[el2] || 0) / total;
158
+ const x = (ref.composition[el2] ?? 0) / total;
157
159
  const current = hull_input_map.get(x);
158
160
  if (current === undefined || e_form < current) {
159
161
  hull_input_map.set(x, e_form);
@@ -178,7 +180,7 @@ export function calculate_e_above_hull(input, reference_entries) {
178
180
  results[id] = NaN;
179
181
  continue;
180
182
  }
181
- const x = (entry.composition[el2] || 0) / total;
183
+ const x = (entry.composition[el2] ?? 0) / total;
182
184
  const y_hull = interpolate_hull_2d(lower_hull, x);
183
185
  results[id] = y_hull === null ? NaN : Math.max(0, e_form - y_hull);
184
186
  }
@@ -438,7 +440,6 @@ export function get_convex_hull_stats(processed_entries, elements, max_arity = 4
438
440
  function to_hull_entry(entry) {
439
441
  return {
440
442
  ...entry,
441
- visible: true,
442
443
  is_element: get_arity(entry) === 1,
443
444
  x: 0,
444
445
  y: 0,
@@ -501,10 +502,9 @@ export function compute_lower_hull_2d(points) {
501
502
  // Sort by x then y
502
503
  const sorted = [...points].sort((p1, p2) => p1.x - p2.x || p1.y - p2.y);
503
504
  const lower = [];
504
- const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
505
505
  for (const point of sorted) {
506
506
  while (lower.length >= 2 &&
507
- cross(lower[lower.length - 2], lower[lower.length - 1], point) <= 0)
507
+ cross_point_2d(lower[lower.length - 2], lower[lower.length - 1], point) <= 0)
508
508
  lower.pop();
509
509
  lower.push(point);
510
510
  }
@@ -838,7 +838,7 @@ export const compute_e_above_hull_for_points = (points, models) => points.map((p
838
838
  const z_hull = e_hull_at_xy(models, point.x, point.y);
839
839
  if (z_hull === null)
840
840
  return 0;
841
- return point.z - z_hull;
841
+ return Math.max(0, point.z - z_hull);
842
842
  });
843
843
  const subtract_4d = (pt1, pt2) => ({
844
844
  x: pt1.x - pt2.x,
@@ -1,9 +1,20 @@
1
1
  import type { CompositionType } from '../composition';
2
2
  import type { ShowControlsProp } from '../controls';
3
3
  import type { ElementSymbol } from '../element';
4
- import type { Vec3 } from '../math';
5
- import type { Sides } from '../plot';
6
- import type { Rect } from '../plot/layout';
4
+ import type { Point2D, Point3D, Vec3 } from '../math';
5
+ import type { Rect, Sides } from '../plot/layout';
6
+ import type { AnyStructure } from '../structure';
7
+ export interface StructurePopupStats {
8
+ id?: string;
9
+ formula?: string;
10
+ e_above_hull?: number;
11
+ e_form?: number;
12
+ }
13
+ export interface StructurePopupContext {
14
+ structure: AnyStructure;
15
+ stats?: StructurePopupStats;
16
+ formula_html: string;
17
+ }
7
18
  export interface PhaseData {
8
19
  composition: CompositionType;
9
20
  energy: number;
@@ -33,13 +44,6 @@ export interface ProcessedPhaseData {
33
44
  elements: ElementSymbol[];
34
45
  el_refs: Record<string, PhaseData>;
35
46
  }
36
- export interface Point2D {
37
- x: number;
38
- y: number;
39
- }
40
- export interface Point3D extends Point2D {
41
- z: number;
42
- }
43
47
  export type MarkerSymbol = // Marker symbol types for convex hull entries
44
48
  `circle` | `star` | `triangle` | `cross` | `diamond` | `square` | `wye`;
45
49
  export type HullFaceColorMode = `uniform` | `formation_energy` | `dominant_element` | `facet_index`;
@@ -47,7 +51,6 @@ export declare const HULL_FACE_COLOR_MODES: readonly HullFaceColorMode[];
47
51
  export interface ConvexHullEntry extends PhaseData, Point3D {
48
52
  is_element: boolean;
49
53
  size?: number;
50
- visible: boolean;
51
54
  marker?: MarkerSymbol;
52
55
  }
53
56
  export interface ConvexHullConfig {
@@ -103,10 +106,7 @@ export interface LabelPlacement {
103
106
  }
104
107
  export interface HoverData3D<T = ConvexHullEntry> {
105
108
  entry: T;
106
- position: {
107
- x: number;
108
- y: number;
109
- };
109
+ position: Point2D;
110
110
  }
111
111
  export interface PhaseStats {
112
112
  total: number;
@@ -0,0 +1,12 @@
1
+ type PulseAnimationOptions = {
2
+ step?: number;
3
+ frequency?: number;
4
+ on_tick?: () => void;
5
+ reset_when_inactive?: boolean;
6
+ };
7
+ type PulseAnimation = {
8
+ readonly time: number;
9
+ readonly unit: number;
10
+ };
11
+ export declare function create_pulse_animation(active: () => boolean, options?: PulseAnimationOptions): PulseAnimation;
12
+ export {};
@@ -0,0 +1,37 @@
1
+ export function create_pulse_animation(active, options = {}) {
2
+ let time = $state(0);
3
+ let frame_id = null;
4
+ const { step = 0.02, frequency = 4, on_tick, reset_when_inactive = true } = options;
5
+ const cancel_frame = () => {
6
+ if (frame_id == null)
7
+ return;
8
+ cancelAnimationFrame(frame_id);
9
+ frame_id = null;
10
+ };
11
+ const stop = () => {
12
+ cancel_frame();
13
+ if (reset_when_inactive)
14
+ time = 0;
15
+ };
16
+ $effect(() => {
17
+ if (!active())
18
+ return stop();
19
+ const animate = () => {
20
+ time += step;
21
+ on_tick?.();
22
+ if (!active())
23
+ return stop();
24
+ frame_id = requestAnimationFrame(animate);
25
+ };
26
+ frame_id = requestAnimationFrame(animate);
27
+ return cancel_frame;
28
+ });
29
+ return {
30
+ get time() {
31
+ return time;
32
+ },
33
+ get unit() {
34
+ return 0.5 + 0.5 * Math.sin(time * frequency);
35
+ },
36
+ };
37
+ }
@@ -84,10 +84,10 @@
84
84
 
85
85
  <!-- electron orbitals -->
86
86
  {#each shells as electrons, shell_idx (`${shell_idx}-${electrons}`)}
87
- {@const n = shell_idx + 1}
88
- {@const shell_radius = nucleus_svg_props.r + n * shell_width}
89
- {@const active = n === highlight_shell}
90
- <g class="shell" style:animation-duration="{orbital_period * n ** 1.5}s">
87
+ {@const shell_number = shell_idx + 1}
88
+ {@const shell_radius = nucleus_svg_props.r + shell_number * shell_width}
89
+ {@const active = shell_number === highlight_shell}
90
+ <g class="shell" style:animation-duration="{orbital_period * shell_number ** 1.5}s">
91
91
  <circle
92
92
  r={shell_radius}
93
93
  {...shell_svg_props}
@@ -1,2 +1,4 @@
1
- declare const data: import('./types.ts').ChemicalElement[]
1
+ import type { ChemicalElement } from './types'
2
+
3
+ declare const data: ChemicalElement[]
2
4
  export default data
@@ -1,4 +1,4 @@
1
- export * from './types';
1
+ export type * from './types';
2
2
  export { default as BohrAtom } from './BohrAtom.svelte';
3
3
  export { default as element_data } from './data';
4
4
  export { default as ElementHeading } from './ElementHeading.svelte';
@@ -1,4 +1,3 @@
1
- export * from './types';
2
1
  export { default as BohrAtom } from './BohrAtom.svelte';
3
2
  export { default as element_data } from './data';
4
3
  export { default as ElementHeading } from './ElementHeading.svelte';
@@ -165,8 +165,8 @@
165
165
 
166
166
  // Yield to browser so spinner can render before heavy computation
167
167
  const tick = () =>
168
- new Promise<void>((r) =>
169
- requestAnimationFrame(() => requestAnimationFrame(() => r()))
168
+ new Promise<void>((resolve) =>
169
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
170
170
  )
171
171
 
172
172
  // Parse and load Fermi surface from content (async for UI responsiveness)
@@ -339,8 +339,8 @@
339
339
  })
340
340
 
341
341
  function handle_keydown(event: KeyboardEvent) {
342
- const target = event.target as HTMLElement
343
- if ([`INPUT`, `TEXTAREA`].includes(target.tagName)) return
342
+ const target = event.target
343
+ if (target instanceof HTMLElement && [`INPUT`, `TEXTAREA`].includes(target.tagName)) return
344
344
  // Only handle shortcuts when component is focused/hovered or contains focus
345
345
  if (!wrapper?.contains(document.activeElement) && !hovered) return
346
346
 
@@ -78,6 +78,6 @@ type $$ComponentProps = {
78
78
  }]> | FermiTooltipConfig;
79
79
  on_hover?: (data: FermiHoverData | null) => void;
80
80
  } & HTMLAttributes<HTMLDivElement>;
81
- declare const FermiSurface: import("svelte").Component<$$ComponentProps, {}, "height" | "width" | "dragover" | "fullscreen" | "hovered" | "controls_open" | "loading" | "camera_projection" | "color_scale" | "wrapper" | "error_msg" | "surface_opacity" | "show_vectors" | "bz_data" | "mu" | "selected_bands" | "interpolation_factor" | "fermi_data" | "band_data" | "color_property" | "representation" | "show_bz" | "bz_opacity" | "tile_bz" | "clip_enabled" | "clip_axis" | "clip_position" | "clip_flip">;
81
+ declare const FermiSurface: import("svelte").Component<$$ComponentProps, {}, "height" | "width" | "dragover" | "loading" | "fullscreen" | "hovered" | "controls_open" | "camera_projection" | "color_scale" | "wrapper" | "error_msg" | "surface_opacity" | "show_vectors" | "bz_data" | "mu" | "selected_bands" | "interpolation_factor" | "fermi_data" | "band_data" | "color_property" | "representation" | "show_bz" | "bz_opacity" | "tile_bz" | "clip_enabled" | "clip_axis" | "clip_position" | "clip_flip">;
82
82
  type FermiSurface = ReturnType<typeof FermiSurface>;
83
83
  export default FermiSurface;
@@ -2,7 +2,8 @@
2
2
  import SettingsSection from '../layout/SettingsSection.svelte'
3
3
  import DraggablePane from '../overlays/DraggablePane.svelte'
4
4
  import type { CameraProjection } from '../settings'
5
- import { type Snippet } from 'svelte'
5
+ import { make_change_detector } from '../utils'
6
+ import type { Snippet } from 'svelte'
6
7
  import type {
7
8
  BandGridData,
8
9
  ColorProperty,
@@ -80,9 +81,9 @@
80
81
  { value: `interpolateSpectral`, label: `Spectral` },
81
82
  ]
82
83
 
83
- // Check if custom properties are available on any surface
84
- let has_custom_properties = $derived(
85
- fermi_data?.isosurfaces?.some((iso) => iso.properties?.length) ?? false,
84
+ let has_custom_color = $derived(
85
+ Boolean(custom_property_label) ||
86
+ (fermi_data?.isosurfaces?.some((iso) => iso.properties?.length) ?? false),
86
87
  )
87
88
 
88
89
  // Get unique band indices from Fermi surface data
@@ -93,18 +94,14 @@
93
94
  )
94
95
  : [],
95
96
  )
96
- // Track previous available bands to detect when fermi_data changes
97
- let prev_available_bands = $state<number[]>([])
97
+ let available_bands_key = $derived(available_bands.join(`,`))
98
+ const available_bands_changed = make_change_detector()
98
99
 
99
- // Reset selected_bands when available_bands changes (new file loaded)
100
- // This ensures band selection doesn't persist across different files
101
100
  $effect(() => {
102
- const bands_changed = available_bands.length !== prev_available_bands.length ||
103
- available_bands.some((band, idx) => band !== prev_available_bands[idx])
104
- if (bands_changed) { // Always update tracking variable to avoid stale comparisons
105
- prev_available_bands = [...available_bands]
106
- // Only reset selected_bands when there are bands to select
107
- if (available_bands.length > 0) selected_bands = [...available_bands]
101
+ if (color_property === `custom` && !has_custom_color) color_property = `band`
102
+ const bands_changed = available_bands_changed(available_bands_key)
103
+ if (available_bands.length > 0 && (selected_bands === undefined || bands_changed)) {
104
+ selected_bands = [...available_bands]
108
105
  }
109
106
  })
110
107
 
@@ -121,9 +118,8 @@
121
118
  }
122
119
  }
123
120
 
124
- function handle_mu_change(event: Event) {
125
- const target = event.target as HTMLInputElement
126
- const trimmed = target.value.trim()
121
+ function handle_mu_change(event: Event & { currentTarget: HTMLInputElement }) {
122
+ const trimmed = event.currentTarget.value.trim()
127
123
  const parsed = parseFloat(trimmed)
128
124
  // Only update mu when input is valid; keep last valid value during transient
129
125
  // invalid states (e.g. empty string while user is typing a new value)
@@ -216,7 +212,7 @@
216
212
  <option value="band">Band</option>
217
213
  <option value="velocity">Velocity</option>
218
214
  <option value="spin">Spin</option>
219
- {#if has_custom_properties || custom_property_label}
215
+ {#if has_custom_color}
220
216
  <option value="custom">{custom_property_label ?? `Custom`}</option>
221
217
  {/if}
222
218
  </select>
@@ -334,7 +330,7 @@
334
330
  <select
335
331
  value={interpolation_factor}
336
332
  onchange={(event) => {
337
- const val = parseFloat((event.target as HTMLSelectElement).value)
333
+ const val = parseFloat(event.currentTarget.value)
338
334
  if (!Number.isFinite(val)) return
339
335
  interpolation_factor = val
340
336
  on_interpolation_change?.(val)
@@ -1,5 +1,5 @@
1
1
  import type { CameraProjection } from '../settings';
2
- import { type Snippet } from 'svelte';
2
+ import type { Snippet } from 'svelte';
3
3
  import type { BandGridData, ColorProperty, FermiSurfaceData, RepresentationMode } from './types';
4
4
  type $$ComponentProps = {
5
5
  controls_open?: boolean;
@@ -307,8 +307,8 @@
307
307
  const e2: Vec3 = math.subtract(v2, v0)
308
308
  const normal = math.cross_3d(e1, e2)
309
309
  const len = Math.hypot(...normal)
310
- const n = len > 1e-10 ? normal.map((x) => x / len) : [0, 0, 1]
311
- normals.push(...n, ...n, ...n)
310
+ const unit_normal = len > 1e-10 ? normal.map((coord) => coord / len) : [0, 0, 1]
311
+ normals.push(...unit_normal, ...unit_normal, ...unit_normal)
312
312
  }
313
313
 
314
314
  // Per-vertex colors for this triangle
@@ -394,7 +394,9 @@
394
394
 
395
395
  const computed_camera_position = $derived.by(
396
396
  () =>
397
- camera_position || ([10, 3, 8].map((x) => x * Math.max(1, scene_size)) as Vec3),
397
+ camera_position || ([10, 3, 8].map((coord) =>
398
+ coord * Math.max(1, scene_size)
399
+ ) as Vec3),
398
400
  )
399
401
 
400
402
  const gizmo_props = $derived({
@@ -463,7 +465,7 @@
463
465
  const e2: Vec3 = math.subtract(v2, v0)
464
466
  const normal_vec = math.cross_3d(e1, e2)
465
467
  const len = Math.hypot(...normal_vec)
466
- const norm = len > 1e-10 ? normal_vec.map((x) => x / len) : [0, 0, 0]
468
+ const norm = len > 1e-10 ? normal_vec.map((coord) => coord / len) : [0, 0, 0]
467
469
  normals.push(...norm, ...norm, ...norm)
468
470
  }
469
471
  }
@@ -656,8 +658,8 @@
656
658
  <!-- Reciprocal lattice vectors -->
657
659
  {#if show_vectors && fermi_data?.k_lattice}
658
660
  {#each fermi_data.k_lattice as vec, idx (idx)}
659
- {@const scaled_vec = vec.map((x) => x * vector_scale) as Vec3}
660
- {@const label_position = scaled_vec.map((x) => x * 1.15) as Vec3}
661
+ {@const scaled_vec = vec.map((coord) => coord * vector_scale) as Vec3}
662
+ {@const label_position = scaled_vec.map((coord) => coord * 1.15) as Vec3}
661
663
  <Arrow
662
664
  position={[0, 0, 0]}
663
665
  vector={scaled_vec}
@@ -3,6 +3,7 @@ import * as math from '../math';
3
3
  import { EPS } from '../math';
4
4
  import { CLOSED_CONTOUR_TOLERANCE, IRREDUCIBLE_BZ_MIN_VERTICES, IRREDUCIBLE_BZ_TOLERANCE, SPANNING_THRESHOLD, } from './constants';
5
5
  import { marching_cubes } from './marching-cubes';
6
+ const safe_mod = (val, dim) => ((val % dim) + dim) % dim;
6
7
  // Precompute Catmull-Rom coefficients for a given t value
7
8
  // Returns [c0, c1, c2, c3] where result = c0*p0 + c1*p1 + c2*p2 + c3*p3
8
9
  function catmull_rom_coeffs(t) {
@@ -238,7 +239,6 @@ function trilinear_interpolate_vec3(grid, x, y, z) {
238
239
  if (nx === 0 || ny === 0 || nz === 0)
239
240
  return [0, 0, 0];
240
241
  // Use safe modulo pattern to handle negative values from floating-point edge cases
241
- const safe_mod = (val, n) => ((val % n) + n) % n;
242
242
  const x0 = safe_mod(Math.floor(x), nx);
243
243
  const y0 = safe_mod(Math.floor(y), ny);
244
244
  const z0 = safe_mod(Math.floor(z), nz);
@@ -387,7 +387,7 @@ function slice_surface_with_plane(surface, plane_normal, plane_distance, in_plan
387
387
  if (intersection) {
388
388
  crossing_edges.push({ edge_key, intersection });
389
389
  // Register this face with the edge
390
- const faces = edge_to_faces.get(edge_key) || [];
390
+ const faces = edge_to_faces.get(edge_key) ?? [];
391
391
  faces.push(face_idx);
392
392
  edge_to_faces.set(edge_key, faces);
393
393
  }
@@ -1,16 +1,3 @@
1
- // Dynamic imports for Three.js exporters (tree-shaking friendly)
2
- async function get_stl_exporter() {
3
- const { STLExporter } = await import(`three/addons/exporters/STLExporter.js`);
4
- return new STLExporter();
5
- }
6
- async function get_obj_exporter() {
7
- const { OBJExporter } = await import(`three/addons/exporters/OBJExporter.js`);
8
- return new OBJExporter();
9
- }
10
- async function get_gltf_exporter() {
11
- const { GLTFExporter } = await import(`three/addons/exporters/GLTFExporter.js`);
12
- return new GLTFExporter();
13
- }
14
1
  // Helper to trigger file download
15
2
  function download_file(content, filename, mime_type) {
16
3
  const blob = new Blob([content], { type: mime_type });
@@ -25,7 +12,8 @@ function download_file(content, filename, mime_type) {
25
12
  }
26
13
  // Export scene to STL format (good for 3D printing)
27
14
  export async function export_to_stl(scene, filename) {
28
- const exporter = await get_stl_exporter();
15
+ const { STLExporter } = await import(`three/addons/exporters/STLExporter.js`);
16
+ const exporter = new STLExporter();
29
17
  const result = exporter.parse(scene, { binary: true });
30
18
  // Binary STL returns DataView, convert to ArrayBuffer for Blob
31
19
  const buffer = result instanceof DataView ? result.buffer : result;
@@ -33,13 +21,15 @@ export async function export_to_stl(scene, filename) {
33
21
  }
34
22
  // Export scene to OBJ format (widely compatible)
35
23
  export async function export_to_obj(scene, filename) {
36
- const exporter = await get_obj_exporter();
24
+ const { OBJExporter } = await import(`three/addons/exporters/OBJExporter.js`);
25
+ const exporter = new OBJExporter();
37
26
  const result = exporter.parse(scene);
38
27
  download_file(result, `${filename}.obj`, `text/plain`);
39
28
  }
40
29
  // Export scene to GLTF format (modern web/AR standard)
41
30
  export async function export_to_gltf(scene, filename) {
42
- const exporter = await get_gltf_exporter();
31
+ const { GLTFExporter } = await import(`three/addons/exporters/GLTFExporter.js`);
32
+ const exporter = new GLTFExporter();
43
33
  return new Promise((resolve, reject) => {
44
34
  exporter.parse(scene, (gltf) => {
45
35
  const output = JSON.stringify(gltf, null, 2);
@@ -50,14 +40,11 @@ export async function export_to_gltf(scene, filename) {
50
40
  }
51
41
  // Main export function that dispatches to the appropriate format
52
42
  export function export_scene(scene, format, filename) {
53
- switch (format) {
54
- case `stl`:
55
- return export_to_stl(scene, filename);
56
- case `obj`:
57
- return export_to_obj(scene, filename);
58
- case `gltf`:
59
- return export_to_gltf(scene, filename);
60
- default:
61
- return Promise.reject(new Error(`Unsupported export format: ${format}`));
62
- }
43
+ if (format === `stl`)
44
+ return export_to_stl(scene, filename);
45
+ if (format === `obj`)
46
+ return export_to_obj(scene, filename);
47
+ if (format === `gltf`)
48
+ return export_to_gltf(scene, filename);
49
+ return Promise.reject(new Error(`Unsupported export format: ${format}`));
63
50
  }