matterviz 0.4.0 → 0.4.1

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 (326) hide show
  1. package/dist/brillouin/BrillouinZone.svelte +68 -145
  2. package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -14
  3. package/dist/brillouin/BrillouinZoneExportPane.svelte +43 -96
  4. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  5. package/dist/brillouin/BrillouinZoneInfoPane.svelte +9 -32
  6. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
  7. package/dist/brillouin/BrillouinZoneScene.svelte +49 -203
  8. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +3 -23
  9. package/dist/brillouin/ReciprocalVectors.svelte +39 -0
  10. package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
  11. package/dist/brillouin/compute.d.ts +2 -0
  12. package/dist/brillouin/compute.js +80 -77
  13. package/dist/brillouin/geometry.d.ts +8 -0
  14. package/dist/brillouin/geometry.js +57 -0
  15. package/dist/brillouin/index.d.ts +2 -0
  16. package/dist/brillouin/index.js +2 -0
  17. package/dist/brillouin/types.d.ts +2 -2
  18. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
  19. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +100 -191
  20. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
  21. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +176 -464
  22. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
  23. package/dist/chempot-diagram/color.d.ts +3 -6
  24. package/dist/chempot-diagram/color.js +5 -5
  25. package/dist/chempot-diagram/compute.d.ts +3 -3
  26. package/dist/chempot-diagram/compute.js +3 -1
  27. package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
  28. package/dist/chempot-diagram/controls-state.svelte.js +42 -0
  29. package/dist/chempot-diagram/export.d.ts +47 -0
  30. package/dist/chempot-diagram/export.js +133 -0
  31. package/dist/chempot-diagram/index.d.ts +1 -0
  32. package/dist/chempot-diagram/index.js +1 -0
  33. package/dist/chempot-diagram/pointer.d.ts +0 -10
  34. package/dist/chempot-diagram/pointer.js +4 -4
  35. package/dist/chempot-diagram/types.d.ts +3 -3
  36. package/dist/colors/index.js +2 -2
  37. package/dist/composition/FormulaFilter.svelte +6 -5
  38. package/dist/composition/PieChart.svelte +5 -5
  39. package/dist/composition/chem-sys.js +3 -2
  40. package/dist/composition/format.js +3 -2
  41. package/dist/composition/parse.d.ts +0 -1
  42. package/dist/composition/parse.js +17 -19
  43. package/dist/controls.d.ts +1 -0
  44. package/dist/controls.js +0 -1
  45. package/dist/convex-hull/ConvexHull.svelte +8 -10
  46. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
  47. package/dist/convex-hull/ConvexHull2D.svelte +94 -175
  48. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  49. package/dist/convex-hull/ConvexHull3D.svelte +176 -680
  50. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  51. package/dist/convex-hull/ConvexHull4D.svelte +180 -680
  52. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  53. package/dist/convex-hull/ConvexHullChrome.svelte +268 -0
  54. package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
  55. package/dist/convex-hull/ConvexHullControls.svelte +88 -7
  56. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
  57. package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
  58. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
  59. package/dist/convex-hull/ConvexHullStats.svelte +29 -168
  60. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
  61. package/dist/convex-hull/ConvexHullTooltip.svelte +11 -2
  62. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
  63. package/dist/convex-hull/barycentric-coords.d.ts +2 -4
  64. package/dist/convex-hull/barycentric-coords.js +6 -33
  65. package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
  66. package/dist/convex-hull/canvas-interactions.svelte.js +278 -0
  67. package/dist/convex-hull/helpers.d.ts +39 -7
  68. package/dist/convex-hull/helpers.js +154 -69
  69. package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
  70. package/dist/convex-hull/hull-state.svelte.js +124 -0
  71. package/dist/convex-hull/index.d.ts +9 -7
  72. package/dist/convex-hull/index.js +7 -2
  73. package/dist/convex-hull/thermodynamics.js +91 -920
  74. package/dist/convex-hull/types.d.ts +12 -4
  75. package/dist/convex-hull/types.js +12 -0
  76. package/dist/coordination/CoordinationBarPlot.svelte +4 -11
  77. package/dist/element/BohrAtom.svelte +2 -1
  78. package/dist/element/ElementTile.svelte.d.ts +1 -1
  79. package/dist/element/index.d.ts +4 -0
  80. package/dist/element/index.js +18 -0
  81. package/dist/feedback/DragOverlay.svelte +3 -1
  82. package/dist/feedback/DragOverlay.svelte.d.ts +1 -0
  83. package/dist/feedback/StatusMessage.svelte +13 -3
  84. package/dist/fermi-surface/FermiSurface.svelte +67 -146
  85. package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -14
  86. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  87. package/dist/fermi-surface/FermiSurfaceScene.svelte +72 -224
  88. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
  89. package/dist/fermi-surface/compute.js +11 -10
  90. package/dist/fermi-surface/export.js +4 -15
  91. package/dist/fermi-surface/index.d.ts +0 -1
  92. package/dist/fermi-surface/index.js +0 -1
  93. package/dist/fermi-surface/parse.d.ts +1 -1
  94. package/dist/fermi-surface/parse.js +64 -75
  95. package/dist/fermi-surface/types.d.ts +2 -2
  96. package/dist/heatmap-matrix/HeatmapMatrix.svelte +55 -40
  97. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +4 -3
  98. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +3 -2
  99. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
  100. package/dist/heatmap-matrix/index.d.ts +3 -2
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +1 -0
  103. package/dist/io/ExportPane.svelte +166 -0
  104. package/dist/io/ExportPane.svelte.d.ts +17 -0
  105. package/dist/io/decompress.js +1 -2
  106. package/dist/io/export.d.ts +5 -1
  107. package/dist/io/export.js +32 -28
  108. package/dist/io/fetch.d.ts +2 -1
  109. package/dist/io/file-drop.d.ts +7 -0
  110. package/dist/io/file-drop.js +13 -0
  111. package/dist/io/index.d.ts +2 -0
  112. package/dist/io/index.js +10 -0
  113. package/dist/io/types.d.ts +13 -0
  114. package/dist/isosurface/parse.js +46 -44
  115. package/dist/labels.js +1 -1
  116. package/dist/layout/FullscreenButton.svelte +33 -0
  117. package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
  118. package/dist/layout/FullscreenToggle.svelte +8 -14
  119. package/dist/layout/ViewerChrome.svelte +116 -0
  120. package/dist/layout/ViewerChrome.svelte.d.ts +17 -0
  121. package/dist/layout/fullscreen.d.ts +4 -0
  122. package/dist/layout/fullscreen.svelte.d.ts +8 -0
  123. package/dist/layout/fullscreen.svelte.js +37 -0
  124. package/dist/layout/index.d.ts +3 -0
  125. package/dist/layout/index.js +3 -0
  126. package/dist/math.d.ts +7 -3
  127. package/dist/math.js +18 -21
  128. package/dist/overlays/index.d.ts +4 -0
  129. package/dist/periodic-table/PeriodicTable.svelte +9 -8
  130. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  131. package/dist/phase-diagram/PhaseDiagramControls.svelte +3 -2
  132. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
  133. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
  134. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
  135. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +47 -132
  136. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
  137. package/dist/phase-diagram/colors.js +1 -1
  138. package/dist/phase-diagram/parse.d.ts +2 -1
  139. package/dist/plot/bar/BarPlot.svelte +79 -316
  140. package/dist/plot/bar/BarPlot.svelte.d.ts +7 -15
  141. package/dist/plot/bar/BarPlotControls.svelte.d.ts +1 -1
  142. package/dist/plot/bar/SpacegroupBarPlot.svelte +2 -1
  143. package/dist/plot/box/BoxPlot.svelte +76 -246
  144. package/dist/plot/box/BoxPlot.svelte.d.ts +4 -3
  145. package/dist/plot/box/BoxPlotControls.svelte.d.ts +1 -1
  146. package/dist/plot/box/Violin.svelte.d.ts +1 -1
  147. package/dist/plot/box/box-plot.d.ts +3 -2
  148. package/dist/plot/box/box-plot.js +5 -2
  149. package/dist/plot/box/kde.d.ts +2 -1
  150. package/dist/plot/box/kde.js +4 -4
  151. package/dist/plot/core/auto-place.d.ts +1 -1
  152. package/dist/plot/core/auto-place.js +4 -1
  153. package/dist/plot/core/components/ColorBar.svelte +5 -5
  154. package/dist/plot/core/components/ColorBar.svelte.d.ts +5 -4
  155. package/dist/plot/core/components/Line.svelte +3 -2
  156. package/dist/plot/core/components/Line.svelte.d.ts +3 -2
  157. package/dist/plot/core/components/PlotAxis.svelte +2 -1
  158. package/dist/plot/core/components/PlotAxis.svelte.d.ts +2 -1
  159. package/dist/plot/core/components/PlotControls.svelte.d.ts +1 -1
  160. package/dist/plot/core/components/ReferenceLine3D.svelte +2 -2
  161. package/dist/plot/core/components/ReferenceLine3D.svelte.d.ts +4 -4
  162. package/dist/plot/core/components/ReferencePlane.svelte +2 -2
  163. package/dist/plot/core/components/ReferencePlane.svelte.d.ts +4 -4
  164. package/dist/plot/core/data-cleaning.js +18 -18
  165. package/dist/plot/core/fill-utils.d.ts +4 -3
  166. package/dist/plot/core/fill-utils.js +6 -3
  167. package/dist/plot/core/interactions.d.ts +5 -1
  168. package/dist/plot/core/interactions.js +14 -0
  169. package/dist/plot/core/pan-zoom.svelte.d.ts +35 -0
  170. package/dist/plot/core/pan-zoom.svelte.js +221 -0
  171. package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
  172. package/dist/plot/core/placed-tween.svelte.js +68 -0
  173. package/dist/plot/core/reference-line.d.ts +10 -10
  174. package/dist/plot/core/reference-line.js +6 -6
  175. package/dist/plot/core/scales.d.ts +17 -25
  176. package/dist/plot/core/scales.js +10 -8
  177. package/dist/plot/core/svg.d.ts +2 -1
  178. package/dist/plot/core/types.d.ts +18 -7
  179. package/dist/plot/core/utils/label-placement.d.ts +1 -1
  180. package/dist/plot/core/utils/label-placement.js +3 -3
  181. package/dist/plot/core/utils.d.ts +2 -1
  182. package/dist/plot/histogram/Histogram.svelte +77 -314
  183. package/dist/plot/histogram/HistogramControls.svelte.d.ts +1 -1
  184. package/dist/plot/sankey/Sankey.svelte +2 -5
  185. package/dist/plot/sankey/Sankey.svelte.d.ts +1 -1
  186. package/dist/plot/sankey/sankey.js +3 -1
  187. package/dist/plot/scatter/BinnedScatterPlot.svelte +3 -5
  188. package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +4 -4
  189. package/dist/plot/scatter/ScatterPlot.svelte +160 -450
  190. package/dist/plot/scatter/ScatterPlot.svelte.d.ts +7 -15
  191. package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
  192. package/dist/plot/scatter/binned-scatter-types.d.ts +4 -11
  193. package/dist/plot/scatter/index.d.ts +1 -1
  194. package/dist/plot/scatter-3d/ScatterPlot3D.svelte +15 -26
  195. package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +6 -14
  196. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +9 -10
  197. package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte.d.ts +5 -5
  198. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +122 -121
  199. package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +5 -14
  200. package/dist/plot/scatter-3d/Surface3D.svelte +6 -5
  201. package/dist/plot/scatter-3d/Surface3D.svelte.d.ts +4 -3
  202. package/dist/plot/sunburst/Sunburst.svelte +16 -20
  203. package/dist/plot/sunburst/Sunburst.svelte.d.ts +4 -3
  204. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +1 -1
  205. package/dist/plot/sunburst/sunburst.js +4 -1
  206. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  207. package/dist/sanitize.js +13 -2
  208. package/dist/scene/SceneCamera.svelte +62 -0
  209. package/dist/scene/SceneCamera.svelte.d.ts +19 -0
  210. package/dist/scene/bind-renderer.svelte.d.ts +2 -0
  211. package/dist/scene/bind-renderer.svelte.js +14 -0
  212. package/dist/scene/index.d.ts +4 -0
  213. package/dist/scene/index.js +5 -0
  214. package/dist/scene/props.js +52 -0
  215. package/dist/scene/types.d.ts +26 -0
  216. package/dist/scene/types.js +1 -0
  217. package/dist/settings.d.ts +14 -2
  218. package/dist/settings.js +59 -1
  219. package/dist/spectral/Bands.svelte +8 -7
  220. package/dist/spectral/Bands.svelte.d.ts +3 -2
  221. package/dist/spectral/BandsAndDos.svelte +22 -24
  222. package/dist/spectral/BrillouinBandsDos.svelte +3 -3
  223. package/dist/spectral/Dos.svelte +5 -4
  224. package/dist/spectral/Dos.svelte.d.ts +2 -1
  225. package/dist/spectral/helpers.d.ts +6 -6
  226. package/dist/spectral/helpers.js +43 -37
  227. package/dist/state.svelte.d.ts +0 -7
  228. package/dist/state.svelte.js +0 -6
  229. package/dist/structure/Arrow.svelte +2 -4
  230. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  231. package/dist/structure/CanvasTooltip.svelte +1 -0
  232. package/dist/structure/CellSelect.svelte +11 -3
  233. package/dist/structure/CellSelect.svelte.d.ts +2 -1
  234. package/dist/structure/Lattice.svelte +2 -2
  235. package/dist/structure/Structure.svelte +291 -355
  236. package/dist/structure/Structure.svelte.d.ts +5 -15
  237. package/dist/structure/StructureControls.svelte +217 -2
  238. package/dist/structure/StructureControls.svelte.d.ts +5 -3
  239. package/dist/structure/StructureExportPane.svelte +54 -156
  240. package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
  241. package/dist/structure/StructureInfoPane.svelte +5 -3
  242. package/dist/structure/StructureInfoPane.svelte.d.ts +5 -5
  243. package/dist/structure/StructureScene.svelte +365 -198
  244. package/dist/structure/StructureScene.svelte.d.ts +22 -20
  245. package/dist/structure/{label-placement.d.ts → atom-label-placement.d.ts} +3 -3
  246. package/dist/structure/{label-placement.js → atom-label-placement.js} +12 -2
  247. package/dist/structure/atom-properties.d.ts +1 -1
  248. package/dist/structure/atom-properties.js +11 -16
  249. package/dist/structure/bond-order-perception.js +2 -4
  250. package/dist/structure/bonding.d.ts +3 -0
  251. package/dist/structure/bonding.js +91 -48
  252. package/dist/structure/export.d.ts +24 -4
  253. package/dist/structure/export.js +64 -122
  254. package/dist/structure/index.d.ts +2 -0
  255. package/dist/structure/index.js +2 -0
  256. package/dist/structure/parse.d.ts +3 -2
  257. package/dist/structure/parse.js +333 -370
  258. package/dist/structure/partial-occupancy.d.ts +0 -1
  259. package/dist/structure/partial-occupancy.js +1 -1
  260. package/dist/structure/pbc.d.ts +1 -1
  261. package/dist/structure/pbc.js +186 -13
  262. package/dist/structure/polyhedra.d.ts +41 -0
  263. package/dist/structure/polyhedra.js +602 -0
  264. package/dist/structure/site.d.ts +4 -0
  265. package/dist/structure/site.js +1 -0
  266. package/dist/structure/supercell.js +3 -2
  267. package/dist/structure/validation.js +5 -6
  268. package/dist/symmetry/SymmetryElementControls.svelte +69 -0
  269. package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
  270. package/dist/symmetry/SymmetryElements.svelte +354 -0
  271. package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
  272. package/dist/symmetry/SymmetryStats.svelte +111 -6
  273. package/dist/symmetry/WyckoffTable.svelte +68 -7
  274. package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
  275. package/dist/symmetry/cell-transform.js +7 -14
  276. package/dist/symmetry/index.d.ts +14 -4
  277. package/dist/symmetry/index.js +301 -80
  278. package/dist/symmetry/spacegroups.d.ts +5 -1
  279. package/dist/symmetry/spacegroups.js +15 -1
  280. package/dist/symmetry/symmetry-elements.d.ts +33 -0
  281. package/dist/symmetry/symmetry-elements.js +521 -0
  282. package/dist/symmetry/wyckoff-db.d.ts +9 -0
  283. package/dist/symmetry/wyckoff-db.js +87 -0
  284. package/dist/table/HeatmapTable.svelte +4 -15
  285. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  286. package/dist/trajectory/Trajectory.svelte +58 -61
  287. package/dist/trajectory/Trajectory.svelte.d.ts +10 -22
  288. package/dist/trajectory/TrajectoryExportPane.svelte +15 -24
  289. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
  290. package/dist/trajectory/TrajectoryInfoPane.svelte +3 -2
  291. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
  292. package/dist/trajectory/constants.js +6 -2
  293. package/dist/trajectory/extract.js +17 -37
  294. package/dist/trajectory/format-detect.d.ts +0 -1
  295. package/dist/trajectory/format-detect.js +3 -9
  296. package/dist/trajectory/frame-reader.d.ts +0 -1
  297. package/dist/trajectory/frame-reader.js +62 -128
  298. package/dist/trajectory/helpers.d.ts +10 -2
  299. package/dist/trajectory/helpers.js +56 -36
  300. package/dist/trajectory/parse/ase.d.ts +9 -1
  301. package/dist/trajectory/parse/ase.js +47 -32
  302. package/dist/trajectory/parse/diagnostics.d.ts +3 -0
  303. package/dist/trajectory/parse/diagnostics.js +14 -0
  304. package/dist/trajectory/parse/index.d.ts +1 -1
  305. package/dist/trajectory/parse/index.js +54 -102
  306. package/dist/trajectory/parse/lammps.d.ts +0 -2
  307. package/dist/trajectory/parse/lammps.js +8 -6
  308. package/dist/trajectory/parse/pymatgen.d.ts +2 -0
  309. package/dist/trajectory/parse/pymatgen.js +74 -0
  310. package/dist/trajectory/parse/vasp.js +4 -3
  311. package/dist/trajectory/parse/xyz.d.ts +9 -21
  312. package/dist/trajectory/parse/xyz.js +28 -33
  313. package/dist/trajectory/plotting.d.ts +0 -1
  314. package/dist/trajectory/plotting.js +3 -100
  315. package/dist/utils.d.ts +1 -0
  316. package/dist/utils.js +1 -1
  317. package/dist/xrd/XrdPlot.svelte +14 -29
  318. package/dist/xrd/broadening.d.ts +2 -1
  319. package/dist/xrd/calc-xrd.js +6 -11
  320. package/dist/xrd/index.d.ts +2 -2
  321. package/package.json +29 -16
  322. package/dist/element/data.json +0 -11864
  323. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  324. package/dist/fermi-surface/marching-cubes.js +0 -2
  325. package/dist/plot/core/hover-lock.svelte.d.ts +0 -14
  326. package/dist/plot/core/hover-lock.svelte.js +0 -45
