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
@@ -44,7 +44,7 @@ export function handle_legend_double_click(series, idx, prev_visibility) {
44
44
  const check_series = prev_visibility ? series.slice(0, prev_visibility.length) : series;
45
45
  const is_isolated = check_series.every((srs, srs_idx) => {
46
46
  const in_group = label ? srs.label === label : srs_idx === idx;
47
- return in_group ? srs.visible ?? true : !(srs.visible ?? true);
47
+ return in_group ? (srs.visible ?? true) : !(srs.visible ?? true);
48
48
  });
49
49
  // Restore from isolation
50
50
  if (is_isolated && prev_visibility) {
@@ -56,8 +56,7 @@ export function handle_legend_double_click(series, idx, prev_visibility) {
56
56
  };
57
57
  }
58
58
  // Isolate series
59
- const new_prev = prev_visibility ??
60
- (current.filter(Boolean).length > 1 ? [...current] : null);
59
+ const new_prev = prev_visibility ?? (current.filter(Boolean).length > 1 ? [...current] : null);
61
60
  return {
62
61
  series: series.map((srs, srs_idx) => {
63
62
  const in_group = label ? srs.label === label : srs_idx === idx;
@@ -1,113 +1,165 @@
1
- <script lang="ts">import { PLOT_COLORS } from '../colors';
2
- import { get_electro_neg_formula } from '../composition';
3
- import { StatusMessage } from '../feedback';
4
- import { create_file_drop_handler } from '../io';
5
- import { ScatterPlot } from '../plot';
6
- import { parse_any_structure } from '../structure/parse';
7
- import { is_crystal } from '../structure/validation';
8
- import { calculate_all_pair_rdfs, calculate_rdf } from './index';
9
- let { patterns, structures, mode = `element_pairs`, show_reference_line = true, x_axis = {}, y_axis = {}, cutoff = 15, n_bins = 75, pbc = [true, true, true], enable_drop = false, on_file_drop, loading = $bindable(false), error_msg = $bindable(), children, drag_dropped = $bindable([]), dragging = $bindable(false), ...rest } = $props();
10
- function format_structure_label(struct, label_base) {
11
- const formula = get_electro_neg_formula(struct);
12
- return formula && label_base ? `${formula}: ${label_base}` : formula || label_base;
13
- }
14
- const compute_and_add = (content, filename) => {
1
+ <script lang="ts">
2
+ import { PLOT_COLORS } from '../colors'
3
+ import { get_electro_neg_formula } from '../composition'
4
+ import { StatusMessage } from '../feedback'
5
+ import { create_file_drop_handler } from '../io'
6
+ import type { DataSeries } from '../plot'
7
+ import { ScatterPlot } from '../plot'
8
+ import type { Crystal, Pbc } from '../structure'
9
+ import { parse_any_structure } from '../structure/parse'
10
+ import { is_crystal } from '../structure/validation'
11
+ import type { ComponentProps, Snippet } from 'svelte'
12
+ import { calculate_all_pair_rdfs, calculate_rdf, type RdfEntry } from './index'
13
+
14
+ let {
15
+ patterns,
16
+ structures,
17
+ mode = `element_pairs`,
18
+ show_reference_line = true,
19
+ x_axis = {},
20
+ y_axis = {},
21
+ cutoff = 15,
22
+ n_bins = 75,
23
+ pbc = [true, true, true],
24
+ enable_drop = false,
25
+ on_file_drop,
26
+ loading = $bindable(false),
27
+ error_msg = $bindable(),
28
+ children,
29
+ drag_dropped = $bindable([]),
30
+ dragging = $bindable(false),
31
+ ...rest
32
+ }: {
33
+ patterns?: RdfEntry | RdfEntry[]
34
+ structures?: Crystal | Crystal[] | Record<string, Crystal>
35
+ mode?: `element_pairs` | `full`
36
+ show_reference_line?: boolean
37
+ x_axis?: ComponentProps<typeof ScatterPlot>[`x_axis`]
38
+ y_axis?: ComponentProps<typeof ScatterPlot>[`y_axis`]
39
+ cutoff?: number
40
+ n_bins?: number
41
+ pbc?: Pbc
42
+ enable_drop?: boolean
43
+ on_file_drop?: (content: string | ArrayBuffer, filename: string) => void
44
+ loading?: boolean
45
+ error_msg?: string
46
+ children?: Snippet<[{ drag_dropped: Crystal[] }]>
47
+ drag_dropped?: Crystal[]
48
+ dragging?: boolean
49
+ } & ComponentProps<typeof ScatterPlot> = $props()
50
+
51
+ function format_structure_label(struct: Crystal, label_base: string): string {
52
+ const formula = get_electro_neg_formula(struct)
53
+ return formula && label_base ? `${formula}: ${label_base}` : formula || label_base
54
+ }
55
+
56
+ const compute_and_add = (content: string | ArrayBuffer, filename: string) => {
15
57
  try {
16
- const text = content instanceof ArrayBuffer
17
- ? new TextDecoder().decode(content)
18
- : content;
19
- const parsed_struct = parse_any_structure(text, filename);
20
- if (is_crystal(parsed_struct)) {
21
- drag_dropped = [...drag_dropped, parsed_struct];
22
- }
23
- else {
24
- error_msg = `Crystal has no lattice or sites; cannot compute RDF`;
25
- }
58
+ const text = content instanceof ArrayBuffer
59
+ ? new TextDecoder().decode(content)
60
+ : content
61
+ const parsed_struct = parse_any_structure(text, filename)
62
+ if (is_crystal(parsed_struct)) {
63
+ drag_dropped = [...drag_dropped, parsed_struct]
64
+ } else {
65
+ error_msg = `Crystal has no lattice or sites; cannot compute RDF`
66
+ }
67
+ } catch (exc) {
68
+ error_msg = `Failed to process structure: ${
69
+ exc instanceof Error ? exc.message : String(exc)
70
+ }`
26
71
  }
27
- catch (exc) {
28
- error_msg = `Failed to process structure: ${exc instanceof Error ? exc.message : String(exc)}`;
29
- }
30
- };
31
- const handle_drop = create_file_drop_handler({
72
+ }
73
+
74
+ const handle_drop = create_file_drop_handler({
32
75
  allow: () => enable_drop,
33
- on_drop: (content, filename) => (on_file_drop || compute_and_add)(content, filename),
76
+ on_drop: (content, filename) =>
77
+ (on_file_drop || compute_and_add)(content, filename),
34
78
  on_error: (msg) => {
35
- error_msg = msg;
79
+ error_msg = msg
36
80
  },
37
81
  set_loading: (val) => {
38
- loading = val;
39
- if (val)
40
- [error_msg, dragging] = [undefined, false];
82
+ loading = val
83
+ if (val) [error_msg, dragging] = [undefined, false]
41
84
  },
42
- });
43
- function handle_dragover(ev) {
44
- ev.preventDefault();
45
- if (ev.dataTransfer)
46
- ev.dataTransfer.dropEffect = `copy`;
47
- dragging = true;
48
- }
49
- const entries = $derived.by(() => {
50
- const result = [];
85
+ })
86
+
87
+ function handle_dragover(ev: DragEvent) {
88
+ ev.preventDefault()
89
+ if (ev.dataTransfer) ev.dataTransfer.dropEffect = `copy`
90
+ dragging = true
91
+ }
92
+
93
+ const entries = $derived.by(() => {
94
+ const result: RdfEntry[] = []
95
+
51
96
  // Add patterns
52
97
  if (patterns) {
53
- if (Array.isArray(patterns))
54
- result.push(...patterns);
55
- else
56
- result.push(patterns);
98
+ if (Array.isArray(patterns)) result.push(...patterns)
99
+ else result.push(patterns)
57
100
  }
101
+
58
102
  // Add structures
59
- const struct_list = [];
103
+ const struct_list: { struct: Crystal; label: string }[] = []
60
104
  if (structures) {
61
- if (Array.isArray(structures)) {
62
- structures.forEach((struct, idx) => struct_list.push({
63
- struct,
64
- label: format_structure_label(struct, `Crystal ${idx + 1}`),
65
- }));
66
- }
67
- else if (is_crystal(structures)) {
68
- struct_list.push({
69
- struct: structures,
70
- label: format_structure_label(structures, ``),
71
- });
72
- }
73
- else {
74
- Object.entries(structures).forEach(([label, struct]) => struct_list.push({ struct, label: format_structure_label(struct, label) }));
75
- }
105
+ if (Array.isArray(structures)) {
106
+ structures.forEach((struct, idx) =>
107
+ struct_list.push({
108
+ struct,
109
+ label: format_structure_label(struct, `Crystal ${idx + 1}`),
110
+ })
111
+ )
112
+ } else if (is_crystal(structures)) {
113
+ struct_list.push({
114
+ struct: structures,
115
+ label: format_structure_label(structures, ``),
116
+ })
117
+ } else {
118
+ Object.entries(structures).forEach(([label, struct]) =>
119
+ struct_list.push({ struct, label: format_structure_label(struct, label) })
120
+ )
121
+ }
76
122
  }
77
- drag_dropped.forEach((struct, idx) => struct_list.push({
123
+ drag_dropped.forEach((struct, idx) =>
124
+ struct_list.push({
78
125
  struct,
79
126
  label: format_structure_label(struct, `Dropped ${idx + 1}`),
80
- }));
127
+ })
128
+ )
129
+
81
130
  for (const { struct, label } of struct_list) {
82
- if (mode === `element_pairs`) {
83
- const pairs = calculate_all_pair_rdfs(struct, { cutoff, n_bins, pbc });
84
- result.push(...pairs.map((p) => ({
85
- label: p.element_pair ? `${p.element_pair[0]}-${p.element_pair[1]}` : label,
86
- legend_group: label, // Group by structure name for multi-structure plots
87
- pattern: p,
88
- })));
89
- }
90
- else {
91
- const pattern = calculate_rdf(struct, { cutoff, n_bins, pbc });
92
- result.push({ label, pattern });
93
- }
131
+ if (mode === `element_pairs`) {
132
+ const pairs = calculate_all_pair_rdfs(struct, { cutoff, n_bins, pbc })
133
+ result.push(...pairs.map((p) => ({
134
+ label: p.element_pair ? `${p.element_pair[0]}-${p.element_pair[1]}` : label,
135
+ legend_group: label, // Group by structure name for multi-structure plots
136
+ pattern: p,
137
+ })))
138
+ } else {
139
+ const pattern = calculate_rdf(struct, { cutoff, n_bins, pbc })
140
+ result.push({ label, pattern })
141
+ }
94
142
  }
95
- return result;
96
- });
97
- const max_r = $derived(Math.max(...entries.flatMap((e) => e.pattern.r), 0));
98
- const max_g = $derived(Math.max(1.2, ...entries.flatMap((e) => e.pattern.g_r)));
99
- const series = $derived(entries.map((ent, idx) => ({
100
- x: ent.pattern.r,
101
- y: ent.pattern.g_r,
102
- label: ent.label,
103
- legend_group: ent.legend_group,
104
- visible: mode === `element_pairs` ? idx < 3 : true,
105
- markers: `line`,
106
- line_style: {
143
+ return result
144
+ })
145
+
146
+ const max_r = $derived(Math.max(...entries.flatMap((e) => e.pattern.r), 0))
147
+ const max_g = $derived(Math.max(1.2, ...entries.flatMap((e) => e.pattern.g_r)))
148
+
149
+ const series = $derived<DataSeries[]>(
150
+ entries.map((ent, idx) => ({
151
+ x: ent.pattern.r,
152
+ y: ent.pattern.g_r,
153
+ label: ent.label,
154
+ legend_group: ent.legend_group,
155
+ visible: mode === `element_pairs` ? idx < 3 : true,
156
+ markers: `line` as const,
157
+ line_style: {
107
158
  stroke: ent.color ?? PLOT_COLORS[idx % PLOT_COLORS.length],
108
159
  stroke_width: 2,
109
- },
110
- })));
160
+ },
161
+ })),
162
+ )
111
163
  </script>
112
164
 
113
165
  <StatusMessage bind:message={error_msg} type="error" dismissible />
@@ -1,4 +1,4 @@
1
- import { calc_lattice_params, euclidean_dist, matrix_inverse_3x3, pbc_dist, } from '../math';
1
+ import { calc_lattice_params, create_lattice_converters, euclidean_dist, pbc_dist, } from '../math';
2
2
  import { make_supercell } from '../structure/supercell';
3
3
  // Calculate radial distribution function
4
4
  export function calculate_rdf(structure, options = {}) {
@@ -45,18 +45,17 @@ export function calculate_rdf(structure, options = {}) {
45
45
  }
46
46
  // Calculate distances and bin them with occupancy weighting
47
47
  const use_pbc = pbc.some((flag) => flag);
48
- const lattice_inv = use_pbc ? matrix_inverse_3x3(lattice) : undefined;
48
+ const converters = use_pbc ? create_lattice_converters(lattice) : undefined;
49
49
  for (const center of centers) {
50
50
  for (const neighbor of neighbors) {
51
51
  if (center === neighbor)
52
52
  continue;
53
53
  const dist = use_pbc
54
- ? pbc_dist(center.xyz, neighbor.xyz, lattice, lattice_inv, pbc)
54
+ ? pbc_dist(center.xyz, neighbor.xyz, lattice, converters, pbc)
55
55
  : euclidean_dist(center.xyz, neighbor.xyz);
56
56
  if (dist > 0 && dist < cutoff) {
57
57
  // Weight by product of occupancies for the species pair
58
- const weight = get_occu(center, center_species) *
59
- get_occu(neighbor, neighbor_species);
58
+ const weight = get_occu(center, center_species) * get_occu(neighbor, neighbor_species);
60
59
  g_r[Math.min(Math.floor(dist / bin_size), n_bins - 1)] += weight;
61
60
  }
62
61
  }
@@ -0,0 +1,4 @@
1
+ export declare function sanitize_html(html: unknown): string;
2
+ export declare const sanitize_formula: (formula: string, use_subscripts?: boolean) => string;
3
+ export declare const sanitize_svg: (html: string) => string;
4
+ export declare const sanitize_icon_svg: (html: string) => string;
@@ -0,0 +1,107 @@
1
+ import DOMPurify from 'dompurify';
2
+ import { format_formula_html } from './phase-diagram/utils';
3
+ // SSR: provide a DOM for DOMPurify when no browser window exists (e.g. during vite build)
4
+ let ssr_window;
5
+ if (typeof globalThis.window === `undefined`) {
6
+ try {
7
+ const { Window } = await import(`happy-dom`);
8
+ ssr_window = new Window();
9
+ }
10
+ catch {
11
+ // happy-dom not available at runtime — get_purify() will fall back to pass-through
12
+ }
13
+ }
14
+ const SAFE_TAGS = [`a`, `b`, `i`, `em`, `strong`, `sub`, `sup`, `br`, `span`, `code`, `small`];
15
+ const SAFE_ATTRS = [`style`, `class`, `title`, `href`, `target`, `rel`];
16
+ // only allow safe CSS properties for text formatting
17
+ const SAFE_STYLE_RE = /^\s*(color|font-weight|font-style|font-size|text-decoration|vertical-align)\s*:/;
18
+ // Add a token to a space-separated string if not already present
19
+ const ensure_token = (value, token) => {
20
+ const tokens = new Set(value.split(/\s+/).filter(Boolean));
21
+ tokens.add(token);
22
+ return [...tokens].join(` `);
23
+ };
24
+ // undefined = not yet checked, null = no DOM available, instance = ready
25
+ let purify;
26
+ function get_purify() {
27
+ if (purify !== undefined)
28
+ return purify;
29
+ const instance = ssr_window
30
+ ? DOMPurify(ssr_window)
31
+ : DOMPurify();
32
+ if (typeof instance.sanitize !== `function`) {
33
+ purify = null;
34
+ return null;
35
+ }
36
+ purify = instance;
37
+ instance.addHook(`uponSanitizeAttribute`, (node, data) => {
38
+ if (data.attrName === `style`) {
39
+ const rules = data.attrValue.split(`;`).filter((rule) => SAFE_STYLE_RE.test(rule));
40
+ if (rules.length === 0)
41
+ data.keepAttr = false;
42
+ else
43
+ data.attrValue = rules.join(`;`);
44
+ }
45
+ // force rel="noopener" on links to prevent window.opener attacks
46
+ if (data.attrName === `href`) {
47
+ node.setAttribute(`rel`, ensure_token(node.getAttribute(`rel`) ?? ``, `noopener`));
48
+ }
49
+ if (data.attrName === `rel`) {
50
+ data.attrValue = ensure_token(data.attrValue, `noopener`);
51
+ }
52
+ });
53
+ return instance;
54
+ }
55
+ // Wrap in <svg>, sanitize with allowlist, then unwrap. Required because DOMPurify
56
+ // needs the <svg> parent to parse children in the SVG namespace.
57
+ function sanitize_svg_content(html, allowed_tags, allowed_attrs) {
58
+ const dp = get_purify();
59
+ if (!dp)
60
+ return html;
61
+ const wrapped = dp.sanitize(`<svg>${html}</svg>`, {
62
+ ALLOWED_TAGS: [...allowed_tags, `svg`],
63
+ ALLOWED_ATTR: allowed_attrs,
64
+ });
65
+ const open_end = wrapped.indexOf(`>`);
66
+ const close_start = wrapped.lastIndexOf(`</svg>`);
67
+ if (open_end < 0 || close_start < 0)
68
+ return wrapped;
69
+ return wrapped.slice(open_end + 1, close_start);
70
+ }
71
+ // Sanitize HTML string, allowing only safe formatting tags and links.
72
+ // Two-pass: happy-dom promotes dangerous children when a non-allowed parent is
73
+ // stripped (e.g. <div><script>…</script></div> → <script>…</script>). The first
74
+ // pass explicitly removes dangerous tags so they can't survive promotion.
75
+ export function sanitize_html(html) {
76
+ const str = html == null ? `` : String(html);
77
+ const dp = get_purify();
78
+ if (!dp)
79
+ return str;
80
+ // oxfmt-ignore
81
+ const safe = dp.sanitize(str, { ADD_ATTR: [`target`], FORBID_TAGS: [
82
+ `script`, `style`, `iframe`, `object`, `embed`, `form`, `input`, `textarea`,
83
+ `select`, `button`, `meta`, `link`, `base`, `template`, `noscript`,
84
+ ] });
85
+ return dp.sanitize(safe, { ALLOWED_TAGS: SAFE_TAGS, ALLOWED_ATTR: SAFE_ATTRS });
86
+ }
87
+ // Sanitize a chemical formula with optional subscript formatting
88
+ export const sanitize_formula = (formula, use_subscripts = true) => sanitize_html(format_formula_html(formula, use_subscripts));
89
+ const SVG_TEXT_TAGS = [`tspan`, `title`];
90
+ // oxfmt-ignore
91
+ const SVG_TEXT_ATTRS = [`dx`, `dy`, `x`, `y`, `fill`, `font-size`, `font-weight`, `baseline-shift`];
92
+ // Sanitize HTML intended for SVG text contexts (tspan, title)
93
+ export const sanitize_svg = (html) => sanitize_svg_content(html, SVG_TEXT_TAGS, SVG_TEXT_ATTRS);
94
+ // oxfmt-ignore
95
+ const SVG_ICON_TAGS = [
96
+ `path`, `circle`, `rect`, `line`, `polyline`, `polygon`, `g`, `ellipse`,
97
+ `clipPath`, `defs`, `mask`, `use`, `title`,
98
+ ];
99
+ // oxfmt-ignore
100
+ const SVG_ICON_ATTRS = [
101
+ `d`, `fill`, `stroke`, `stroke-width`, `stroke-linecap`, `stroke-linejoin`,
102
+ `cx`, `cy`, `r`, `rx`, `ry`, `x`, `y`, `x1`, `y1`, `x2`, `y2`,
103
+ `width`, `height`, `viewBox`, `points`, `transform`, `opacity`,
104
+ `clip-path`, `clip-rule`, `fill-rule`, `id`, `class`,
105
+ ];
106
+ // Sanitize inline SVG markup (path, circle, rect, etc.) for icon rendering
107
+ export const sanitize_icon_svg = (html) => sanitize_svg_content(html, SVG_ICON_TAGS, SVG_ICON_ATTRS);
@@ -17,6 +17,13 @@ export interface SettingType<T = unknown> {
17
17
  export declare const SHOW_BONDS_OPTIONS: readonly ["never", "always", "crystals", "molecules"];
18
18
  export type ShowBonds = (typeof SHOW_BONDS_OPTIONS)[number];
19
19
  export type CameraProjection = `perspective` | `orthographic`;
20
+ export declare const VECTOR_COLOR_MODES: readonly ["auto", "element", "spin_direction", "magnitude", "uniform"];
21
+ export type VectorColorMode = (typeof VECTOR_COLOR_MODES)[number];
22
+ export type VectorLayerConfig = {
23
+ visible: boolean;
24
+ color: string | null;
25
+ scale: number | null;
26
+ };
20
27
  export declare const ATOM_COLOR_MODE_OPTIONS: readonly ["element", "coordination", "wyckoff", "custom"];
21
28
  export type AtomColorMode = (typeof ATOM_COLOR_MODE_OPTIONS)[number];
22
29
  type DisplayConfigType = {
@@ -122,12 +129,17 @@ export interface SettingsConfig {
122
129
  site_label_offset: SettingType<Vec3>;
123
130
  ambient_light: SettingType<number>;
124
131
  directional_light: SettingType<number>;
125
- show_force_vectors: SettingType<boolean>;
126
- force_scale: SettingType<number>;
127
- force_color: SettingType<string>;
128
- force_shaft_radius: SettingType<number>;
129
- force_arrow_head_radius: SettingType<number>;
130
- force_arrow_head_length: SettingType<number>;
132
+ vector_configs: SettingType<Record<string, VectorLayerConfig>>;
133
+ vector_scale: SettingType<number>;
134
+ vector_color: SettingType<string>;
135
+ vector_color_mode: SettingType<VectorColorMode>;
136
+ vector_color_scale: SettingType<D3InterpolateName>;
137
+ vector_normalize: SettingType<boolean>;
138
+ vector_uniform_thickness: SettingType<boolean>;
139
+ vector_origin_gap: SettingType<number>;
140
+ vector_shaft_radius: SettingType<number>;
141
+ vector_arrow_head_radius: SettingType<number>;
142
+ vector_arrow_head_length: SettingType<number>;
131
143
  show_cell: SettingType<boolean>;
132
144
  show_cell_vectors: SettingType<boolean>;
133
145
  cell_edge_opacity: SettingType<number>;
package/dist/settings.js CHANGED
@@ -3,6 +3,13 @@
3
3
  import { symbol_names } from './labels';
4
4
  import { merge_nested } from './utils';
5
5
  export const SHOW_BONDS_OPTIONS = [`never`, `always`, `crystals`, `molecules`];
6
+ export const VECTOR_COLOR_MODES = [
7
+ `auto`,
8
+ `element`,
9
+ `spin_direction`,
10
+ `magnitude`,
11
+ `uniform`,
12
+ ];
6
13
  export const ATOM_COLOR_MODE_OPTIONS = [
7
14
  `element`,
8
15
  `coordination`,
@@ -74,8 +81,8 @@ export const SETTINGS_CONFIG = {
74
81
  structure: {
75
82
  // Atoms & Bonds
76
83
  atom_radius: {
77
- value: 1.0,
78
- description: `Radius multiplier for atoms (1.0 = standard atomic radii)`,
84
+ value: 0.7,
85
+ description: `Radius multiplier for atoms (0.7 = standard atomic radii)`,
79
86
  minimum: 0.1,
80
87
  maximum: 3.0,
81
88
  },
@@ -267,36 +274,58 @@ export const SETTINGS_CONFIG = {
267
274
  minimum: 0,
268
275
  maximum: 4,
269
276
  },
270
- // Forces & Lattice
271
- show_force_vectors: {
272
- value: false,
273
- description: `Display force vectors on atoms`,
277
+ // Site Vectors (force, magmom, spin) & Lattice
278
+ vector_configs: {
279
+ value: {},
280
+ description: `Per-key configuration for site vector layers. Keys map to site property names (e.g. force, magmom, force_DFT). Auto-populated when a structure with vector data loads.`,
274
281
  },
275
- force_scale: {
282
+ vector_scale: {
276
283
  value: 1.0,
277
- description: `Scale factor for force vector arrows`,
284
+ description: `Scale factor for site vector arrows`,
278
285
  minimum: 0.1,
279
286
  maximum: 10.0,
280
287
  },
281
- force_color: {
288
+ vector_color: {
282
289
  value: `#ff0000`,
283
- description: `Color for force vectors`,
290
+ description: `Color for site vector arrows (used in uniform mode and as fallback)`,
291
+ },
292
+ vector_color_mode: {
293
+ value: `auto`,
294
+ description: `How to color arrows. auto = element for force, spin-direction for magmom/spin. element = majority species color. spin_direction = red/blue by z-component. magnitude = continuous color scale by vector length. uniform = single color (vector_color).`,
295
+ },
296
+ vector_color_scale: {
297
+ value: `interpolateViridis`,
298
+ description: `D3 color scale for magnitude coloring mode`,
299
+ },
300
+ vector_normalize: {
301
+ value: false,
302
+ description: `Show all arrows at the same length (direction only). Useful for spin/magmom visualization where orientation matters but magnitude does not.`,
303
+ },
304
+ vector_uniform_thickness: {
305
+ value: false,
306
+ description: `Use the same shaft and head size for all arrows regardless of length. When off (default), thickness scales with arrow length.`,
307
+ },
308
+ vector_origin_gap: {
309
+ value: 0,
310
+ description: `Fraction of visual atom radius to offset each arrow origin when multiple vectors are shown per site. 0 = all from atom center, 0.5 = halfway to surface.`,
311
+ minimum: 0,
312
+ maximum: 0.5,
284
313
  },
285
- force_shaft_radius: {
314
+ vector_shaft_radius: {
286
315
  value: -0.03,
287
- description: `Radius of force vector shaft (negative = relative to length, positive = absolute)`,
316
+ description: `Radius of vector shaft (negative = relative to length, positive = absolute)`,
288
317
  minimum: -0.1,
289
318
  maximum: 0.1,
290
319
  },
291
- force_arrow_head_radius: {
320
+ vector_arrow_head_radius: {
292
321
  value: -0.06,
293
- description: `Radius of force vector arrow head (negative = relative to length, positive = absolute)`,
322
+ description: `Radius of vector arrow head (negative = relative to length, positive = absolute)`,
294
323
  minimum: -0.2,
295
324
  maximum: 0.2,
296
325
  },
297
- force_arrow_head_length: {
326
+ vector_arrow_head_length: {
298
327
  value: -0.15,
299
- description: `Length of force vector arrow head (negative = relative to length, positive = absolute)`,
328
+ description: `Length of vector arrow head (negative = relative to length, positive = absolute)`,
300
329
  minimum: -0.5,
301
330
  maximum: 0.5,
302
331
  },
@@ -785,6 +814,7 @@ export const SETTINGS_CONFIG = {
785
814
  },
786
815
  },
787
816
  convex_hull: {
817
+ // Convex hull defaults (binary/ternary/quaternary)
788
818
  binary: {
789
819
  camera_zoom: {
790
820
  value: 1.0,