matterviz 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/dist/EmptyState.svelte +10 -2
  2. package/dist/FilePicker.svelte +123 -82
  3. package/dist/Icon.svelte +18 -12
  4. package/dist/MillerIndexInput.svelte +27 -21
  5. package/dist/api/optimade.js +6 -6
  6. package/dist/app.css +216 -207
  7. package/dist/brillouin/BrillouinZone.svelte +292 -149
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +69 -42
  11. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  12. package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte +275 -163
  14. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  15. package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
  16. package/dist/brillouin/compute.js +11 -6
  17. package/dist/chempot-diagram/ChemPotDiagram.svelte +162 -27
  18. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +451 -281
  19. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2148 -1642
  20. package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -5
  21. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  22. package/dist/chempot-diagram/async-compute.svelte.js +77 -0
  23. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  24. package/dist/chempot-diagram/chempot-worker.js +11 -0
  25. package/dist/chempot-diagram/color.js +1 -2
  26. package/dist/chempot-diagram/compute.d.ts +10 -0
  27. package/dist/chempot-diagram/compute.js +250 -88
  28. package/dist/chempot-diagram/index.d.ts +2 -1
  29. package/dist/chempot-diagram/index.js +2 -1
  30. package/dist/chempot-diagram/temperature.js +8 -9
  31. package/dist/chempot-diagram/types.d.ts +3 -0
  32. package/dist/chempot-diagram/types.js +1 -0
  33. package/dist/colors/index.d.ts +1 -1
  34. package/dist/colors/index.js +5 -3
  35. package/dist/composition/BarChart.svelte +128 -55
  36. package/dist/composition/BubbleChart.svelte +102 -49
  37. package/dist/composition/Composition.svelte +100 -79
  38. package/dist/composition/Formula.svelte +108 -62
  39. package/dist/composition/FormulaFilter.svelte +665 -537
  40. package/dist/composition/PieChart.svelte +183 -108
  41. package/dist/composition/format.d.ts +5 -0
  42. package/dist/composition/format.js +20 -3
  43. package/dist/composition/parse.js +14 -9
  44. package/dist/convex-hull/ConvexHull.svelte +93 -40
  45. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
  46. package/dist/convex-hull/ConvexHull2D.svelte +549 -360
  47. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  48. package/dist/convex-hull/ConvexHull3D.svelte +1296 -827
  49. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  50. package/dist/convex-hull/ConvexHull4D.svelte +1004 -688
  51. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  52. package/dist/convex-hull/ConvexHullControls.svelte +115 -28
  53. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
  54. package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
  55. package/dist/convex-hull/ConvexHullStats.svelte +425 -328
  56. package/dist/convex-hull/ConvexHullTooltip.svelte +40 -16
  57. package/dist/convex-hull/GasPressureControls.svelte +104 -61
  58. package/dist/convex-hull/StructurePopup.svelte +25 -4
  59. package/dist/convex-hull/TemperatureSlider.svelte +45 -25
  60. package/dist/convex-hull/barycentric-coords.js +13 -7
  61. package/dist/convex-hull/demo-temperature.js +8 -4
  62. package/dist/convex-hull/gas-thermodynamics.js +17 -12
  63. package/dist/convex-hull/helpers.d.ts +9 -0
  64. package/dist/convex-hull/helpers.js +77 -34
  65. package/dist/convex-hull/thermodynamics.js +61 -56
  66. package/dist/convex-hull/types.d.ts +9 -14
  67. package/dist/convex-hull/types.js +0 -17
  68. package/dist/coordination/CoordinationBarPlot.svelte +227 -154
  69. package/dist/element/BohrAtom.svelte +55 -12
  70. package/dist/element/ElementHeading.svelte +7 -2
  71. package/dist/element/ElementPhoto.svelte +15 -9
  72. package/dist/element/ElementStats.svelte +10 -4
  73. package/dist/element/ElementTile.svelte +137 -73
  74. package/dist/element/Nucleus.svelte +39 -11
  75. package/dist/feedback/ClickFeedback.svelte +16 -5
  76. package/dist/feedback/DragOverlay.svelte +10 -2
  77. package/dist/feedback/Spinner.svelte +4 -2
  78. package/dist/feedback/StatusMessage.svelte +8 -2
  79. package/dist/fermi-surface/FermiSlice.svelte +118 -88
  80. package/dist/fermi-surface/FermiSurface.svelte +328 -187
  81. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  82. package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
  83. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  84. package/dist/fermi-surface/FermiSurfaceScene.svelte +535 -342
  85. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  86. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
  87. package/dist/fermi-surface/compute.js +16 -20
  88. package/dist/fermi-surface/parse.js +24 -14
  89. package/dist/fermi-surface/symmetry.js +2 -7
  90. package/dist/fermi-surface/types.d.ts +3 -5
  91. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1019 -765
  92. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +1 -1
  93. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +76 -22
  94. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +2 -3
  95. package/dist/icons.js +47 -0
  96. package/dist/index.d.ts +2 -1
  97. package/dist/index.js +2 -1
  98. package/dist/io/decompress.js +1 -1
  99. package/dist/io/export.d.ts +3 -0
  100. package/dist/io/export.js +129 -143
  101. package/dist/io/is-binary.js +2 -3
  102. package/dist/io/url-drop.js +1 -2
  103. package/dist/isosurface/Isosurface.svelte +202 -148
  104. package/dist/isosurface/IsosurfaceControls.svelte +46 -28
  105. package/dist/isosurface/parse.js +34 -29
  106. package/dist/isosurface/slice.js +5 -10
  107. package/dist/isosurface/types.d.ts +2 -1
  108. package/dist/isosurface/types.js +61 -12
  109. package/dist/labels.js +11 -8
  110. package/dist/layout/FullscreenToggle.svelte +11 -2
  111. package/dist/layout/InfoCard.svelte +38 -6
  112. package/dist/layout/InfoTag.svelte +63 -32
  113. package/dist/layout/PropertyFilter.svelte +82 -37
  114. package/dist/layout/SettingsSection.svelte +85 -55
  115. package/dist/layout/SubpageGrid.svelte +10 -2
  116. package/dist/layout/json-tree/JsonNode.svelte +183 -138
  117. package/dist/layout/json-tree/JsonTree.svelte +499 -413
  118. package/dist/layout/json-tree/JsonValue.svelte +127 -99
  119. package/dist/layout/json-tree/utils.js +4 -2
  120. package/dist/marching-cubes.js +25 -2
  121. package/dist/math.d.ts +13 -17
  122. package/dist/math.js +133 -67
  123. package/dist/overlays/ContextMenu.svelte +65 -40
  124. package/dist/overlays/DraggablePane.svelte +211 -139
  125. package/dist/periodic-table/PeriodicTable.svelte +278 -145
  126. package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
  127. package/dist/periodic-table/PropertySelect.svelte +25 -7
  128. package/dist/periodic-table/TableInset.svelte +8 -3
  129. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +446 -309
  130. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  131. package/dist/phase-diagram/PhaseDiagramControls.svelte +102 -43
  132. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
  133. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +63 -40
  134. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +71 -28
  135. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +1 -1
  136. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +158 -101
  137. package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
  138. package/dist/phase-diagram/build-diagram.js +9 -9
  139. package/dist/phase-diagram/colors.js +1 -3
  140. package/dist/phase-diagram/parse.js +10 -9
  141. package/dist/phase-diagram/svg-to-diagram.js +53 -49
  142. package/dist/phase-diagram/utils.d.ts +1 -0
  143. package/dist/phase-diagram/utils.js +80 -25
  144. package/dist/plot/AxisLabel.svelte +28 -3
  145. package/dist/plot/BarPlot.svelte +1182 -734
  146. package/dist/plot/BarPlot.svelte.d.ts +2 -2
  147. package/dist/plot/BarPlotControls.svelte +31 -5
  148. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  149. package/dist/plot/ColorBar.svelte +479 -329
  150. package/dist/plot/ColorScaleSelect.svelte +27 -6
  151. package/dist/plot/ElementScatter.svelte +36 -15
  152. package/dist/plot/FillArea.svelte +152 -95
  153. package/dist/plot/Histogram.svelte +934 -571
  154. package/dist/plot/Histogram.svelte.d.ts +1 -1
  155. package/dist/plot/HistogramControls.svelte +53 -9
  156. package/dist/plot/HistogramControls.svelte.d.ts +1 -1
  157. package/dist/plot/InteractiveAxisLabel.svelte +34 -11
  158. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  159. package/dist/plot/Line.svelte +63 -28
  160. package/dist/plot/PlotControls.svelte +157 -114
  161. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  162. package/dist/plot/PlotLegend.svelte +174 -91
  163. package/dist/plot/PlotTooltip.svelte +45 -6
  164. package/dist/plot/PortalSelect.svelte +175 -147
  165. package/dist/plot/ReferenceLine.svelte +76 -22
  166. package/dist/plot/ReferenceLine3D.svelte +132 -107
  167. package/dist/plot/ReferencePlane.svelte +146 -121
  168. package/dist/plot/ScatterPlot.svelte +1681 -1091
  169. package/dist/plot/ScatterPlot.svelte.d.ts +2 -2
  170. package/dist/plot/ScatterPlot3D.svelte +256 -131
  171. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  172. package/dist/plot/ScatterPlot3DControls.svelte +113 -63
  173. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
  174. package/dist/plot/ScatterPlot3DScene.svelte +608 -403
  175. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  176. package/dist/plot/ScatterPlotControls.svelte +65 -25
  177. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  178. package/dist/plot/ScatterPoint.svelte +98 -26
  179. package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
  180. package/dist/plot/SpacegroupBarPlot.svelte +142 -85
  181. package/dist/plot/Surface3D.svelte +159 -108
  182. package/dist/plot/ZeroLines.svelte +55 -3
  183. package/dist/plot/ZoomRect.svelte +4 -2
  184. package/dist/plot/axis-utils.js +1 -3
  185. package/dist/plot/data-cleaning.js +12 -28
  186. package/dist/plot/data-transform.js +2 -1
  187. package/dist/plot/fill-utils.js +2 -0
  188. package/dist/plot/layout.d.ts +4 -1
  189. package/dist/plot/layout.js +33 -14
  190. package/dist/plot/reference-line.d.ts +2 -2
  191. package/dist/plot/reference-line.js +7 -5
  192. package/dist/plot/scales.js +24 -36
  193. package/dist/plot/types.d.ts +11 -23
  194. package/dist/plot/types.js +6 -11
  195. package/dist/plot/utils/label-placement.d.ts +32 -15
  196. package/dist/plot/utils/label-placement.js +227 -66
  197. package/dist/plot/utils/series-visibility.js +2 -3
  198. package/dist/rdf/RdfPlot.svelte +143 -91
  199. package/dist/rdf/calc-rdf.js +4 -5
  200. package/dist/sanitize.d.ts +4 -0
  201. package/dist/sanitize.js +107 -0
  202. package/dist/settings.d.ts +18 -6
  203. package/dist/settings.js +46 -16
  204. package/dist/spectral/Bands.svelte +632 -453
  205. package/dist/spectral/BandsAndDos.svelte +90 -49
  206. package/dist/spectral/BrillouinBandsDos.svelte +151 -93
  207. package/dist/spectral/Dos.svelte +389 -258
  208. package/dist/spectral/helpers.js +55 -43
  209. package/dist/state.svelte.d.ts +1 -1
  210. package/dist/state.svelte.js +3 -2
  211. package/dist/structure/Arrow.svelte +59 -20
  212. package/dist/structure/AtomLegend.svelte +215 -134
  213. package/dist/structure/Bond.svelte +73 -47
  214. package/dist/structure/CanvasTooltip.svelte +10 -2
  215. package/dist/structure/CellSelect.svelte +72 -45
  216. package/dist/structure/Cylinder.svelte +33 -17
  217. package/dist/structure/Lattice.svelte +88 -33
  218. package/dist/structure/Structure.svelte +1063 -797
  219. package/dist/structure/Structure.svelte.d.ts +1 -1
  220. package/dist/structure/StructureControls.svelte +349 -118
  221. package/dist/structure/StructureExportPane.svelte +124 -89
  222. package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
  223. package/dist/structure/StructureInfoPane.svelte +304 -237
  224. package/dist/structure/StructureScene.svelte +879 -443
  225. package/dist/structure/StructureScene.svelte.d.ts +15 -7
  226. package/dist/structure/atom-properties.js +8 -8
  227. package/dist/structure/bonding.js +6 -7
  228. package/dist/structure/export.js +14 -29
  229. package/dist/structure/ferrox-wasm.js +1 -1
  230. package/dist/structure/index.d.ts +13 -3
  231. package/dist/structure/index.js +83 -23
  232. package/dist/structure/measure.d.ts +2 -2
  233. package/dist/structure/measure.js +4 -44
  234. package/dist/structure/parse.js +113 -141
  235. package/dist/structure/partial-occupancy.js +7 -10
  236. package/dist/structure/pbc.d.ts +1 -0
  237. package/dist/structure/pbc.js +16 -6
  238. package/dist/structure/supercell.d.ts +2 -2
  239. package/dist/structure/supercell.js +12 -22
  240. package/dist/structure/validation.js +1 -2
  241. package/dist/symmetry/SymmetryStats.svelte +84 -41
  242. package/dist/symmetry/WyckoffTable.svelte +26 -6
  243. package/dist/symmetry/cell-transform.js +5 -3
  244. package/dist/symmetry/index.js +8 -7
  245. package/dist/symmetry/spacegroups.js +148 -148
  246. package/dist/table/HeatmapTable.svelte +790 -554
  247. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  248. package/dist/table/ToggleMenu.svelte +125 -92
  249. package/dist/table/index.js +2 -4
  250. package/dist/theme/ThemeControl.svelte +21 -12
  251. package/dist/time.js +4 -1
  252. package/dist/tooltip/TooltipContent.svelte +33 -8
  253. package/dist/trajectory/Trajectory.svelte +758 -558
  254. package/dist/trajectory/TrajectoryError.svelte +14 -3
  255. package/dist/trajectory/TrajectoryExportPane.svelte +137 -83
  256. package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
  257. package/dist/trajectory/extract.js +10 -26
  258. package/dist/trajectory/format-detect.js +5 -5
  259. package/dist/trajectory/frame-reader.d.ts +1 -1
  260. package/dist/trajectory/frame-reader.js +5 -12
  261. package/dist/trajectory/helpers.d.ts +0 -1
  262. package/dist/trajectory/helpers.js +2 -17
  263. package/dist/trajectory/index.js +14 -12
  264. package/dist/trajectory/parse/ase.js +5 -4
  265. package/dist/trajectory/parse/hdf5.js +26 -18
  266. package/dist/trajectory/parse/index.js +13 -18
  267. package/dist/trajectory/parse/lammps.js +17 -7
  268. package/dist/trajectory/parse/vasp.js +5 -2
  269. package/dist/trajectory/parse/xyz.js +8 -7
  270. package/dist/trajectory/plotting.js +13 -8
  271. package/dist/utils.d.ts +1 -0
  272. package/dist/utils.js +13 -0
  273. package/dist/xrd/XrdPlot.svelte +337 -247
  274. package/dist/xrd/broadening.js +14 -9
  275. package/dist/xrd/calc-xrd.js +12 -18
  276. package/dist/xrd/parse.d.ts +1 -1
  277. package/dist/xrd/parse.js +17 -17
  278. package/package.json +99 -103
  279. package/readme.md +1 -1
  280. /package/dist/theme/{themes.js → themes.mjs} +0 -0
