matterviz 0.1.13 → 0.1.14

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 (175) hide show
  1. package/dist/FilePicker.svelte +1 -1
  2. package/dist/FilePicker.svelte.d.ts +1 -1
  3. package/dist/bands/Bands.svelte +137 -71
  4. package/dist/bands/Bands.svelte.d.ts +3 -1
  5. package/dist/bands/BandsAndDos.svelte +12 -3
  6. package/dist/bands/BandsAndDos.svelte.d.ts +3 -2
  7. package/dist/bands/BrillouinBandsDos.svelte +122 -0
  8. package/dist/bands/BrillouinBandsDos.svelte.d.ts +19 -0
  9. package/dist/bands/Dos.svelte +86 -42
  10. package/dist/bands/Dos.svelte.d.ts +5 -3
  11. package/dist/bands/helpers.d.ts +11 -7
  12. package/dist/bands/helpers.js +109 -8
  13. package/dist/bands/index.d.ts +3 -2
  14. package/dist/bands/index.js +3 -2
  15. package/dist/bands/types.d.ts +7 -1
  16. package/dist/brillouin/BrillouinZone.svelte +12 -5
  17. package/dist/brillouin/BrillouinZone.svelte.d.ts +13 -3
  18. package/dist/brillouin/BrillouinZoneInfoPane.svelte +2 -2
  19. package/dist/brillouin/BrillouinZoneScene.svelte +89 -31
  20. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +10 -1
  21. package/dist/brillouin/compute.js +6 -6
  22. package/dist/colors/index.d.ts +1 -0
  23. package/dist/colors/index.js +18 -0
  24. package/dist/composition/BarChart.svelte +3 -1
  25. package/dist/composition/BarChart.svelte.d.ts +4 -1
  26. package/dist/composition/BubbleChart.svelte +6 -5
  27. package/dist/composition/BubbleChart.svelte.d.ts +4 -1
  28. package/dist/composition/Formula.svelte +181 -0
  29. package/dist/composition/Formula.svelte.d.ts +18 -0
  30. package/dist/composition/PieChart.svelte +3 -1
  31. package/dist/composition/PieChart.svelte.d.ts +4 -1
  32. package/dist/composition/index.d.ts +2 -1
  33. package/dist/composition/index.js +1 -0
  34. package/dist/composition/parse.d.ts +15 -1
  35. package/dist/composition/parse.js +98 -2
  36. package/dist/constants.d.ts +2 -0
  37. package/dist/constants.js +17 -0
  38. package/dist/coordination/CoordinationBarPlot.svelte +76 -66
  39. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  40. package/dist/coordination/calc-coordination.d.ts +2 -2
  41. package/dist/coordination/calc-coordination.js +11 -24
  42. package/dist/element/ElementTile.svelte +7 -8
  43. package/dist/feedback/CopyFeedback.svelte +42 -0
  44. package/dist/feedback/CopyFeedback.svelte.d.ts +10 -0
  45. package/dist/feedback/DragOverlay.svelte +34 -0
  46. package/dist/feedback/DragOverlay.svelte.d.ts +7 -0
  47. package/dist/feedback/StatusMessage.svelte +69 -0
  48. package/dist/feedback/StatusMessage.svelte.d.ts +9 -0
  49. package/dist/feedback/index.d.ts +4 -0
  50. package/dist/feedback/index.js +4 -0
  51. package/dist/icons.d.ts +4 -0
  52. package/dist/icons.js +4 -0
  53. package/dist/index.d.ts +5 -8
  54. package/dist/index.js +5 -15
  55. package/dist/io/export.js +16 -16
  56. package/dist/labels.js +1 -2
  57. package/dist/{InfoCard.svelte → layout/InfoCard.svelte} +1 -1
  58. package/dist/{Nav.svelte → layout/Nav.svelte} +1 -1
  59. package/dist/{Nav.svelte.d.ts → layout/Nav.svelte.d.ts} +1 -1
  60. package/dist/{SettingsSection.svelte → layout/SettingsSection.svelte} +2 -2
  61. package/dist/{SettingsSection.svelte.d.ts → layout/SettingsSection.svelte.d.ts} +7 -1
  62. package/dist/layout/index.d.ts +3 -0
  63. package/dist/layout/index.js +3 -0
  64. package/dist/{ContextMenu.svelte → overlays/ContextMenu.svelte} +1 -1
  65. package/dist/{DraggablePane.svelte → overlays/DraggablePane.svelte} +2 -2
  66. package/dist/{DraggablePane.svelte.d.ts → overlays/DraggablePane.svelte.d.ts} +9 -2
  67. package/dist/overlays/index.d.ts +2 -0
  68. package/dist/overlays/index.js +2 -0
  69. package/dist/periodic-table/PeriodicTable.svelte +2 -3
  70. package/dist/periodic-table/PropertySelect.svelte.d.ts +1 -1
  71. package/dist/phase-diagram/PhaseDiagram2D.svelte +211 -269
  72. package/dist/phase-diagram/PhaseDiagram2D.svelte.d.ts +2 -2
  73. package/dist/phase-diagram/PhaseDiagram3D.svelte +57 -146
  74. package/dist/phase-diagram/PhaseDiagram3D.svelte.d.ts +4 -2
  75. package/dist/phase-diagram/PhaseDiagram4D.svelte +52 -135
  76. package/dist/phase-diagram/PhaseDiagram4D.svelte.d.ts +6 -2
  77. package/dist/phase-diagram/PhaseDiagramControls.svelte +83 -81
  78. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +2 -3
  79. package/dist/phase-diagram/PhaseDiagramInfoPane.svelte +1 -1
  80. package/dist/phase-diagram/PhaseDiagramInfoPane.svelte.d.ts +1 -1
  81. package/dist/phase-diagram/PhaseDiagramStats.svelte +7 -8
  82. package/dist/phase-diagram/PhaseEntryTooltip.svelte +43 -0
  83. package/dist/phase-diagram/PhaseEntryTooltip.svelte.d.ts +7 -0
  84. package/dist/phase-diagram/StructurePopup.svelte +0 -10
  85. package/dist/phase-diagram/helpers.d.ts +18 -1
  86. package/dist/phase-diagram/helpers.js +73 -0
  87. package/dist/phase-diagram/index.d.ts +4 -0
  88. package/dist/phase-diagram/thermodynamics.js +6 -8
  89. package/dist/phase-diagram/types.d.ts +9 -2
  90. package/dist/plot/BarPlot.svelte +62 -42
  91. package/dist/plot/BarPlot.svelte.d.ts +7 -7
  92. package/dist/plot/BarPlotControls.svelte +4 -4
  93. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  94. package/dist/plot/ElementScatter.svelte +1 -1
  95. package/dist/plot/ElementScatter.svelte.d.ts +2 -2
  96. package/dist/plot/Histogram.svelte +339 -280
  97. package/dist/plot/Histogram.svelte.d.ts +3 -10
  98. package/dist/plot/HistogramControls.svelte +5 -7
  99. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  100. package/dist/plot/PlotControls.svelte +35 -40
  101. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  102. package/dist/plot/ScatterPlot.svelte +189 -297
  103. package/dist/plot/ScatterPlot.svelte.d.ts +6 -11
  104. package/dist/plot/ScatterPlotControls.svelte +2 -4
  105. package/dist/plot/ScatterPlotControls.svelte.d.ts +2 -3
  106. package/dist/plot/SpacegroupBarPlot.svelte +17 -21
  107. package/dist/plot/data-transform.js +1 -1
  108. package/dist/plot/index.d.ts +0 -14
  109. package/dist/plot/index.js +0 -28
  110. package/dist/plot/layout.d.ts +1 -0
  111. package/dist/plot/layout.js +3 -1
  112. package/dist/plot/scales.d.ts +11 -0
  113. package/dist/plot/scales.js +40 -1
  114. package/dist/plot/types.d.ts +53 -9
  115. package/dist/plot/types.js +26 -1
  116. package/dist/plot/utils/label-placement.d.ts +40 -0
  117. package/dist/plot/utils/label-placement.js +91 -0
  118. package/dist/plot/utils/series-visibility.d.ts +7 -0
  119. package/dist/plot/utils/series-visibility.js +53 -0
  120. package/dist/rdf/RdfPlot.svelte +133 -76
  121. package/dist/rdf/RdfPlot.svelte.d.ts +7 -2
  122. package/dist/rdf/calc-rdf.js +3 -2
  123. package/dist/settings.d.ts +6 -0
  124. package/dist/settings.js +30 -6
  125. package/dist/structure/{Vector.svelte.d.ts → Arrow.svelte.d.ts} +3 -3
  126. package/dist/structure/Bond.svelte +22 -38
  127. package/dist/structure/CanvasTooltip.svelte +1 -1
  128. package/dist/structure/CanvasTooltip.svelte.d.ts +3 -1
  129. package/dist/structure/Structure.svelte +165 -9
  130. package/dist/structure/Structure.svelte.d.ts +9 -2
  131. package/dist/structure/StructureControls.svelte +80 -45
  132. package/dist/structure/StructureControls.svelte.d.ts +2 -1
  133. package/dist/structure/StructureExportPane.svelte +1 -1
  134. package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
  135. package/dist/structure/StructureInfoPane.svelte +19 -37
  136. package/dist/structure/StructureInfoPane.svelte.d.ts +3 -1
  137. package/dist/structure/StructureScene.svelte +60 -33
  138. package/dist/structure/StructureScene.svelte.d.ts +6 -1
  139. package/dist/structure/bonding.js +6 -5
  140. package/dist/structure/index.d.ts +5 -5
  141. package/dist/structure/index.js +11 -13
  142. package/dist/structure/measure.d.ts +1 -1
  143. package/dist/structure/measure.js +3 -2
  144. package/dist/structure/parse.js +24 -25
  145. package/dist/structure/pbc.js +4 -6
  146. package/dist/structure/supercell.d.ts +1 -1
  147. package/dist/structure/supercell.js +50 -52
  148. package/dist/structure/validation.d.ts +2 -0
  149. package/dist/structure/validation.js +8 -0
  150. package/dist/symmetry/SymmetryStats.svelte +157 -0
  151. package/dist/symmetry/SymmetryStats.svelte.d.ts +18 -0
  152. package/dist/symmetry/WyckoffTable.svelte +1 -1
  153. package/dist/symmetry/index.d.ts +21 -2
  154. package/dist/symmetry/index.js +39 -56
  155. package/dist/symmetry/spacegroups.d.ts +2 -2
  156. package/dist/symmetry/spacegroups.js +8 -11
  157. package/dist/trajectory/Trajectory.svelte +11 -11
  158. package/dist/trajectory/Trajectory.svelte.d.ts +3 -1
  159. package/dist/trajectory/TrajectoryExportPane.svelte +1 -1
  160. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
  161. package/dist/trajectory/TrajectoryInfoPane.svelte +8 -10
  162. package/dist/trajectory/index.d.ts +1 -1
  163. package/dist/trajectory/index.js +13 -16
  164. package/dist/trajectory/parse.js +18 -15
  165. package/dist/trajectory/plotting.js +16 -14
  166. package/dist/xrd/XrdPlot.svelte +70 -55
  167. package/dist/xrd/XrdPlot.svelte.d.ts +2 -3
  168. package/dist/xrd/calc-xrd.js +14 -27
  169. package/dist/xrd/index.d.ts +2 -2
  170. package/package.json +26 -18
  171. /package/dist/{Spinner.svelte → feedback/Spinner.svelte} +0 -0
  172. /package/dist/{Spinner.svelte.d.ts → feedback/Spinner.svelte.d.ts} +0 -0
  173. /package/dist/{InfoCard.svelte.d.ts → layout/InfoCard.svelte.d.ts} +0 -0
  174. /package/dist/{ContextMenu.svelte.d.ts → overlays/ContextMenu.svelte.d.ts} +0 -0
  175. /package/dist/structure/{Vector.svelte → Arrow.svelte} +0 -0
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">import { tooltip } from 'svelte-multiselect';
2
- let { files, active_files = [], show_category_filters = false, on_drag_start, on_drag_end, type_mapper, file_type_colors = {
2
+ let { files = [], active_files = [], show_category_filters = false, on_drag_start, on_drag_end, type_mapper, file_type_colors = {
3
3
  cif: `rgba(100, 149, 237, 0.8)`,
4
4
  xyz: `rgba(50, 205, 50, 0.8)`,
5
5
  extxyz: `rgba(50, 205, 50, 0.8)`,
@@ -1,7 +1,7 @@
1
1
  import type { FileInfo } from './';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
4
- files: FileInfo[];
4
+ files?: FileInfo[];
5
5
  active_files?: string[];
6
6
  show_category_filters?: boolean;
7
7
  on_drag_start?: (file: FileInfo, event: DragEvent) => void;
@@ -1,55 +1,49 @@
1
1
  <script lang="ts">import * as helpers from './helpers';
2
2
  import { plot_colors } from '../colors';
3
3
  import ScatterPlot from '../plot/ScatterPlot.svelte';
4
- let { band_structs, line_kwargs = {}, path_mode = `strict`, band_type = undefined, show_legend = true, x_axis = {}, y_axis = {}, ...rest } = $props();
4
+ import { SvelteMap } from 'svelte/reactivity';
5
+ let { band_structs, line_kwargs = {}, path_mode = `strict`, band_type = undefined, show_legend = true, x_axis = {}, y_axis = {}, x_positions = $bindable(undefined), reference_frequency = null, ...rest } = $props();
5
6
  // Helper function to get line styling for a band
6
7
  function get_line_style(color, is_acoustic, mode_type, frequencies, band_idx) {
7
- let stroke = color;
8
- let stroke_width = is_acoustic ? 1.5 : 1;
8
+ const defaults = { stroke: color, stroke_width: is_acoustic ? 1.5 : 1 };
9
9
  if (typeof line_kwargs === `function`) {
10
10
  const custom = line_kwargs(frequencies, band_idx);
11
11
  return {
12
- stroke: custom.stroke ?? stroke,
13
- stroke_width: custom.stroke_width ?? stroke_width,
12
+ stroke: custom.stroke ?? defaults.stroke,
13
+ stroke_width: custom.stroke_width ?? defaults.stroke_width,
14
14
  };
15
15
  }
16
16
  if (typeof line_kwargs === `object` && line_kwargs !== null) {
17
- const lk = line_kwargs;
18
- // Check for mode-specific styling
19
- if (`acoustic` in lk || `optical` in lk) {
20
- const mode_kwargs = lk[mode_type];
21
- if (mode_kwargs) {
22
- stroke = mode_kwargs.stroke ?? stroke;
23
- stroke_width = mode_kwargs.stroke_width ?? stroke_width;
24
- }
25
- }
26
- else {
27
- // Global styling for all bands
28
- stroke = lk.stroke ?? stroke;
29
- stroke_width = lk.stroke_width ?? stroke_width;
30
- }
17
+ const mode_kwargs = line_kwargs[mode_type];
18
+ const source = (mode_kwargs ?? line_kwargs);
19
+ return {
20
+ stroke: source.stroke ?? defaults.stroke,
21
+ stroke_width: source.stroke_width ?? defaults.stroke_width,
22
+ };
31
23
  }
32
- return { stroke, stroke_width };
24
+ return defaults;
33
25
  }
34
26
  // Normalize input to dict format
35
27
  let band_structs_dict = $derived.by(() => {
36
28
  if (!band_structs)
37
29
  return {};
38
- const is_single_struct = `qpoints` in band_structs && `branches` in band_structs;
39
- if (is_single_struct) {
40
- const normalized = helpers.normalize_band_structure(band_structs);
41
- return normalized ? { default: normalized } : {};
42
- }
43
- // Normalize each structure in the dict
30
+ const is_single = `qpoints` in band_structs && `branches` in band_structs;
44
31
  const result = {};
45
- for (const [key, bs] of Object.entries(band_structs)) {
46
- const normalized = helpers.normalize_band_structure(bs);
32
+ if (is_single) {
33
+ const normalized = helpers.normalize_band_structure(band_structs);
47
34
  if (normalized)
48
- result[key] = normalized;
35
+ result.default = normalized;
36
+ }
37
+ else {
38
+ for (const [key, bs] of Object.entries(band_structs)) {
39
+ const normalized = helpers.normalize_band_structure(bs);
40
+ if (normalized)
41
+ result[key] = normalized;
42
+ }
49
43
  }
50
44
  return result;
51
45
  });
52
- let detected_band_type = $derived.by(() => band_type ?? `phonon`);
46
+ let detected_band_type = $derived(band_type ?? `phonon`);
53
47
  // Determine which segments to plot based on path_mode
54
48
  let segments_to_plot = $derived.by(() => {
55
49
  const all_segments = {};
@@ -79,13 +73,14 @@ let segments_to_plot = $derived.by(() => {
79
73
  return new Set(Object.keys(all_segments));
80
74
  });
81
75
  // Map segments to x-axis positions
82
- let x_positions = $derived.by(() => {
76
+ $effect(() => {
83
77
  const positions = {};
84
78
  let current_x = 0;
85
79
  // Preserve physical path order using the first available structure
86
80
  const canonical = Object.values(band_structs_dict)[0];
87
81
  const ordered_segments = helpers.get_ordered_segments(canonical, segments_to_plot);
88
- for (const segment_key of ordered_segments) {
82
+ for (let seg_idx = 0; seg_idx < ordered_segments.length; seg_idx++) {
83
+ const segment_key = ordered_segments[seg_idx];
89
84
  if (positions[segment_key])
90
85
  continue;
91
86
  const [start_label, end_label] = segment_key.split(`_`);
@@ -97,15 +92,23 @@ let x_positions = $derived.by(() => {
97
92
  return branch_start === start_label && branch_end === end_label;
98
93
  });
99
94
  if (matching_branch) {
100
- const segment_len = bs.distance[matching_branch.end_index] -
101
- bs.distance[matching_branch.start_index];
102
- positions[segment_key] = [current_x, current_x + segment_len];
103
- current_x += segment_len;
95
+ // Check if this is a discontinuity: consecutive indices mean no path between points
96
+ const is_discontinuity = matching_branch.end_index - matching_branch.start_index === 1;
97
+ if (is_discontinuity) {
98
+ // Place at same x position as current, no advancement
99
+ positions[segment_key] = [current_x, current_x];
100
+ }
101
+ else {
102
+ const segment_len = bs.distance[matching_branch.end_index] -
103
+ bs.distance[matching_branch.start_index];
104
+ positions[segment_key] = [current_x, current_x + segment_len];
105
+ current_x += segment_len;
106
+ }
104
107
  break;
105
108
  }
106
109
  }
107
110
  }
108
- return positions;
111
+ x_positions = positions;
109
112
  });
110
113
  // Convert band structures to scatter plot series
111
114
  let series_data = $derived.by(() => {
@@ -124,14 +127,18 @@ let series_data = $derived.by(() => {
124
127
  const segment_key = helpers.get_segment_key(start_label, end_label);
125
128
  if (!segments_to_plot.has(segment_key))
126
129
  continue;
127
- const [x_start, x_end] = x_positions[segment_key] || [0, 1];
130
+ // Skip discontinuous segments (consecutive labeled points)
131
+ const is_discontinuity = branch.end_index - branch.start_index === 1;
132
+ if (is_discontinuity)
133
+ continue;
134
+ const [x_start, x_end] = x_positions?.[segment_key] || [0, 1];
128
135
  // Scale distances for this segment
129
136
  const segment_distances = bs.distance.slice(start_idx, end_idx);
130
137
  const dist_min = segment_distances[0];
131
138
  const dist_range = segment_distances[segment_distances.length - 1] - dist_min;
132
139
  const scaled_distances = dist_range === 0
133
140
  ? segment_distances.map(() => (x_start + x_end) / 2)
134
- : segment_distances.map((d) => x_start + ((d - dist_min) / dist_range) * (x_end - x_start));
141
+ : segment_distances.map((dist) => x_start + ((dist - dist_min) / dist_range) * (x_end - x_start));
135
142
  // Create series for each band
136
143
  for (let band_idx = 0; band_idx < bs.nb_bands; band_idx++) {
137
144
  const frequencies = bs.bands[band_idx].slice(start_idx, end_idx);
@@ -145,6 +152,7 @@ let series_data = $derived.by(() => {
145
152
  markers: `line`,
146
153
  label: structure_label,
147
154
  line_style,
155
+ metadata: { band_idx },
148
156
  });
149
157
  }
150
158
  }
@@ -153,31 +161,62 @@ let series_data = $derived.by(() => {
153
161
  });
154
162
  // Get x-axis tick positions with custom labels for symmetry points
155
163
  let x_axis_ticks = $derived.by(() => {
156
- const tick_labels = {};
157
- const sorted_positions = Object.entries(x_positions).sort(([, [a]], [, [b]]) => a - b);
158
- for (const [segment_key, [x_start, x_end]] of sorted_positions) {
159
- const [start_label, end_label] = segment_key.split(`_`);
160
- // Add start label if not already present
161
- if (!(x_start in tick_labels)) {
162
- const pretty_start = start_label !== `null`
163
- ? helpers.pretty_sym_point(start_label)
164
- : ``;
165
- if (pretty_start)
166
- tick_labels[x_start] = pretty_start;
167
- }
168
- // Add end label
169
- const pretty_end = end_label !== `null`
170
- ? helpers.pretty_sym_point(end_label)
164
+ const tick_map = new SvelteMap();
165
+ Object.entries(x_positions ?? {})
166
+ .sort(([, [a]], [, [b]]) => a - b)
167
+ .forEach(([segment_key, [x_start, x_end]]) => {
168
+ const [start_lbl, end_lbl] = segment_key.split(`_`);
169
+ const pretty_start = start_lbl !== `null`
170
+ ? helpers.pretty_sym_point(start_lbl)
171
171
  : ``;
172
- if (pretty_end)
173
- tick_labels[x_end] = pretty_end;
174
- }
175
- return tick_labels;
172
+ const pretty_end = end_lbl !== `null` ? helpers.pretty_sym_point(end_lbl) : ``;
173
+ // Check if this is a discontinuity (zero-length segment)
174
+ const is_discontinuity = Math.abs(x_end - x_start) < 1e-6;
175
+ if (is_discontinuity && pretty_start && pretty_end) {
176
+ // Combine labels at discontinuity points
177
+ if (!tick_map.has(x_start))
178
+ tick_map.set(x_start, []);
179
+ const labels = tick_map.get(x_start);
180
+ if (!labels.includes(pretty_start))
181
+ labels.push(pretty_start);
182
+ if (!labels.includes(pretty_end))
183
+ labels.push(pretty_end);
184
+ }
185
+ else {
186
+ // Normal segment with distinct start/end
187
+ if (pretty_start) {
188
+ if (!tick_map.has(x_start))
189
+ tick_map.set(x_start, []);
190
+ const labels = tick_map.get(x_start);
191
+ if (!labels.includes(pretty_start))
192
+ labels.push(pretty_start);
193
+ }
194
+ if (pretty_end) {
195
+ if (!tick_map.has(x_end))
196
+ tick_map.set(x_end, []);
197
+ const labels = tick_map.get(x_end);
198
+ if (!labels.includes(pretty_end))
199
+ labels.push(pretty_end);
200
+ }
201
+ }
202
+ });
203
+ // Merge labels at same position with pipe separator
204
+ return Object.fromEntries(Array.from(tick_map.entries()).map(([pos, labels]) => [
205
+ pos,
206
+ labels.join(` | `),
207
+ ]));
176
208
  });
177
209
  let x_range = $derived.by(() => {
178
- const all_x = Object.values(x_positions).flat().sort();
179
- return [all_x.at(0) ?? 0, all_x.at(-1) ?? 1];
210
+ const flat = Object.values(x_positions ?? {}).flat();
211
+ return [flat[0] ?? 0, flat.at(-1) ?? 1];
212
+ });
213
+ let final_y_axis = $derived({
214
+ label: detected_band_type === `phonon` ? `Frequency (THz)` : `Energy (eV)`,
215
+ format: `.2f`,
216
+ label_shift: { y: 15 },
217
+ ...y_axis,
180
218
  });
219
+ let display = $state({ x_grid: false, y_grid: true, y_zero_line: true });
181
220
  </script>
182
221
 
183
222
  <ScatterPlot
@@ -186,22 +225,35 @@ let x_range = $derived.by(() => {
186
225
  label: `Wave Vector`,
187
226
  ticks: Object.keys(x_axis_ticks).length > 0 ? x_axis_ticks : undefined,
188
227
  format: ``,
189
- range: x_range, // Explicitly set range to disable padding
228
+ range: x_range,
190
229
  ...x_axis,
191
230
  }}
192
- y_axis={{
193
- label: detected_band_type === `phonon` ? `Frequency (THz)` : `Energy (eV)`,
194
- format: `.2f`,
195
- ...y_axis,
196
- }}
197
- display={{ x_grid: false, y_grid: true, y_zero_line: true }}
231
+ y_axis={final_y_axis}
232
+ bind:display
198
233
  legend={show_legend && Object.keys(band_structs_dict).length > 1 ? {} : null}
234
+ hover_config={{ threshold_px: 50 }}
199
235
  {...rest}
200
236
  >
201
- {#snippet user_content({ height, x_scale_fn, pad })}
202
- <!-- Vertical lines at high-symmetry points -->
203
- {@const tick_positions = Object.keys(x_axis_ticks).map(Number).sort((a, b) => a - b)}
204
- {#each tick_positions as x_pos (x_pos)}
237
+ {#snippet tooltip({ x, y_formatted, label, metadata })}
238
+ {@const y_label_full = final_y_axis.label ?? ``}
239
+ {@const [, y_label, y_unit] = y_label_full.match(/^(.+?)\s*\(([^)]+)\)$/) ??
240
+ [, y_label_full, ``]}
241
+ {@const segment = Object.entries(x_positions ?? {}).find(([, [start, end]]) =>
242
+ x >= start && x <= end
243
+ )}
244
+ {@const path = segment?.[0].split(`_`).map((lbl) =>
245
+ lbl !== `null` ? helpers.pretty_sym_point(lbl) : ``
246
+ ).filter(Boolean).join(` → `) || null}
247
+ {@const band_idx = metadata?.band_idx}
248
+ {@const num_structs = Object.keys(band_structs_dict).length}
249
+ {#if num_structs > 1 && label}<strong>{label}</strong><br />{/if}
250
+ {y_label || `Value`}: {y_formatted}{y_unit ? ` ${y_unit}` : ``}<br />
251
+ {#if path}Path: {path}<br />{/if}
252
+ {#if typeof band_idx === `number`}Band: {band_idx + 1}{/if}
253
+ {/snippet}
254
+
255
+ {#snippet user_content({ height, x_scale_fn, y_scale_fn, pad })}
256
+ {#each Object.keys(x_axis_ticks).map(Number) as x_pos (x_pos)}
205
257
  <line
206
258
  x1={x_scale_fn(x_pos)}
207
259
  x2={x_scale_fn(x_pos)}
@@ -212,5 +264,19 @@ let x_range = $derived.by(() => {
212
264
  opacity="var(--bands-symmetry-line-opacity, 0.5)"
213
265
  />
214
266
  {/each}
267
+ {#if reference_frequency !== null}
268
+ {@const y_pos = y_scale_fn(reference_frequency)}
269
+ {@const x_end = x_scale_fn(Object.values(x_positions ?? {}).flat().at(-1) ?? 1)}
270
+ <line
271
+ x1={pad.l}
272
+ x2={x_end}
273
+ y1={y_pos}
274
+ y2={y_pos}
275
+ stroke="var(--bands-reference-line-color, light-dark(#d48860, #c47850))"
276
+ stroke-width="var(--bands-reference-line-width, 1)"
277
+ stroke-dasharray="var(--bands-reference-line-dash, 4,3)"
278
+ opacity="var(--bands-reference-line-opacity, 0.5)"
279
+ />
280
+ {/if}
215
281
  {/snippet}
216
282
  </ScatterPlot>
@@ -10,7 +10,9 @@ type $$ComponentProps = ComponentProps<typeof ScatterPlot> & {
10
10
  path_mode?: PathMode;
11
11
  band_type?: BandStructureType;
12
12
  show_legend?: boolean;
13
+ x_positions?: Record<string, [number, number]>;
14
+ reference_frequency?: number | null;
13
15
  };
14
- declare const Bands: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ declare const Bands: import("svelte").Component<$$ComponentProps, {}, "x_positions">;
15
17
  type Bands = ReturnType<typeof Bands>;
16
18
  export default Bands;
@@ -1,17 +1,24 @@
1
1
  <script lang="ts">import Bands from './Bands.svelte';
2
2
  import Dos from './Dos.svelte';
3
- let { band_structs, doses, bands_props = {}, dos_props = {}, shared_y_axis = true, ...rest } = $props();
3
+ let { band_structs, doses, bands_props = {}, dos_props = {}, shared_y_axis = true, children, ...rest } = $props();
4
4
  // Shared y-axis configuration - use single object when shared_y_axis is true
5
5
  let shared_y_axis_obj = $state({});
6
6
  let bands_y_axis = $derived(shared_y_axis ? shared_y_axis_obj : {});
7
7
  let dos_y_axis = $derived(shared_y_axis ? shared_y_axis_obj : {});
8
- let grid_style = $derived(`display: grid; grid-template-columns: 1fr 200px; gap: 0; ${rest.style ?? ``}`);
8
+ // Track hovered frequency from DOS to show reference line in Bands
9
+ let hovered_frequency = $state(null);
9
10
  </script>
10
11
 
11
- <div {...rest} class="bands-and-dos {rest.class ?? ``}" style={grid_style}>
12
+ <div
13
+ {...rest}
14
+ class="bands-and-dos {rest.class ?? ``}"
15
+ style={`display: grid; grid-template-columns: 1fr 200px; gap: 0;` + (rest.style ?? ``)}
16
+ >
17
+ {@render children?.({ hovered_frequency })}
12
18
  <Bands
13
19
  {band_structs}
14
20
  y_axis={bands_y_axis}
21
+ reference_frequency={hovered_frequency}
15
22
  {...bands_props}
16
23
  padding={{ r: 15 }}
17
24
  />
@@ -20,6 +27,8 @@ let grid_style = $derived(`display: grid; grid-template-columns: 1fr 200px; gap:
20
27
  {doses}
21
28
  orientation="horizontal"
22
29
  y_axis={{ ...dos_y_axis, label: `` }}
30
+ bind:hovered_frequency
31
+ reference_frequency={hovered_frequency}
23
32
  padding={{ l: 15 }}
24
33
  {...dos_props}
25
34
  />
@@ -1,8 +1,8 @@
1
- import type { ComponentProps } from 'svelte';
1
+ import type { ComponentProps, Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  import Bands from './Bands.svelte';
4
4
  import Dos from './Dos.svelte';
5
- import type { BaseBandStructure, Dos as DosData } from './types';
5
+ import type { BaseBandStructure, DosData, HoveredData } from './types';
6
6
  type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
7
7
  band_structs: BaseBandStructure | Record<string, BaseBandStructure>;
8
8
  doses: DosData | Record<string, DosData>;
@@ -10,6 +10,7 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
10
10
  dos_props?: Partial<ComponentProps<typeof Dos>>;
11
11
  shared_y_axis?: boolean;
12
12
  class?: string;
13
+ children?: Snippet<[HoveredData]>;
13
14
  };
14
15
  declare const BandsAndDos: import("svelte").Component<$$ComponentProps, {}, "">;
15
16
  type BandsAndDos = ReturnType<typeof BandsAndDos>;
@@ -0,0 +1,122 @@
1
+ <script lang="ts">import { BrillouinZone, reciprocal_lattice } from '../brillouin';
2
+ import Bands from './Bands.svelte';
3
+ import Dos from './Dos.svelte';
4
+ import * as helpers from './helpers';
5
+ let { structure, band_structs, doses, bands_props = {}, dos_props = {}, bz_props = {}, children, ...rest } = $props();
6
+ let first_band_struct = $derived(`qpoints` in band_structs
7
+ ? band_structs
8
+ : band_structs[Object.keys(band_structs)[0]]);
9
+ // Convert fractional k-point coordinates to Cartesian reciprocal space
10
+ // using the structure's reciprocal lattice (consistent with BZ computation)
11
+ let k_path_points = $derived.by(() => {
12
+ if (!first_band_struct?.qpoints || !structure?.lattice?.matrix)
13
+ return [];
14
+ const k_lattice = reciprocal_lattice(structure.lattice.matrix);
15
+ return helpers.extract_k_path_points(first_band_struct, k_lattice);
16
+ });
17
+ let hovered_band_point = $state(null);
18
+ let bands_x_positions = $state({});
19
+ let hovered_qpoint_index = $derived(hovered_band_point && first_band_struct &&
20
+ Object.keys(bands_x_positions).length > 0
21
+ ? helpers.find_qpoint_at_rescaled_x(first_band_struct, hovered_band_point.x, bands_x_positions)
22
+ : null);
23
+ let hovered_k_point = $derived(hovered_qpoint_index !== null
24
+ ? k_path_points[hovered_qpoint_index]
25
+ : null);
26
+ const [desktop_width, tablet_width] = [1200, 600];
27
+ let clientWidth = $state(desktop_width);
28
+ let is_desktop = $derived(clientWidth >= desktop_width);
29
+ let is_mobile = $derived(clientWidth < tablet_width);
30
+ let screen_class = $derived(clientWidth >= desktop_width
31
+ ? `desktop`
32
+ : clientWidth >= tablet_width
33
+ ? `tablet`
34
+ : `phone`);
35
+ let y_axis_dos = $derived({
36
+ ...(is_desktop ? { label: `` } : {}),
37
+ ...dos_props.y_axis,
38
+ });
39
+ // Track hovered frequency from DOS to show reference line in Bands
40
+ let hovered_frequency = $state(null);
41
+ </script>
42
+
43
+ <div
44
+ {...rest}
45
+ class="bands-dos-brillouin {screen_class} {rest.class ?? ``}"
46
+ bind:clientWidth
47
+ >
48
+ {@render children?.({ hovered_frequency, hovered_band_point, hovered_qpoint_index })}
49
+ <Bands
50
+ style="grid-area: bands; min-width: 0; min-height: 0; overflow: hidden"
51
+ {band_structs}
52
+ padding={{ r: is_desktop ? 10 : 5, ...bands_props.padding }}
53
+ bind:x_positions={bands_x_positions}
54
+ reference_frequency={hovered_frequency}
55
+ on_point_hover={(event) => {
56
+ hovered_band_point = event?.point ?? null
57
+ bands_props.on_point_hover?.(event)
58
+ }}
59
+ {...bands_props}
60
+ />
61
+
62
+ <BrillouinZone
63
+ style="grid-area: bz; min-width: 0; min-height: 0; overflow: hidden; height: 100%"
64
+ {structure}
65
+ {k_path_points}
66
+ k_path_labels={first_band_struct?.qpoints?.flatMap((q, idx) =>
67
+ k_path_points[idx]
68
+ ? [{
69
+ position: k_path_points[idx],
70
+ label: q.label ? helpers.pretty_sym_point(q.label) : null,
71
+ }]
72
+ : []
73
+ ) ?? []}
74
+ {hovered_k_point}
75
+ {hovered_qpoint_index}
76
+ {...bz_props}
77
+ />
78
+
79
+ <Dos
80
+ style="grid-area: dos; min-width: 0; min-height: 0; overflow: hidden"
81
+ {doses}
82
+ orientation={is_desktop ? `horizontal` : `vertical`}
83
+ x_axis={{ ticks: 4 }}
84
+ y_axis={y_axis_dos}
85
+ bind:hovered_frequency
86
+ reference_frequency={hovered_frequency}
87
+ padding={{
88
+ l: is_desktop ? 20 : undefined,
89
+ r: is_mobile ? 0 : undefined,
90
+ ...dos_props.padding,
91
+ }}
92
+ {...dos_props}
93
+ />
94
+ </div>
95
+
96
+ <style>
97
+ .bands-dos-brillouin {
98
+ width: var(--bz-bands-dos-width, 100%);
99
+ height: var(--bz-bands-dos-height, 600px);
100
+ min-height: var(--bz-bands-dos-min-height, 400px);
101
+ display: grid;
102
+ gap: var(--bz-bands-dos-gap, 1em);
103
+ }
104
+ .bands-dos-brillouin.desktop {
105
+ /* layout: BZ | bands | DOS side by side */
106
+ grid-template-columns: 30% 55% 15%;
107
+ grid-template-areas: 'bz bands dos';
108
+ }
109
+ .bands-dos-brillouin.tablet {
110
+ /* layout: bands on top, BZ and DOS below */
111
+ grid-template-columns: 40% 60%;
112
+ grid-template-rows: 50% 50%;
113
+ grid-template-areas:
114
+ 'bands bands'
115
+ 'bz dos';
116
+ }
117
+ .bands-dos-brillouin.phone {
118
+ /* layout: all stacked vertically */
119
+ grid-template-columns: 1fr;
120
+ grid-template-areas: 'bands' 'dos' 'bz';
121
+ }
122
+ </style>
@@ -0,0 +1,19 @@
1
+ import { BrillouinZone } from '../brillouin';
2
+ import type { PymatgenStructure } from '../structure';
3
+ import type { ComponentProps, Snippet } from 'svelte';
4
+ import type { HTMLAttributes } from 'svelte/elements';
5
+ import Bands from './Bands.svelte';
6
+ import Dos from './Dos.svelte';
7
+ import type { BaseBandStructure, DosData, HoveredData } from './types';
8
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
9
+ structure: PymatgenStructure;
10
+ band_structs: BaseBandStructure | Record<string, BaseBandStructure>;
11
+ doses: DosData | Record<string, DosData>;
12
+ bands_props?: Partial<ComponentProps<typeof Bands>>;
13
+ dos_props?: Partial<ComponentProps<typeof Dos>>;
14
+ bz_props?: Partial<ComponentProps<typeof BrillouinZone>>;
15
+ children?: Snippet<[HoveredData]>;
16
+ };
17
+ declare const BrillouinBandsDos: import("svelte").Component<$$ComponentProps, {}, "">;
18
+ type BrillouinBandsDos = ReturnType<typeof BrillouinBandsDos>;
19
+ export default BrillouinBandsDos;