@@ -0,0 +1,521 @@
1
+ import * as math from '../math';
2
+ // All element kinds in display order (axes first, then planes, then point elements).
3
+ // Single source of truth for ordering — both the controls legend and the element list
4
+ // returned by symmetry_elements_from_ops follow this sequence.
5
+ export const SYM_ELEM_KINDS = [
6
+ `rotation`,
7
+ `screw`,
8
+ `rotoinversion`,
9
+ `mirror`,
10
+ `glide`,
11
+ `inversion`,
12
+ ];
13
+ // Default overlay visibility: a SINGLE kind (proper rotation axes). High-symmetry
14
+ // structures easily have 100+ distinct elements which, drawn all at once, bury the
15
+ // structure entirely. Users opt into additional kinds individually via
16
+ // SymmetryElementControls (or the show_kinds prop).
17
+ export const DEFAULT_SHOW_SYM_KINDS = { rotation: true };
18
+ // Human-readable labels + representative legend colors per kind. mirror/glide/
19
+ // rotoinversion/inversion match the SymmetryElements.svelte render colors exactly;
20
+ // rotation/screw axes are colored by rotation order in-scene, so their swatch uses the
21
+ // 2-fold color as representative.
22
+ export const SYM_ELEM_KIND_INFO = {
23
+ rotation: { label: `rotation axes`, color: `#e63946` },
24
+ screw: { label: `screw axes`, color: `#e76f51` },
25
+ mirror: { label: `mirror planes`, color: `#ffb703` },
26
+ glide: { label: `glide planes`, color: `#8ecae6` },
27
+ rotoinversion: { label: `rotoinversion axes`, color: `#9c27b0` },
28
+ inversion: { label: `inversion centers`, color: `#555555` },
29
+ };
30
+ // Tally elements per kind (for legend labels like "mirror planes (9)")
31
+ export function count_symmetry_elements(elements) {
32
+ const counts = {};
33
+ for (const elem of elements)
34
+ counts[elem.kind] = (counts[elem.kind] ?? 0) + 1;
35
+ return counts;
36
+ }
37
+ // Whether the overlay would actually draw something: at least one PRESENT element whose
38
+ // kind is ENABLED in show_kinds. Used to gate declutter so callers don't hide polyhedra /
39
+ // shrink atoms when the enabled kinds match no present element (e.g. the rotation-only
40
+ // default on an inversion-only P-1 cell).
41
+ export const has_visible_symmetry_overlay = (elements, show_kinds = DEFAULT_SHOW_SYM_KINDS) => elements.some((elem) => show_kinds[elem.kind] ?? false);
42
+ const ELEM_TOL = 1e-6;
43
+ // The 12 edges of the unit cube [0,1]³ (corner pairs differing in exactly one coord)
44
+ const UNIT_CUBE_EDGES = (() => {
45
+ const corners = [];
46
+ for (let x = 0; x <= 1; x++) {
47
+ for (let y = 0; y <= 1; y++)
48
+ for (let z = 0; z <= 1; z++)
49
+ corners.push([x, y, z]);
50
+ }
51
+ const edges = [];
52
+ for (let idx_a = 0; idx_a < corners.length; idx_a++) {
53
+ for (let idx_b = idx_a + 1; idx_b < corners.length; idx_b++) {
54
+ const manhattan = corners[idx_a].reduce((sum, val, dim) => sum + Math.abs(val - corners[idx_b][dim]), 0);
55
+ if (manhattan === 1)
56
+ edges.push([corners[idx_a], corners[idx_b]]);
57
+ }
58
+ }
59
+ return edges;
60
+ })();
61
+ // moyo-wasm serializes nalgebra matrices as flat 9-arrays in COLUMN-major order
62
+ export const mat3_from_flat_col_major = (flat) => [
63
+ [flat[0], flat[3], flat[6]],
64
+ [flat[1], flat[4], flat[7]],
65
+ [flat[2], flat[5], flat[8]],
66
+ ];
67
+ const mat_round = (mat) => mat.map((row) => row.map((val) => Math.round(val)));
68
+ const mat_add = (mat_a, mat_b) => mat_a.map((row, idx) => row.map((val, jdx) => val + mat_b[idx][jdx]));
69
+ const mat_scale = (mat, factor) => mat.map((row) => row.map((val) => val * factor));
70
+ const mat_negate = (mat) => mat_scale(mat, -1);
71
+ const IDENTITY = [
72
+ [1, 0, 0],
73
+ [0, 1, 0],
74
+ [0, 0, 1],
75
+ ];
76
+ const is_identity = (mat) => mat.every((row, idx) => row.every((val, jdx) => val === (idx === jdx ? 1 : 0)));
77
+ const trace = (mat) => mat[0][0] + mat[1][1] + mat[2][2];
78
+ // Projection onto the invariant (+1 eigenvalue) subspace P = (1/n) Σₖ Wᵏ, plus the
79
+ // matrix order n (crystallographic: 1, 2, 3, 4 or 6)
80
+ function invariant_projector(mat) {
81
+ let sum = IDENTITY;
82
+ let power = mat;
83
+ for (let order = 1; order <= 6; order++) {
84
+ if (is_identity(power))
85
+ return { proj: mat_scale(sum, 1 / order), order };
86
+ sum = mat_add(sum, power);
87
+ power = mat_round(math.dot(power, mat));
88
+ }
89
+ throw new Error(`Matrix is not of finite crystallographic order`);
90
+ }
91
+ const gcd = (val_a, val_b) => val_b === 0 ? val_a : gcd(val_b, val_a % val_b);
92
+ // Extract the (1D) invariant axis of a proper rotation as a reduced integer vector with
93
+ // canonical sign (first nonzero component positive). proj must have rank 1.
94
+ function axis_from_projector(proj, order) {
95
+ // n·P is an integer matrix whose nonzero columns all span the axis
96
+ const int_proj = mat_round(mat_scale(proj, order));
97
+ let best = null;
98
+ let best_norm = 0;
99
+ for (let col = 0; col < 3; col++) {
100
+ const vec = [int_proj[0][col], int_proj[1][col], int_proj[2][col]];
101
+ const norm = Math.abs(vec[0]) + Math.abs(vec[1]) + Math.abs(vec[2]);
102
+ if (norm > best_norm) {
103
+ best = vec;
104
+ best_norm = norm;
105
+ }
106
+ }
107
+ if (!best)
108
+ return null;
109
+ const divisor = best.reduce((acc, val) => gcd(acc, Math.abs(val)), 0);
110
+ let axis = best.map((val) => val / divisor);
111
+ const first_nonzero = axis.find((val) => val !== 0) ?? 1;
112
+ // normalize -0 to 0 when flipping to canonical sign (first nonzero positive)
113
+ if (first_nonzero < 0)
114
+ axis = axis.map((val) => (val === 0 ? 0 : -val));
115
+ return axis;
116
+ }
117
+ // Fixed point of the affine map x ↦ W·x + w_loc as the orbit average of the origin.
118
+ // Exact whenever w_loc has no component in the invariant subspace (P·w_loc = 0).
119
+ function fixed_point(mat, w_loc, order) {
120
+ let current = [0, 0, 0];
121
+ const sum = [0, 0, 0];
122
+ for (let iter = 0; iter < order; iter++) {
123
+ if (iter > 0)
124
+ current = math.add(math.mat3x3_vec3_multiply(mat, current), w_loc);
125
+ sum[0] += current[0];
126
+ sum[1] += current[1];
127
+ sum[2] += current[2];
128
+ }
129
+ return sum.map((val) => val / order);
130
+ }
131
+ // NOTE on epsilon: 1e-8 is the loosest of three intentionally different wrap
132
+ // helpers (vs wrap_frac_coord @1e-10 [[src/lib/structure/pbc.ts:26]] for parsed
133
+ // coords and wrap_frac @1e-9 [[src/lib/symmetry/index.ts:80]] for standardized
134
+ // Wyckoff positions). Inputs here are fixed points / intercepts obtained by
135
+ // solving linear systems and averaging over operation order, so float error is
136
+ // largest; the result feeds toFixed()-based dedup keys for symmetry elements,
137
+ // which need both near-0 and near-1 snapped onto exactly 0 to stay stable near
138
+ // cell boundaries. Do not unify: tightening this epsilon breaks element dedup.
139
+ const wrap_point = (pos) => pos.map((coord) => {
140
+ const wrapped = coord - Math.floor(coord); // always in [0, 1)
141
+ // snap near-0 and near-1 (which wraps to near-0) onto exactly 0
142
+ return wrapped < 1e-8 || wrapped > 1 - 1e-8 ? 0 : wrapped;
143
+ });
144
+ // Enumerate lattice translations invariant under W (lying along the axis / in the
145
+ // plane), including centering vectors of non-primitive cells (I/F/A/B/C/R): without
146
+ // them, e.g. the body-centering (1/2,1/2,1/2) period along ⟨111⟩ axes is missed and
147
+ // centering-composed mirrors get mislabeled as glides. Candidates r + c with
148
+ // r ∈ {-1,0,1}³ cover all cases for moyo translations, which lie in [0,1)³.
149
+ function invariant_translations(mat, centerings) {
150
+ const result = [];
151
+ for (let dx = -1; dx <= 1; dx++) {
152
+ for (let dy = -1; dy <= 1; dy++) {
153
+ for (let dz = -1; dz <= 1; dz++) {
154
+ for (const centering of [[0, 0, 0], ...centerings]) {
155
+ const cand = [dx + centering[0], dy + centering[1], dz + centering[2]];
156
+ if (cand.every((val) => Math.abs(val) < ELEM_TOL))
157
+ continue;
158
+ const mapped = math.mat3x3_vec3_multiply(mat, cand);
159
+ if (mapped.some((val, idx) => Math.abs(val - cand[idx]) > ELEM_TOL))
160
+ continue;
161
+ result.push(cand);
162
+ }
163
+ }
164
+ }
165
+ }
166
+ return result;
167
+ }
168
+ // Reduce an intrinsic (in-plane glide) translation modulo invariant lattice translations
169
+ function reduce_intrinsic_translation(w_intrinsic, candidates) {
170
+ let best = w_intrinsic;
171
+ let best_sq = math.dot(w_intrinsic, w_intrinsic);
172
+ for (const cand of candidates) {
173
+ const reduced = math.subtract(w_intrinsic, cand);
174
+ const sq = math.dot(reduced, reduced);
175
+ if (sq < best_sq - ELEM_TOL) {
176
+ best = reduced;
177
+ best_sq = sq;
178
+ }
179
+ }
180
+ return best;
181
+ }
182
+ const is_zero_vec = (vec) => vec.every((val) => Math.abs(val) < ELEM_TOL);
183
+ // Glide letter from the reduced glide vector: a/b/c (half along one cell axis),
184
+ // n (half along a face/body diagonal), d (quarter diagonal), g otherwise
185
+ function glide_letter(glide_vec) {
186
+ const doubled = glide_vec.map((val) => val * 2);
187
+ const is_int = (vals) => vals.every((val) => Math.abs(val - Math.round(val)) < 1e-4);
188
+ if (is_int(doubled)) {
189
+ const nonzero = doubled.map((val) => Math.round(val)).filter((val) => val !== 0);
190
+ if (nonzero.length === 1)
191
+ return [`a`, `b`, `c`][doubled.findIndex((val) => Math.round(val) !== 0)];
192
+ return `n`;
193
+ }
194
+ if (is_int(glide_vec.map((val) => val * 4)))
195
+ return `d`;
196
+ return `g`;
197
+ }
198
+ // Translation-independent analysis of a rotation matrix. Returns null for the identity
199
+ // (pure translations define no geometric element).
200
+ function build_rotation_info(rotation, centerings) {
201
+ const mat = mat_round(mat3_from_flat_col_major(rotation));
202
+ const det = Math.round(math.det_3x3(mat));
203
+ const tr = Math.round(trace(mat));
204
+ if (det === 1 && tr === 3)
205
+ return null; // identity or pure translation
206
+ const { proj, order: mat_order } = invariant_projector(mat);
207
+ if (det === -1 && tr === -3) {
208
+ return {
209
+ mat,
210
+ proj,
211
+ mat_order,
212
+ kind: `inversion`,
213
+ order: 1,
214
+ axis: null,
215
+ axis_sq: 0,
216
+ period: 1,
217
+ invariant_candidates: [],
218
+ };
219
+ }
220
+ if (det === 1) {
221
+ // proper rotation (order from trace: −1→2, 0→3, 1→4, 2→6)
222
+ const proper_order_by_trace = { [-1]: 2, 0: 3, 1: 4, 2: 6 };
223
+ const order = proper_order_by_trace[tr];
224
+ if (!order)
225
+ throw new Error(`Invalid proper rotation trace ${tr}`);
226
+ const axis = axis_from_projector(proj, mat_order);
227
+ if (!axis)
228
+ throw new Error(`Failed to extract rotation axis`);
229
+ // shortest lattice period along the axis (1 for primitive lattices; can be 1/2 via
230
+ // centering vectors, e.g. (1/2,1/2,1/2) along ⟨111⟩ in body-centered cells)
231
+ const axis_sq = math.dot(axis, axis);
232
+ let period = 1;
233
+ for (const cand of invariant_translations(mat, centerings)) {
234
+ const lambda_cand = math.dot(cand, axis) / axis_sq;
235
+ // candidate must be parallel to the axis and shorter than the current period
236
+ const is_parallel = cand.every((val, idx) => Math.abs(val - lambda_cand * axis[idx]) < ELEM_TOL);
237
+ if (is_parallel && lambda_cand > ELEM_TOL && lambda_cand < period) {
238
+ period = lambda_cand;
239
+ }
240
+ }
241
+ return {
242
+ mat,
243
+ proj,
244
+ mat_order,
245
+ kind: `proper`,
246
+ order,
247
+ axis,
248
+ axis_sq,
249
+ period,
250
+ invariant_candidates: [],
251
+ };
252
+ }
253
+ // Improper: mirror/glide (trace 1) or rotoinversion −3/−4/−6. The proper part −W is a
254
+ // rotation about the same axis (the plane normal for mirrors).
255
+ const proper_part = mat_negate(mat);
256
+ const { proj: proper_proj, order: proper_order } = invariant_projector(proper_part);
257
+ const axis = axis_from_projector(proper_proj, proper_order);
258
+ if (!axis)
259
+ throw new Error(`Failed to extract improper-operation axis`);
260
+ if (tr === 1) {
261
+ return {
262
+ mat,
263
+ proj,
264
+ mat_order,
265
+ kind: `mirror`,
266
+ order: 2,
267
+ axis,
268
+ axis_sq: math.dot(axis, axis),
269
+ period: 1,
270
+ invariant_candidates: invariant_translations(mat, centerings),
271
+ };
272
+ }
273
+ const rotoinv_order_by_trace = { 0: 3, [-1]: 4, [-2]: 6 };
274
+ const order = rotoinv_order_by_trace[tr];
275
+ if (!order)
276
+ throw new Error(`Invalid improper rotation trace ${tr}`);
277
+ return {
278
+ mat,
279
+ proj,
280
+ mat_order,
281
+ kind: `rotoinversion`,
282
+ order,
283
+ axis,
284
+ axis_sq: math.dot(axis, axis),
285
+ period: 1,
286
+ invariant_candidates: [],
287
+ };
288
+ }
289
+ // Classify the operation (info.mat, w) given precomputed rotation-dependent data
290
+ function classify_with_rotation_info(info, w) {
291
+ const { mat, proj, mat_order, kind, order, axis } = info;
292
+ const w_intrinsic = math.mat3x3_vec3_multiply(proj, w);
293
+ const w_loc = math.subtract(w, w_intrinsic);
294
+ const point = wrap_point(fixed_point(mat, w_loc, mat_order));
295
+ if (kind === `inversion`) {
296
+ return { kind, order: 1, label: `-1`, axis: null, point, translation: null };
297
+ }
298
+ if (kind === `proper`) {
299
+ // Screw component: w_i = λ·axis, reduced modulo the lattice period along the axis
300
+ const { axis_sq, period } = info;
301
+ const lambda_raw = math.dot(w_intrinsic, axis) / axis_sq;
302
+ let lambda = lambda_raw - period * Math.floor(lambda_raw / period + ELEM_TOL);
303
+ if (Math.abs(lambda) < ELEM_TOL)
304
+ lambda = 0;
305
+ const is_screw = lambda > ELEM_TOL;
306
+ const screw_part = Math.round((order * lambda) / period) % order;
307
+ return {
308
+ kind: is_screw ? `screw` : `rotation`,
309
+ order,
310
+ label: is_screw ? `${order}_${screw_part}` : `${order}`,
311
+ axis,
312
+ point,
313
+ translation: is_screw ? axis.map((val) => val * lambda) : null,
314
+ };
315
+ }
316
+ if (kind === `mirror`) {
317
+ const glide_vec = reduce_intrinsic_translation(w_intrinsic, info.invariant_candidates);
318
+ const is_glide = !is_zero_vec(glide_vec);
319
+ return {
320
+ kind: is_glide ? `glide` : `mirror`,
321
+ order: 2,
322
+ label: is_glide ? glide_letter(glide_vec) : `m`,
323
+ axis,
324
+ point,
325
+ translation: is_glide ? glide_vec : null,
326
+ };
327
+ }
328
+ // Rotoinversion −3/−4/−6 (no intrinsic translation: P = 0, so w_i = 0)
329
+ return { kind, order, label: `-${order}`, axis, point, translation: null };
330
+ }
331
+ // Classify a single operation (rotation as flat column-major 9-array, translation
332
+ // vector, both fractional). Returns null for the identity and pure (centering)
333
+ // translations, which define no geometric element. For non-primitive (centered) cells,
334
+ // pass the centering vectors so intrinsic screw/glide translations are reduced modulo
335
+ // the TRUE lattice — e.g. along a ⟨111⟩ axis of a body-centered cell the lattice period
336
+ // is (1/2)(1,1,1), and a C-centering-composed mirror is still a mirror, not an n-glide.
337
+ export function classify_symmetry_op(rotation, translation, centerings = []) {
338
+ const info = build_rotation_info(rotation, centerings);
339
+ return info ? classify_with_rotation_info(info, translation) : null;
340
+ }
341
+ // Canonical dedup key for the geometric locus of an element. Elements are identified
342
+ // modulo lattice translations:
343
+ // - inversion centers: the wrapped center
344
+ // - planes: (normal, offset s = x₀·normal mod 1) — lattice translations change s by an
345
+ // integer since the normal is an integer vector
346
+ // - axis lines: (direction, line intercept x₀ − λ·u wrapped mod 1) — wrapping merges
347
+ // lattice-equivalent parallel lines
348
+ function element_locus_key(elem) {
349
+ const fmt = (val) => (Math.abs(val) < 1e-4 ? 0 : val).toFixed(4);
350
+ if (elem.axis === null)
351
+ return `center|${elem.point.map(fmt).join(`,`)}`;
352
+ const axis_key = elem.axis.join(`,`);
353
+ if (elem.kind === `mirror` || elem.kind === `glide`) {
354
+ const offset = math.dot(elem.point, elem.axis);
355
+ const wrapped = offset - Math.floor(offset + 1e-6);
356
+ return `plane|${axis_key}|${fmt(wrapped >= 1 - 1e-4 ? 0 : wrapped)}`;
357
+ }
358
+ const lambda = math.dot(elem.point, elem.axis) / math.dot(elem.axis, elem.axis);
359
+ const intercept = wrap_point(math.subtract(elem.point, elem.axis.map((val) => val * lambda)));
360
+ return `line|${axis_key}|${intercept.map(fmt).join(`,`)}`;
361
+ }
362
+ // Derive all distinct symmetry elements (modulo lattice translations) from a list of
363
+ // space-group operations. Each operation is classified, then re-anchored with the 8
364
+ // lattice offsets t ∈ {0,1}³ to enumerate the distinct in-cell instances of its element
365
+ // family (e.g. the inversion centers of P-1 sit at all 8 half-lattice points; 2-fold
366
+ // axes recur at quarter positions). Elements sharing the same geometric locus but
367
+ // different symbols (e.g. a 2-fold axis inside a 4-fold axis) are kept as separate
368
+ // entries — filter by `order`/`label` downstream if desired.
369
+ export function symmetry_elements_from_ops(operations) {
370
+ // Centering vectors are the pure-translation operations (identity rotation, w ∉ ℤ³).
371
+ // They define the true lattice for screw/glide reduction in centered cells.
372
+ // Deduplicated since supercell inputs repeat sublattice translations.
373
+ const centering_keys = new Set();
374
+ const centerings = [];
375
+ for (const { rotation, translation } of operations) {
376
+ if (!is_identity(mat_round(mat3_from_flat_col_major(rotation))))
377
+ continue;
378
+ const wrapped = wrap_point(translation);
379
+ if (is_zero_vec(wrapped))
380
+ continue;
381
+ const key = wrapped.map((val) => val.toFixed(6)).join(`,`);
382
+ if (centering_keys.has(key))
383
+ continue;
384
+ centering_keys.add(key);
385
+ centerings.push(wrapped);
386
+ }
387
+ // Cache rotation-dependent analysis: supercell inputs can have thousands of ops but
388
+ // share at most 48 distinct rotation matrices
389
+ const info_cache = new Map();
390
+ const seen = new Map();
391
+ for (const { rotation, translation } of operations) {
392
+ const rot_key = rotation.join(`,`);
393
+ let info = info_cache.get(rot_key);
394
+ if (info === undefined) {
395
+ info = build_rotation_info(rotation, centerings);
396
+ info_cache.set(rot_key, info);
397
+ }
398
+ if (info === null)
399
+ continue; // identity / pure translation
400
+ for (let dx = 0; dx <= 1; dx++) {
401
+ for (let dy = 0; dy <= 1; dy++) {
402
+ for (let dz = 0; dz <= 1; dz++) {
403
+ const shifted = [translation[0] + dx, translation[1] + dy, translation[2] + dz];
404
+ const elem = classify_with_rotation_info(info, shifted);
405
+ const key = `${elem.kind}|${elem.label}|${element_locus_key(elem)}`;
406
+ if (!seen.has(key))
407
+ seen.set(key, elem);
408
+ }
409
+ }
410
+ }
411
+ }
412
+ // Stable order: by kind (SYM_ELEM_KINDS sequence), then descending order, label, point
413
+ return [...seen.values()].sort((el1, el2) => SYM_ELEM_KINDS.indexOf(el1.kind) - SYM_ELEM_KINDS.indexOf(el2.kind) ||
414
+ el2.order - el1.order ||
415
+ el1.label.localeCompare(el2.label) ||
416
+ el1.point.join(`,`).localeCompare(el2.point.join(`,`)));
417
+ }
418
+ // Evenly-spaced dash layout along a segment of given length (Å): returns dash centers
419
+ // (distance from the segment start) and lengths. Dashes touch both segment ends so
420
+ // dashed axes still visually span the full cell; the gap stretches as needed. Used to
421
+ // render screw axes dashed (vs solid pure rotations) — translation-carrying elements
422
+ // are dashed, echoing the ITA plane-group convention.
423
+ export function dash_segments(length, dash, gap) {
424
+ if (!(length > 0) || !(dash > 0) || gap < 0)
425
+ return [];
426
+ if (length <= dash)
427
+ return [{ center: length / 2, length }];
428
+ const count = Math.floor((length + gap) / (dash + gap));
429
+ if (count <= 1)
430
+ return [{ center: length / 2, length: dash }];
431
+ // count·dash + (count−1)·gap_actual = length, with gap_actual ≥ gap by construction
432
+ const gap_actual = (length - count * dash) / (count - 1);
433
+ return Array.from({ length: count }, (_, idx) => ({
434
+ center: idx * (dash + gap_actual) + dash / 2,
435
+ length: dash,
436
+ }));
437
+ }
438
+ // Convert a fractional direction or point to Cartesian coordinates (lattice rows are
439
+ // basis vectors). Valid for axis directions AND plane normals (eigenvectors of the
440
+ // fractional operator map to eigenvectors of the orthogonal Cartesian operator).
441
+ export const frac_to_cart_direction = (frac, lattice) => math.create_frac_to_cart(lattice)(frac);
442
+ // Clip the line (point + t·direction, fractional) to the unit cell [0,1]³ using the
443
+ // slab method, returning the Cartesian segment endpoints (or null if the line misses
444
+ // the cell). Used to draw rotation/screw axes spanning the rendered cell.
445
+ export function clip_line_to_cell(point, direction, lattice, eps = 1e-9) {
446
+ let t_min = -Infinity;
447
+ let t_max = Infinity;
448
+ for (let dim = 0; dim < 3; dim++) {
449
+ if (Math.abs(direction[dim]) < eps) {
450
+ if (point[dim] < -eps || point[dim] > 1 + eps)
451
+ return null; // parallel, outside slab
452
+ continue;
453
+ }
454
+ const t_0 = (0 - point[dim]) / direction[dim];
455
+ const t_1 = (1 - point[dim]) / direction[dim];
456
+ t_min = Math.max(t_min, Math.min(t_0, t_1));
457
+ t_max = Math.min(t_max, Math.max(t_0, t_1));
458
+ }
459
+ if (t_min >= t_max - eps || !Number.isFinite(t_min) || !Number.isFinite(t_max)) {
460
+ return null;
461
+ }
462
+ const endpoint = (t_param) => frac_to_cart_direction(point.map((coord, idx) => coord + t_param * direction[idx]), lattice);
463
+ return [endpoint(t_min), endpoint(t_max)];
464
+ }
465
+ // Clip the plane through `point` with fractional-direct-space normal `normal_frac` to
466
+ // the unit cell, returning the Cartesian polygon vertices in winding order (empty if
467
+ // the plane misses the cell). The plane equation in fractional coordinates uses the
468
+ // pullback n_eq[i] = lattice[i]·N_cart of the Cartesian normal, which is exact for
469
+ // arbitrary (non-orthogonal) cells.
470
+ export function clip_plane_to_cell(point, normal_frac, lattice) {
471
+ const normal_cart = frac_to_cart_direction(normal_frac, lattice);
472
+ const n_eq = [
473
+ math.dot(lattice[0], normal_cart),
474
+ math.dot(lattice[1], normal_cart),
475
+ math.dot(lattice[2], normal_cart),
476
+ ];
477
+ const signed_dist = (frac) => n_eq[0] * (frac[0] - point[0]) +
478
+ n_eq[1] * (frac[1] - point[1]) +
479
+ n_eq[2] * (frac[2] - point[2]);
480
+ const frac_points = [];
481
+ const tol = 1e-7;
482
+ // Intersect the plane with the 12 unit-cube edges (corners on the plane included)
483
+ for (const [edge_a, edge_b] of UNIT_CUBE_EDGES) {
484
+ const dist_a = signed_dist(edge_a);
485
+ const dist_b = signed_dist(edge_b);
486
+ if (Math.abs(dist_a) < tol)
487
+ frac_points.push(edge_a);
488
+ if (Math.abs(dist_b) < tol)
489
+ frac_points.push(edge_b);
490
+ if (dist_a * dist_b < -tol * tol) {
491
+ const frac_t = dist_a / (dist_a - dist_b);
492
+ frac_points.push(edge_a.map((val, dim) => val + frac_t * (edge_b[dim] - val)));
493
+ }
494
+ }
495
+ // Dedup and convert to Cartesian
496
+ const seen = new Set();
497
+ const cart_points = [];
498
+ for (const frac of frac_points) {
499
+ const key = frac.map((val) => val.toFixed(6)).join(`,`);
500
+ if (seen.has(key))
501
+ continue;
502
+ seen.add(key);
503
+ cart_points.push(frac_to_cart_direction(frac, lattice));
504
+ }
505
+ if (cart_points.length < 3)
506
+ return [];
507
+ // Order vertices by angle around the centroid within the plane
508
+ const centroid = cart_points
509
+ .reduce((acc, vert) => math.add(acc, vert), [0, 0, 0])
510
+ .map((val) => val / cart_points.length);
511
+ const normal_unit = math.normalize_vec(normal_cart);
512
+ const ref_vec = math.normalize_vec(math.subtract(cart_points[0], centroid));
513
+ const cross_ref = math.cross_3d(normal_unit, ref_vec);
514
+ return cart_points
515
+ .map((vert) => {
516
+ const rel = math.subtract(vert, centroid);
517
+ return { vert, angle: Math.atan2(math.dot(rel, cross_ref), math.dot(rel, ref_vec)) };
518
+ })
519
+ .sort((pt_a, pt_b) => pt_a.angle - pt_b.angle)
520
+ .map(({ vert }) => vert);
521
+ }
@@ -0,0 +1,9 @@
1
+ import type { MoyoHallSymbolEntry, MoyoWyckoffPosition } from '@spglib/moyo-wasm';
2
+ import type { WyckoffPos } from './index';
3
+ export declare function spacegroup_wyckoff_positions(hall_number: number): MoyoWyckoffPosition[];
4
+ export declare function spacegroup_settings(spacegroup_number: number): MoyoHallSymbolEntry[];
5
+ export declare const wyckoff_letter: (wyckoff: string) => string;
6
+ export declare const count_free_params: (coordinates: string) => number;
7
+ export declare function enrich_wyckoff_rows(rows: WyckoffPos[], db_positions: MoyoWyckoffPosition[]): WyckoffPos[];
8
+ export declare function wyckoff_sequence(rows: WyckoffPos[]): string;
9
+ export declare function count_structure_free_params(rows: WyckoffPos[]): number | null;
@@ -0,0 +1,87 @@
1
+ // Helpers around moyo's space-group database functions (new in moyo-wasm 0.11):
2
+ // - wyckoff_positions(hall_number): ALL Wyckoff positions of a space-group setting
3
+ // (multiplicity, letter, site symmetry, representative coordinate triplet)
4
+ // - hall_symbol_entries_from_number(number): all settings (origin choices, unique
5
+ // axes, cell choices) of an ITA space group
6
+ // Plus pure helpers to join that database against the occupied Wyckoff orbits of an
7
+ // analyzed structure (Wyckoff sequence, internal degrees of freedom, ITA coords).
8
+ import { superscript_digits } from '../labels';
9
+ import { hall_symbol_entries_from_number, wyckoff_positions } from '@spglib/moyo-wasm';
10
+ // All Wyckoff positions of the space-group setting given by hall_number (1-530),
11
+ // ordered general-position-first. moyo returns [] for out-of-range hall numbers.
12
+ // Returns [] when the moyo WASM module is not initialized (SSR, unit tests) — callers
13
+ // treat the database as an optional enrichment, never a hard requirement.
14
+ export function spacegroup_wyckoff_positions(hall_number) {
15
+ try {
16
+ return wyckoff_positions(hall_number);
17
+ }
18
+ catch {
19
+ return [];
20
+ }
21
+ }
22
+ // All Hall-symbol entries (settings) of the ITA space group `spacegroup_number`
23
+ // (1-230), ordered by Hall number. Returns [] when the WASM module is not initialized.
24
+ export function spacegroup_settings(spacegroup_number) {
25
+ try {
26
+ return hall_symbol_entries_from_number(spacegroup_number);
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ }
32
+ // Wyckoff letter from a `4a`-style multiplicity+letter label. Uppercase `A` is moyo's
33
+ // encoding of ITA's 27th letter alpha (general position of Pmmm-like groups).
34
+ export const wyckoff_letter = (wyckoff) => /[a-zA-Z]+$/.exec(wyckoff)?.[0] ?? ``;
35
+ // Number of free coordinate parameters in an ITA representative triplet: distinct
36
+ // x/y/z variables, e.g. `x,y,z` → 3, `1/4,1/4,z` → 1, `x,2x,1/2` → 1, `x,-x,z` → 2
37
+ export const count_free_params = (coordinates) => new Set(coordinates.match(/[xyz]/g)).size;
38
+ // Attach the space-group database entry (ITA representative coordinates, site-symmetry
39
+ // fallback) to each occupied Wyckoff row, matched by letter. Rows whose letter has no
40
+ // database entry (or an empty database) pass through unchanged.
41
+ export function enrich_wyckoff_rows(rows, db_positions) {
42
+ if (db_positions.length === 0)
43
+ return rows;
44
+ const db_by_letter = new Map(db_positions.map((pos) => [pos.letter, pos]));
45
+ return rows.map((row) => {
46
+ const entry = db_by_letter.get(wyckoff_letter(row.wyckoff));
47
+ if (!entry)
48
+ return row;
49
+ return {
50
+ ...row,
51
+ coordinates: entry.coordinates,
52
+ site_symmetry: row.site_symmetry ?? entry.site_symmetry,
53
+ };
54
+ });
55
+ }
56
+ // Rank `A` (ITA's alpha, the letter AFTER z) above all lowercase letters
57
+ const letter_rank = (letter) => letter.charCodeAt(0) + (letter < `a` ? 64 : 0);
58
+ // Wyckoff sequence of the occupied orbits: letters in descending alphabetical order
59
+ // (general position first, ICSD convention), each with a superscript count when more
60
+ // than one orbit occupies that letter — e.g. cubic perovskite (Pm-3m): `c b a`.
61
+ export function wyckoff_sequence(rows) {
62
+ const counts = new Map();
63
+ for (const row of rows) {
64
+ const letter = wyckoff_letter(row.wyckoff);
65
+ if (letter)
66
+ counts.set(letter, (counts.get(letter) ?? 0) + 1);
67
+ }
68
+ return [...counts.entries()]
69
+ .sort(([letter_1], [letter_2]) => letter_rank(letter_2) - letter_rank(letter_1))
70
+ .map(([letter, count]) => count > 1 ? `${letter}${superscript_digits(String(count))}` : letter)
71
+ .join(` `);
72
+ }
73
+ // Internal degrees of freedom of the structure: free fractional coordinates summed
74
+ // over occupied Wyckoff orbits. Requires every row to carry ITA coordinates (see
75
+ // enrich_wyckoff_rows); returns null for empty rows or when any row lacks coordinates
76
+ // so callers can hide the stat instead of showing a wrong number.
77
+ export function count_structure_free_params(rows) {
78
+ if (rows.length === 0)
79
+ return null;
80
+ let total = 0;
81
+ for (const row of rows) {
82
+ if (row.coordinates === undefined)
83
+ return null;
84
+ total += count_free_params(row.coordinates);
85
+ }
86
+ return total;
87
+ }
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { luminance, watch_dark_mode } from '../colors'
3
3
  import Icon from '../Icon.svelte'
4
+ import { download } from '../io/fetch'
4
5
  import { format_num } from '../labels'
5
6
  import { SettingsSection } from '../layout'
6
7
  import ContextMenu from '../overlays/ContextMenu.svelte'
@@ -769,7 +770,7 @@
769
770
  const row_id = get_row_id(row)
770
771
  const idx = selected_rows.findIndex((selected_row) => get_row_id(selected_row) === row_id)
771
772
  if (idx !== -1) {
772
- selected_rows = selected_rows.filter((_, i) => i !== idx)
773
+ selected_rows = selected_rows.filter((_, row_idx) => row_idx !== idx)
773
774
  } else {
774
775
  selected_rows = [...selected_rows, row]
775
776
  }
@@ -823,7 +824,7 @@
823
824
  }