@@ -1,185 +1,265 @@
1
- <script lang="ts">import { add_alpha, PLOT_COLORS } from '../colors';
2
- import EmptyState from '../EmptyState.svelte';
3
- import StatusMessage from '../feedback/StatusMessage.svelte';
4
- import { decompress_data_binary, decompress_file, detect_compression_format, handle_url_drop, } from '../io';
5
- import { format_value } from '../labels';
6
- import SettingsSection from '../layout/SettingsSection.svelte';
7
- import { BarPlot, ScatterPlot } from '../plot';
8
- import { add_xrd_pattern } from './calc-xrd';
9
- import { compute_broadened_pattern, DEFAULT_BROADENING } from './broadening';
10
- function is_xrd_pattern(obj) {
11
- if (!obj || typeof obj !== `object`)
12
- return false;
13
- const pattern_obj = obj;
14
- const x = pattern_obj.x;
15
- const y = pattern_obj.y;
16
- return (Array.isArray(x) &&
17
- Array.isArray(y) &&
18
- x.length === y.length);
19
- }
20
- function format_hkl(hkl, format) {
1
+ <script lang="ts">
2
+ import { add_alpha, PLOT_COLORS } from '../colors'
3
+ import EmptyState from '../EmptyState.svelte'
4
+ import StatusMessage from '../feedback/StatusMessage.svelte'
5
+ import {
6
+ decompress_data_binary,
7
+ decompress_file,
8
+ detect_compression_format,
9
+ handle_url_drop,
10
+ } from '../io'
11
+ import { format_value } from '../labels'
12
+ import { sanitize_html } from '../sanitize'
13
+ import SettingsSection from '../layout/SettingsSection.svelte'
14
+ import type { Vec2 } from '../math'
15
+ import type {
16
+ AxisConfig,
17
+ BarHandlerProps,
18
+ BarSeries,
19
+ ControlsConfig,
20
+ DataSeries,
21
+ ScatterHandlerProps,
22
+ } from '../plot'
23
+ import { BarPlot, ScatterPlot } from '../plot'
24
+ import { add_xrd_pattern } from './calc-xrd'
25
+ import type { ComponentProps } from 'svelte'
26
+ import type { BroadeningParams } from './broadening'
27
+ import { compute_broadened_pattern, DEFAULT_BROADENING } from './broadening'
28
+ import type { Hkl, HklFormat, PatternEntry, XrdPattern } from './index'
29
+
30
+ function is_xrd_pattern(obj: unknown): obj is XrdPattern {
31
+ if (!obj || typeof obj !== `object`) return false
32
+ const pattern_obj = obj as { x?: unknown; y?: unknown }
33
+ const x = pattern_obj.x
34
+ const y = pattern_obj.y
35
+ return (
36
+ Array.isArray(x) &&
37
+ Array.isArray(y) &&
38
+ x.length === y.length
39
+ )
40
+ }
41
+
42
+ function format_hkl(hkl: Hkl, format: HklFormat): string {
21
43
  if (format === `compact`) {
22
- // Use crystallographic overbar notation for negative indices (e.g. 1̄ instead of -1)
23
- // Note: Requires font support for Unicode combining characters (U+0305)
24
- return hkl
25
- .map((val) => {
26
- // Use combining overline character (U+0305) for negative values
27
- // Apply overbar to each digit for multi-digit numbers
28
- if (val < 0) {
29
- const digits = String(Math.abs(val));
30
- return digits
31
- .split(``)
32
- .map((digit) => `${digit}\u0305`)
33
- .join(``);
34
- }
35
- return `${val}`;
44
+ // Use crystallographic overbar notation for negative indices (e.g. 1̄ instead of -1)
45
+ // Note: Requires font support for Unicode combining characters (U+0305)
46
+ return hkl
47
+ .map((val) => {
48
+ // Use combining overline character (U+0305) for negative values
49
+ // Apply overbar to each digit for multi-digit numbers
50
+ if (val < 0) {
51
+ const digits = String(Math.abs(val))
52
+ return digits
53
+ .split(``)
54
+ .map((digit) => `${digit}\u0305`)
55
+ .join(``)
56
+ }
57
+ return `${val}`
36
58
  })
37
- .join(``);
59
+ .join(``)
38
60
  }
39
- if (format === `full`)
40
- return `(${hkl.join(`, `)})`;
41
- return ``;
42
- }
43
- let { patterns, peak_width = 0.5, annotate_peaks = 5, hkl_format = `compact`, show_angles = null, orientation = `vertical`, wavelength = null, x_axis = {}, y_axis = {}, allow_file_drop = true, on_file_drop, loading = $bindable(false), error_msg = $bindable(), broadening_enabled = $bindable(false), broadening_params = $bindable({ ...DEFAULT_BROADENING }), controls = {}, ...rest } = $props();
44
- let dragover = $state(false);
45
- let dropped_entries = $state([]);
46
- // Normalize various input shapes to a consistent array of { label, pattern, color }
47
- const pattern_entries = $derived.by(() => {
48
- if (!patterns)
49
- return [];
61
+ if (format === `full`) return `(${hkl.join(`, `)})`
62
+ return ``
63
+ }
64
+
65
+ let {
66
+ patterns,
67
+ peak_width = 0.5,
68
+ annotate_peaks = 5,
69
+ hkl_format = `compact`,
70
+ show_angles = null,
71
+ orientation = `vertical`,
72
+ wavelength = null,
73
+ x_axis = {},
74
+ y_axis = {},
75
+ allow_file_drop = true,
76
+ on_file_drop,
77
+ loading = $bindable(false),
78
+ error_msg = $bindable(),
79
+ broadening_enabled = $bindable(false),
80
+ broadening_params = $bindable({ ...DEFAULT_BROADENING }),
81
+ controls = {},
82
+ ...rest
83
+ }:
84
+ & ComponentProps<typeof BarPlot>
85
+ & ComponentProps<typeof ScatterPlot>
86
+ & {
87
+ patterns:
88
+ | XrdPattern
89
+ | Record<string, XrdPattern | { pattern: XrdPattern; color?: string }>
90
+ | PatternEntry[]
91
+ peak_width?: number
92
+ annotate_peaks?: number // int => top-k, float in (0,1) => threshold of max
93
+ hkl_format?: HklFormat
94
+ show_angles?: boolean | null
95
+ wavelength?: number | null
96
+ x_axis?: AxisConfig
97
+ y_axis?: AxisConfig
98
+ allow_file_drop?: boolean
99
+ on_file_drop?: (content: string | ArrayBuffer, filename: string) => void
100
+ loading?: boolean
101
+ error_msg?: string
102
+ broadening_enabled?: boolean
103
+ broadening_params?: BroadeningParams
104
+ controls?: ControlsConfig
105
+ } = $props()
106
+
107
+ let dragover = $state(false)
108
+ let dropped_entries = $state<PatternEntry[]>([])
109
+
110
+ // Normalize various input shapes to a consistent array of { label, pattern, color }
111
+ const pattern_entries = $derived.by<PatternEntry[]>(() => {
112
+ if (!patterns) return []
50
113
  const base_entries = Array.isArray(patterns)
51
- ? patterns
52
- : is_xrd_pattern(patterns)
53
- ? [{ label: `XRD Pattern`, pattern: patterns }]
54
- : Object.entries(patterns).map(([label, value]) => `pattern` in value
55
- ? { label, ...value }
56
- : { label, pattern: value });
114
+ ? (patterns as PatternEntry[])
115
+ : is_xrd_pattern(patterns)
116
+ ? [{ label: `XRD Pattern`, pattern: patterns as XrdPattern }]
117
+ : Object.entries(
118
+ patterns as Record<
119
+ string,
120
+ XrdPattern | { pattern: XrdPattern; color?: string }
121
+ >,
122
+ ).map(([label, value]) =>
123
+ `pattern` in value
124
+ ? { label, ...value }
125
+ : { label, pattern: value as XrdPattern }
126
+ )
57
127
  // Merge user-provided patterns with any dropped-on-the-fly entries
58
- return [...base_entries, ...dropped_entries];
59
- });
60
- // Decide default show_angles
61
- const actual_show_angles = $derived(show_angles ?? pattern_entries.length <= 2);
62
- // Compute global max intensity for normalization (as in pymatviz xrd_pattern)
63
- const global_max_intensity = $derived.by(() => {
64
- let max_val = 0;
128
+ return [...base_entries, ...dropped_entries]
129
+ })
130
+
131
+ // Decide default show_angles
132
+ const actual_show_angles = $derived(
133
+ show_angles ?? pattern_entries.length <= 2,
134
+ )
135
+
136
+ // Compute global max intensity for normalization (as in pymatviz xrd_pattern)
137
+ const global_max_intensity = $derived.by(() => {
138
+ let max_val = 0
65
139
  for (const entry of pattern_entries) {
66
- for (const y of entry.pattern.y)
67
- if (y > max_val)
68
- max_val = y;
140
+ for (const y of entry.pattern.y) if (y > max_val) max_val = y
69
141
  }
70
- return max_val || 1;
71
- });
72
- // Compute overall 2θ domain (degrees)
73
- const angle_range = $derived.by(() => {
74
- if (pattern_entries.length === 0)
75
- return [0, 90]; // Default range
76
- let min_x = Infinity;
77
- let max_x = 0;
78
- for (const entry of pattern_entries) {
79
- const entry_min = Math.min(...entry.pattern.x);
80
- const entry_max = Math.max(...entry.pattern.x);
81
- if (entry_min < min_x)
82
- min_x = entry_min;
83
- if (entry_max > max_x)
84
- max_x = entry_max;
142
+ return max_val || 1
143
+ })
144
+
145
+ // Compute overall 2θ domain (degrees)
146
+ const angle_range = $derived.by((): Vec2 => {
147
+ const valid = pattern_entries.filter((entry) => entry.pattern.x.length > 0)
148
+ if (valid.length === 0) return [0, 90]
149
+ let [min_x, max_x] = [Infinity, 0]
150
+ for (const entry of valid) {
151
+ const entry_min = Math.min(...entry.pattern.x)
152
+ const entry_max = Math.max(...entry.pattern.x)
153
+ if (entry_min < min_x) min_x = entry_min
154
+ if (entry_max > max_x) max_x = entry_max
85
155
  }
86
- // Use data min if it's significantly above 0, otherwise start at 0
87
- const x_min = min_x > 10 ? Math.floor(min_x) : 0;
88
- return [x_min, Math.ceil(max_x)];
89
- });
90
- // Scaled intensities are normalized to 0..100, add 10% top padding for peak labels
91
- const intensity_range = [0, 110];
92
- // Build BarPlot series from entries (for Discrete/Stick view)
93
- const bar_series = $derived.by(() => {
94
- if (broadening_enabled)
95
- return []; // Optimization: skip if not used
96
- const include_name = pattern_entries.length > 1;
156
+ const x_min = min_x > 10 ? Math.floor(min_x) : 0
157
+ return [x_min, Math.ceil(max_x)]
158
+ })
159
+
160
+ // Scaled intensities are normalized to 0..100, add 10% top padding for peak labels
161
+ const intensity_range: [number, number] = [0, 110]
162
+
163
+ // Build BarPlot series from entries (for Discrete/Stick view)
164
+ const bar_series = $derived.by<BarSeries[]>(() => {
165
+ if (broadening_enabled) return [] // Optimization: skip if not used
166
+
167
+ const include_name = pattern_entries.length > 1
97
168
  // Add transparency when multiple series overlap
98
- const alpha = pattern_entries.length > 1 ? 0.6 : 1;
99
- const scale = (y) => (y / global_max_intensity) * 100;
169
+ const alpha = pattern_entries.length > 1 ? 0.6 : 1
170
+ const scale = (y: number) => (y / global_max_intensity) * 100
100
171
  return pattern_entries.map((entry, entry_idx) => {
101
- const xs = entry.pattern.x;
102
- const ys = entry.pattern.y.map((val) => scale(val || 0));
103
- const metadata = [];
104
- const labels = [];
105
- // Determine which peaks to annotate
106
- const intens = ys;
107
- let selected_indices = [];
108
- if (annotate_peaks && annotate_peaks > 0) {
109
- let candidates = [];
110
- if (annotate_peaks > 0 && annotate_peaks < 1) {
111
- const thresh = annotate_peaks * 100;
112
- candidates = intens
113
- .map((y_val, idx) => ({ y_val, idx }))
114
- .filter(({ y_val }) => y_val > thresh);
115
- }
116
- else {
117
- const k = Math.min(intens.length, Math.floor(annotate_peaks));
118
- candidates = intens
119
- .map((y_val, idx) => ({ y_val, idx }))
120
- .sort((a, b) => b.y_val - a.y_val)
121
- .slice(0, k);
122
- }
123
- // Filter out overlapping labels: keep higher intensity peaks when x values are close
124
- // Min spacing as fraction of x-range to avoid overlaps (roughly 3% of range)
125
- const x_range = Math.max(...xs) - Math.min(...xs);
126
- const min_spacing = x_range * 0.03;
127
- // Sort by intensity descending so we keep highest peaks first
128
- candidates.sort((a, b) => b.y_val - a.y_val);
129
- const kept = [];
130
- for (const cand of candidates) {
131
- const cand_x = xs[cand.idx];
132
- // Check if any already-kept peak is too close
133
- const too_close = kept.some((kept_peak) => Math.abs(xs[kept_peak.idx] - cand_x) < min_spacing);
134
- if (!too_close)
135
- kept.push(cand);
136
- }
137
- selected_indices = kept.map((kept_peak) => kept_peak.idx);
172
+ const xs = entry.pattern.x
173
+ const ys = entry.pattern.y.map((val) => scale(val || 0))
174
+ const metadata: Record<string, unknown>[] = []
175
+ const labels: (string | null)[] = []
176
+
177
+ // Determine which peaks to annotate
178
+ const intens = ys
179
+ let selected_indices: number[] = []
180
+ if (annotate_peaks && annotate_peaks > 0) {
181
+ let candidates: { idx: number; y_val: number }[] = []
182
+ if (annotate_peaks > 0 && annotate_peaks < 1) {
183
+ const thresh = annotate_peaks * 100
184
+ candidates = intens
185
+ .map((y_val, idx) => ({ y_val, idx }))
186
+ .filter(({ y_val }) => y_val > thresh)
187
+ } else {
188
+ const max_peaks = Math.min(intens.length, Math.floor(annotate_peaks))
189
+ candidates = intens
190
+ .map((y_val, idx) => ({ y_val, idx }))
191
+ .sort((a, b) => b.y_val - a.y_val)
192
+ .slice(0, max_peaks)
138
193
  }
139
- for (let idx = 0; idx < xs.length; idx++) {
140
- const hkls_objs = entry.pattern.hkls?.[idx] ?? [];
141
- const hkls = hkls_objs
142
- .map((h) => (Array.isArray(h?.hkl) ? h.hkl : null))
143
- .filter((h) => Array.isArray(h) && h.length === 3);
144
- const d_hkl = entry.pattern.d_hkls?.[idx];
145
- metadata.push({ hkls, d: d_hkl, label: entry.label });
146
- if (selected_indices.includes(idx)) {
147
- const angle_text = actual_show_angles
148
- ? `${format_value(xs[idx], `.2f`)}°`
149
- : ``;
150
- const hkl_text = hkls && hkl_format
151
- ? hkls.map((h) => format_hkl(h, hkl_format)).join(`, `)
152
- : ``;
153
- // Use @ separator between hkl and angle for better clarity
154
- const separator = hkl_text && angle_text ? ` @ ` : ``;
155
- const text = [hkl_text, angle_text].filter(Boolean).join(separator);
156
- labels.push(text);
157
- }
158
- else
159
- labels.push(null);
194
+
195
+ // Filter out overlapping labels: keep higher intensity peaks when x values are close
196
+ // Min spacing as fraction of x-range to avoid overlaps (roughly 3% of range)
197
+ const x_range = Math.max(...xs) - Math.min(...xs)
198
+ const min_spacing = x_range * 0.03
199
+ // Sort by intensity descending so we keep highest peaks first
200
+ candidates.sort((a, b) => b.y_val - a.y_val)
201
+ const kept: { idx: number; y_val: number }[] = []
202
+ for (const cand of candidates) {
203
+ const cand_x = xs[cand.idx]
204
+ // Check if any already-kept peak is too close
205
+ const too_close = kept.some(
206
+ (kept_peak) => Math.abs(xs[kept_peak.idx] - cand_x) < min_spacing,
207
+ )
208
+ if (!too_close) kept.push(cand)
160
209
  }
161
- const base_color = entry.color ?? PLOT_COLORS[entry_idx % PLOT_COLORS.length];
162
- return {
163
- x: xs,
164
- y: ys,
165
- label: include_name ? entry.label : ``,
166
- color: add_alpha(base_color, alpha),
167
- bar_width: Math.max(peak_width, 0.8),
168
- visible: true,
169
- metadata,
170
- labels,
171
- };
172
- });
173
- });
174
- // Build ScatterPlot series (for Broadened Profile view)
175
- const scatter_series = $derived.by(() => {
176
- if (!broadening_enabled)
177
- return [];
178
- const include_name = pattern_entries.length > 1;
210
+ selected_indices = kept.map((kept_peak) => kept_peak.idx)
211
+ }
212
+
213
+ for (let idx = 0; idx < xs.length; idx++) {
214
+ const hkls_objs = entry.pattern.hkls?.[idx] ?? []
215
+ const hkls: Hkl[] = hkls_objs
216
+ .map((hkl_obj) => (Array.isArray(hkl_obj?.hkl) ? hkl_obj.hkl : null))
217
+ .filter((hkl_val): hkl_val is Hkl => Array.isArray(hkl_val) && hkl_val.length === 3)
218
+ const d_hkl = entry.pattern.d_hkls?.[idx]
219
+ metadata.push({ hkls, d: d_hkl, label: entry.label })
220
+
221
+ if (selected_indices.includes(idx)) {
222
+ const angle_text = actual_show_angles
223
+ ? `${format_value(xs[idx], `.2f`)}°`
224
+ : ``
225
+ const hkl_text = hkls && hkl_format
226
+ ? hkls.map((hkl_val) => format_hkl(hkl_val, hkl_format)).join(`, `)
227
+ : ``
228
+ // Use @ separator between hkl and angle for better clarity
229
+ const separator = hkl_text && angle_text ? ` @ ` : ``
230
+ const text = [hkl_text, angle_text].filter(Boolean).join(separator)
231
+ labels.push(text)
232
+ } else labels.push(null)
233
+ }
234
+
235
+ const base_color = entry.color ?? PLOT_COLORS[entry_idx % PLOT_COLORS.length]
236
+ return {
237
+ x: xs,
238
+ y: ys,
239
+ label: include_name ? entry.label : ``,
240
+ color: add_alpha(base_color, alpha),
241
+ bar_width: Math.max(peak_width, 0.8),
242
+ visible: true,
243
+ metadata,
244
+ labels,
245
+ }
246
+ })
247
+ })
248
+
249
+ // Build ScatterPlot series (for Broadened Profile view)
250
+ const scatter_series = $derived.by<DataSeries[]>(() => {
251
+ if (!broadening_enabled) return []
252
+
253
+ const include_name = pattern_entries.length > 1
179
254
  // We rescale after broadening so that max peak is 100, matching stick view scale
180
255
  return pattern_entries
181
- .map((entry, entry_idx) => {
182
- const broadened = compute_broadened_pattern(entry.pattern, broadening_params, angle_range);
256
+ .map((entry, entry_idx) => {
257
+ const broadened = compute_broadened_pattern(
258
+ entry.pattern,
259
+ broadening_params,
260
+ angle_range,
261
+ )
262
+
183
263
  // Normalize broadened profile relative to GLOBAL max intensity of stick patterns?
184
264
  // Or normalize to 100 for *this* profile?
185
265
  // Usually we want relative intensities between patterns to be preserved if possible.
@@ -187,91 +267,101 @@ const scatter_series = $derived.by(() => {
187
267
  // If we normalize each profile to 100 independently, we lose relative scaling between patterns.
188
268
  // If we normalize by global_max_intensity (from sticks), broadened peaks will be tiny.
189
269
  // Let's normalize such that the highest peak across ALL broadened patterns is 100.
270
+
190
271
  return {
191
- broadened,
192
- entry,
193
- entry_idx,
194
- };
195
- })
196
- .map(({ broadened, entry, entry_idx }, _, all_processed) => {
272
+ broadened,
273
+ entry,
274
+ entry_idx,
275
+ }
276
+ })
277
+ .map(({ broadened, entry, entry_idx }, _, all_processed) => {
197
278
  // Find global max of all broadened patterns to normalize
198
- const all_ys = all_processed.flatMap((p) => p.broadened.y);
199
- const max_y = Math.max(...all_ys, 1); // Avoid div by zero
200
- const scale = (y) => (y / max_y) * 100;
201
- const base_color = entry.color ?? PLOT_COLORS[entry_idx % PLOT_COLORS.length];
279
+ const all_ys = all_processed.flatMap((processed) => processed.broadened.y)
280
+ const max_y = Math.max(...all_ys, 1) // Avoid div by zero
281
+
282
+ const scale = (y: number) => (y / max_y) * 100
283
+ const base_color = entry.color ?? PLOT_COLORS[entry_idx % PLOT_COLORS.length]
202
284
  // Add transparency when multiple series overlap
203
- const alpha = all_processed.length > 1 ? 0.6 : 1;
285
+ const alpha = all_processed.length > 1 ? 0.6 : 1
286
+
204
287
  return {
205
- x: broadened.x,
206
- y: broadened.y.map(scale),
207
- label: include_name ? entry.label : ``,
208
- color: add_alpha(base_color, alpha),
209
- markers: `line`, // Only line for profile
210
- line_style: { stroke_width: 2 },
211
- visible: true,
212
- };
213
- });
214
- });
215
- async function handle_file_drop(event) {
216
- event.preventDefault();
217
- dragover = false;
218
- if (!allow_file_drop)
219
- return;
220
- loading = true;
221
- error_msg = undefined;
222
- const compute_and_add = async (content, filename) => {
223
- const result = await add_xrd_pattern(content, filename, wavelength);
224
- if (result.error) {
225
- error_msg = result.error;
226
- }
227
- else if (result.pattern) {
228
- dropped_entries = [result.pattern, ...dropped_entries];
229
- }
230
- };
288
+ x: broadened.x,
289
+ y: broadened.y.map(scale),
290
+ label: include_name ? entry.label : ``,
291
+ color: add_alpha(base_color, alpha),
292
+ markers: `line`, // Only line for profile
293
+ line_style: { stroke_width: 2 },
294
+ visible: true,
295
+ } as DataSeries
296
+ })
297
+ })
298
+
299
+ async function handle_file_drop(event: DragEvent) {
300
+ event.preventDefault()
301
+ dragover = false
302
+ if (!allow_file_drop) return
303
+ loading = true
304
+ error_msg = undefined
305
+
306
+ const compute_and_add = async (
307
+ content: string | ArrayBuffer,
308
+ filename: string,
309
+ ) => {
310
+ const result = await add_xrd_pattern(content, filename, wavelength)
311
+ if (result.error) {
312
+ error_msg = result.error
313
+ } else if (result.pattern) {
314
+ dropped_entries = [result.pattern, ...dropped_entries]
315
+ }
316
+ }
317
+
231
318
  try {
232
- // Handle URL-based drops
233
- const handled = await handle_url_drop(event, on_file_drop || compute_and_add).catch(() => false);
234
- if (handled)
235
- return;
236
- const file = event.dataTransfer?.files?.[0];
237
- if (file) {
238
- try {
239
- const lower_name = file.name.toLowerCase();
240
- const compression_format = detect_compression_format(lower_name);
241
- // Get base filename without compression extension
242
- const base_name = compression_format
243
- ? lower_name.replace(/\.(gz|gzip)$/i, ``)
244
- : lower_name;
245
- const base_ext = base_name.split(`.`).pop();
246
- // Handle .brml files (ZIP archives) - both plain and gzipped
247
- if (base_ext === `brml`) {
248
- let buffer = await file.arrayBuffer();
249
- // Decompress if gzipped
250
- if (compression_format === `gzip`) {
251
- buffer = await decompress_data_binary(buffer, `gzip`);
252
- }
253
- const output_name = base_name.endsWith(`.brml`)
254
- ? base_name
255
- : file.name.replace(/\.gz$/i, ``);
256
- await (on_file_drop || compute_and_add)(buffer, output_name);
257
- }
258
- else {
259
- // Text-based formats (.xy, .xye, .xrdml) - decompress_file handles .gz
260
- const { content, filename } = await decompress_file(file);
261
- if (content)
262
- await (on_file_drop || compute_and_add)(content, filename);
263
- }
264
- }
265
- catch (exc) {
266
- error_msg = `Failed to load file ${file.name}: ${exc instanceof Error ? exc.message : String(exc)}`;
319
+ // Handle URL-based drops
320
+ const handled = await handle_url_drop(
321
+ event,
322
+ on_file_drop || compute_and_add,
323
+ ).catch(() => false)
324
+ if (handled) return
325
+
326
+ const file = event.dataTransfer?.files?.[0]
327
+ if (file) {
328
+ try {
329
+ const lower_name = file.name.toLowerCase()
330
+ const compression_format = detect_compression_format(lower_name)
331
+ // Get base filename without compression extension
332
+ const base_name = compression_format
333
+ ? lower_name.replace(/\.(gz|gzip)$/i, ``)
334
+ : lower_name
335
+ const base_ext = base_name.split(`.`).pop()
336
+
337
+ // Handle .brml files (ZIP archives) - both plain and gzipped
338
+ if (base_ext === `brml`) {
339
+ let buffer = await file.arrayBuffer()
340
+ // Decompress if gzipped
341
+ if (compression_format === `gzip`) {
342
+ buffer = await decompress_data_binary(buffer, `gzip`)
267
343
  }
344
+ const output_name = base_name.endsWith(`.brml`)
345
+ ? base_name
346
+ : file.name.replace(/\.gz$/i, ``)
347
+ await (on_file_drop || compute_and_add)(buffer, output_name)
348
+ } else {
349
+ // Text-based formats (.xy, .xye, .xrdml) - decompress_file handles .gz
350
+ const { content, filename } = await decompress_file(file)
351
+ if (content) await (on_file_drop || compute_and_add)(content, filename)
352
+ }
353
+ } catch (exc) {
354
+ error_msg = `Failed to load file ${file.name}: ${
355
+ exc instanceof Error ? exc.message : String(exc)
356
+ }`
268
357
  }
358
+ }
359
+ } finally {
360
+ loading = false
269
361
  }
270
- finally {
271
- loading = false;
272
- }
273
- }
274
- const [angle_label, intensity_label] = [`2θ (degrees)`, `Intensity (a.u.)`];
362
+ }
363
+
364
+ const [angle_label, intensity_label] = [`2θ (degrees)`, `Intensity (a.u.)`]
275
365
  </script>
276
366
 
277
367
  {#snippet broadening_controls_snippet()}
@@ -363,7 +453,7 @@ const [angle_label, intensity_label] = [`2θ (degrees)`, `Intensity (a.u.)`];
363
453
  {#snippet tooltip(info: ScatterHandlerProps)}
364
454
  {@const angle_text = `${format_value(info.x, `.2f`)}°`}
365
455
  {@const intensity_text = `${format_value(info.y, `.1f`)}`}
366
- {@html info.label ?? ``}<br />
456
+ {@html sanitize_html(info.label ?? ``)}<br />
367
457
  2θ: {angle_text}<br />
368
458
  Intensity: {intensity_text}
369
459
  {/snippet}
@@ -410,7 +500,7 @@ const [angle_label, intensity_label] = [`2θ (degrees)`, `Intensity (a.u.)`];
410
500
  ? hkls.map((hkl: Hkl) => format_hkl(hkl, hkl_format)).join(`, `)
411
501
  : ``}
412
502
  {@const d_text = d != null ? `${format_value(d, `.3f`)} Å` : ``}
413
- {@html info.metadata?.label ?? ``}<br />
503
+ {@html sanitize_html(info.metadata?.label ?? ``)}<br />
414
504
  2θ: {angle_text}<br />
415
505
  Intensity: {intensity_text}
416
506
  {#if hkl_text}<br />hkl: {hkl_text}{/if}