824
825
 
825
826
  function export_csv(filename = `table-export`) {
826
- download_file(serialize_table(`,`, true), `${filename}.csv`, `text/csv`)
827
+ download(serialize_table(`,`, true), `${filename}.csv`, `text/csv`)
827
828
  }
828
829
 
829
830
  function export_json(filename = `table-export`) {
@@ -838,25 +839,13 @@
838
839
  }
839
840
  return clean_row
840
841
  })
841
- download_file(
842
+ download(
842
843
  JSON.stringify(rows, null, 2),
843
844
  `${filename}.json`,
844
845
  `application/json`,
845
846
  )
846
847
  }
847
848
 
848
- function download_file(content: string, filename: string, mime_type: string) {
849
- const blob = new Blob([content], { type: mime_type })
850
- const url = URL.createObjectURL(blob)
851
- const link = document.createElement(`a`)
852
- link.href = url
853
- link.download = filename
854
- document.body.append(link)
855
- link.click()
856
- document.body.removeChild(link)
857
- URL.revokeObjectURL(url)
858
- }
859
-
860
849
  function copy_to_clipboard() {
861
850
  navigator.clipboard.writeText(serialize_table(`\t`))
862
851
  }
@@ -44,6 +44,6 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
44
44
  }]>;
45
45
  footer?: Snippet;
46
46
  };
47
- declare const HeatmapTable: import("svelte").Component<$$ComponentProps, {}, "sort" | "data" | "show_controls" | "controls_open" | "show_heatmap" | "column_order" | "selected_rows" | "hidden_columns" | "loading" | "heatmap_opacity">;
47
+ declare const HeatmapTable: import("svelte").Component<$$ComponentProps, {}, "sort" | "data" | "show_controls" | "controls_open" | "loading" | "show_heatmap" | "column_order" | "selected_rows" | "hidden_columns" | "heatmap_opacity">;
48
48
  type HeatmapTable = ReturnType<typeof HeatmapTable>;
49
49
  export default HeatmapTable;