matterviz 0.3.7 → 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 (486) hide show
  1. package/dist/Icon.svelte +7 -4
  2. package/dist/MillerIndexInput.svelte +1 -1
  3. package/dist/api/optimade.js +32 -26
  4. package/dist/app.css +0 -3
  5. package/dist/brillouin/BrillouinZone.svelte +76 -148
  6. package/dist/brillouin/BrillouinZone.svelte.d.ts +6 -14
  7. package/dist/brillouin/BrillouinZoneExportPane.svelte +43 -96
  8. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneInfoPane.svelte +9 -32
  10. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
  11. package/dist/brillouin/BrillouinZoneScene.svelte +97 -205
  12. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +4 -23
  13. package/dist/brillouin/BrillouinZoneTooltip.svelte +16 -25
  14. package/dist/brillouin/ReciprocalVectors.svelte +39 -0
  15. package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
  16. package/dist/brillouin/compute.d.ts +2 -0
  17. package/dist/brillouin/compute.js +89 -90
  18. package/dist/brillouin/geometry.d.ts +8 -0
  19. package/dist/brillouin/geometry.js +57 -0
  20. package/dist/brillouin/index.d.ts +2 -0
  21. package/dist/brillouin/index.js +2 -0
  22. package/dist/brillouin/types.d.ts +2 -2
  23. package/dist/chempot-diagram/ChemPotDiagram.svelte +14 -13
  24. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
  25. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +109 -203
  26. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
  27. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +180 -470
  28. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
  29. package/dist/chempot-diagram/async-compute.svelte.js +3 -1
  30. package/dist/chempot-diagram/chempot-worker.js +2 -1
  31. package/dist/chempot-diagram/color.d.ts +3 -6
  32. package/dist/chempot-diagram/color.js +5 -5
  33. package/dist/chempot-diagram/compute.d.ts +4 -4
  34. package/dist/chempot-diagram/compute.js +20 -20
  35. package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
  36. package/dist/chempot-diagram/controls-state.svelte.js +42 -0
  37. package/dist/chempot-diagram/export.d.ts +47 -0
  38. package/dist/chempot-diagram/export.js +133 -0
  39. package/dist/chempot-diagram/index.d.ts +1 -0
  40. package/dist/chempot-diagram/index.js +1 -0
  41. package/dist/chempot-diagram/pointer.d.ts +0 -10
  42. package/dist/chempot-diagram/pointer.js +4 -4
  43. package/dist/chempot-diagram/types.d.ts +3 -3
  44. package/dist/colors/index.js +8 -7
  45. package/dist/composition/FormulaFilter.svelte +18 -11
  46. package/dist/composition/PieChart.svelte +11 -10
  47. package/dist/composition/chem-sys.d.ts +8 -0
  48. package/dist/composition/chem-sys.js +86 -0
  49. package/dist/composition/format.js +7 -4
  50. package/dist/composition/index.d.ts +1 -0
  51. package/dist/composition/index.js +1 -0
  52. package/dist/composition/parse.d.ts +0 -1
  53. package/dist/composition/parse.js +41 -31
  54. package/dist/controls.d.ts +1 -0
  55. package/dist/controls.js +0 -1
  56. package/dist/convex-hull/ConvexHull.svelte +8 -10
  57. package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
  58. package/dist/convex-hull/ConvexHull2D.svelte +106 -185
  59. package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
  60. package/dist/convex-hull/ConvexHull3D.svelte +179 -683
  61. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  62. package/dist/convex-hull/ConvexHull4D.svelte +183 -687
  63. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  64. package/dist/convex-hull/ConvexHullChrome.svelte +268 -0
  65. package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
  66. package/dist/convex-hull/ConvexHullControls.svelte +88 -7
  67. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
  68. package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
  69. package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
  70. package/dist/convex-hull/ConvexHullStats.svelte +36 -175
  71. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
  72. package/dist/convex-hull/ConvexHullTooltip.svelte +11 -2
  73. package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
  74. package/dist/convex-hull/GasPressureControls.svelte +4 -4
  75. package/dist/convex-hull/TemperatureSlider.svelte +2 -2
  76. package/dist/convex-hull/barycentric-coords.d.ts +2 -4
  77. package/dist/convex-hull/barycentric-coords.js +6 -33
  78. package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
  79. package/dist/convex-hull/canvas-interactions.svelte.js +278 -0
  80. package/dist/convex-hull/demo-temperature.d.ts +1 -1
  81. package/dist/convex-hull/demo-temperature.js +20 -22
  82. package/dist/convex-hull/gas-thermodynamics.d.ts +2 -2
  83. package/dist/convex-hull/gas-thermodynamics.js +22 -30
  84. package/dist/convex-hull/helpers.d.ts +42 -7
  85. package/dist/convex-hull/helpers.js +171 -78
  86. package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
  87. package/dist/convex-hull/hull-state.svelte.js +124 -0
  88. package/dist/convex-hull/index.d.ts +10 -8
  89. package/dist/convex-hull/index.js +7 -2
  90. package/dist/convex-hull/thermodynamics.js +136 -960
  91. package/dist/convex-hull/types.d.ts +13 -5
  92. package/dist/convex-hull/types.js +12 -0
  93. package/dist/coordination/CoordinationBarPlot.svelte +27 -34
  94. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
  95. package/dist/element/BohrAtom.svelte +2 -1
  96. package/dist/element/index.d.ts +4 -0
  97. package/dist/element/index.js +18 -0
  98. package/dist/feedback/DragOverlay.svelte +3 -1
  99. package/dist/feedback/DragOverlay.svelte.d.ts +1 -0
  100. package/dist/feedback/StatusMessage.svelte +13 -3
  101. package/dist/fermi-surface/FermiSlice.svelte +13 -5
  102. package/dist/fermi-surface/FermiSurface.svelte +78 -151
  103. package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -14
  104. package/dist/fermi-surface/FermiSurfaceControls.svelte +1 -1
  105. package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
  106. package/dist/fermi-surface/FermiSurfaceScene.svelte +72 -221
  107. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
  108. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +8 -34
  109. package/dist/fermi-surface/compute.js +67 -66
  110. package/dist/fermi-surface/export.js +6 -16
  111. package/dist/fermi-surface/index.d.ts +0 -1
  112. package/dist/fermi-surface/index.js +0 -1
  113. package/dist/fermi-surface/parse.d.ts +1 -1
  114. package/dist/fermi-surface/parse.js +71 -79
  115. package/dist/fermi-surface/types.d.ts +3 -2
  116. package/dist/heatmap-matrix/HeatmapMatrix.svelte +69 -52
  117. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +4 -3
  118. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +3 -2
  119. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
  120. package/dist/heatmap-matrix/index.d.ts +3 -2
  121. package/dist/heatmap-matrix/index.js +1 -1
  122. package/dist/index.d.ts +1 -0
  123. package/dist/index.js +1 -0
  124. package/dist/io/ExportPane.svelte +166 -0
  125. package/dist/io/ExportPane.svelte.d.ts +17 -0
  126. package/dist/io/decompress.js +5 -4
  127. package/dist/io/export.d.ts +9 -5
  128. package/dist/io/export.js +77 -51
  129. package/dist/io/fetch.d.ts +2 -1
  130. package/dist/io/fetch.js +5 -1
  131. package/dist/io/file-drop.d.ts +8 -1
  132. package/dist/io/file-drop.js +48 -36
  133. package/dist/io/index.d.ts +2 -0
  134. package/dist/io/index.js +10 -0
  135. package/dist/io/types.d.ts +13 -0
  136. package/dist/io/url-drop.js +64 -33
  137. package/dist/isosurface/parse.js +52 -51
  138. package/dist/isosurface/slice.js +5 -4
  139. package/dist/isosurface/types.js +1 -1
  140. package/dist/keyboard.d.ts +3 -0
  141. package/dist/keyboard.js +23 -0
  142. package/dist/labels.d.ts +1 -1
  143. package/dist/labels.js +9 -8
  144. package/dist/layout/FullscreenButton.svelte +33 -0
  145. package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
  146. package/dist/layout/FullscreenToggle.svelte +8 -14
  147. package/dist/layout/PropertyFilter.svelte +3 -2
  148. package/dist/layout/SettingsSection.svelte +1 -1
  149. package/dist/layout/ViewerChrome.svelte +116 -0
  150. package/dist/layout/ViewerChrome.svelte.d.ts +17 -0
  151. package/dist/layout/fullscreen.d.ts +4 -0
  152. package/dist/layout/fullscreen.svelte.d.ts +8 -0
  153. package/dist/layout/fullscreen.svelte.js +37 -0
  154. package/dist/layout/index.d.ts +3 -0
  155. package/dist/layout/index.js +3 -0
  156. package/dist/layout/json-tree/JsonNode.svelte +1 -1
  157. package/dist/layout/json-tree/JsonTree.svelte +2 -2
  158. package/dist/layout/json-tree/utils.js +5 -4
  159. package/dist/marching-cubes.js +8 -13
  160. package/dist/math.d.ts +12 -4
  161. package/dist/math.js +42 -30
  162. package/dist/overlays/DraggablePane.svelte +4 -4
  163. package/dist/overlays/index.d.ts +4 -0
  164. package/dist/periodic-table/PeriodicTable.svelte +27 -15
  165. package/dist/periodic-table/PropertySelect.svelte +1 -0
  166. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +9 -3
  167. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
  168. package/dist/phase-diagram/PhaseDiagramControls.svelte +3 -2
  169. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
  170. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +4 -2
  171. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
  172. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +47 -132
  173. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
  174. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +1 -1
  175. package/dist/phase-diagram/build-diagram.js +2 -2
  176. package/dist/phase-diagram/colors.js +1 -1
  177. package/dist/phase-diagram/parse.d.ts +2 -1
  178. package/dist/phase-diagram/parse.js +6 -5
  179. package/dist/phase-diagram/types.d.ts +1 -1
  180. package/dist/phase-diagram/utils.d.ts +3 -3
  181. package/dist/phase-diagram/utils.js +8 -12
  182. package/dist/plot/{BarPlot.svelte → bar/BarPlot.svelte} +246 -841
  183. package/dist/plot/{BarPlot.svelte.d.ts → bar/BarPlot.svelte.d.ts} +8 -16
  184. package/dist/plot/{BarPlotControls.svelte → bar/BarPlotControls.svelte} +6 -5
  185. package/dist/plot/{BarPlotControls.svelte.d.ts → bar/BarPlotControls.svelte.d.ts} +3 -3
  186. package/dist/plot/{SpacegroupBarPlot.svelte → bar/SpacegroupBarPlot.svelte} +8 -7
  187. package/dist/plot/{SpacegroupBarPlot.svelte.d.ts → bar/SpacegroupBarPlot.svelte.d.ts} +1 -1
  188. package/dist/plot/bar/data.d.ts +40 -0
  189. package/dist/plot/bar/data.js +154 -0
  190. package/dist/plot/bar/geometry.d.ts +39 -0
  191. package/dist/plot/bar/geometry.js +60 -0
  192. package/dist/plot/bar/index.d.ts +3 -0
  193. package/dist/plot/bar/index.js +3 -0
  194. package/dist/plot/box/BoxPlot.svelte +1292 -0
  195. package/dist/plot/box/BoxPlot.svelte.d.ts +95 -0
  196. package/dist/plot/box/BoxPlotControls.svelte +109 -0
  197. package/dist/plot/box/BoxPlotControls.svelte.d.ts +19 -0
  198. package/dist/plot/box/Violin.svelte +14 -0
  199. package/dist/plot/box/Violin.svelte.d.ts +70 -0
  200. package/dist/plot/box/box-plot.d.ts +56 -0
  201. package/dist/plot/box/box-plot.js +129 -0
  202. package/dist/plot/box/index.d.ts +5 -0
  203. package/dist/plot/box/index.js +5 -0
  204. package/dist/plot/box/kde.d.ts +17 -0
  205. package/dist/plot/box/kde.js +160 -0
  206. package/dist/plot/box/quantile.d.ts +3 -0
  207. package/dist/plot/box/quantile.js +53 -0
  208. package/dist/plot/{auto-place.d.ts → core/auto-place.d.ts} +1 -1
  209. package/dist/plot/{auto-place.js → core/auto-place.js} +6 -3
  210. package/dist/plot/core/axis-utils.d.ts +46 -0
  211. package/dist/plot/core/axis-utils.js +110 -0
  212. package/dist/plot/{AxisLabel.svelte → core/components/AxisLabel.svelte} +2 -2
  213. package/dist/plot/{AxisLabel.svelte.d.ts → core/components/AxisLabel.svelte.d.ts} +1 -1
  214. package/dist/plot/{ColorBar.svelte → core/components/ColorBar.svelte} +41 -38
  215. package/dist/plot/{ColorBar.svelte.d.ts → core/components/ColorBar.svelte.d.ts} +7 -6
  216. package/dist/plot/{ColorScaleSelect.svelte → core/components/ColorScaleSelect.svelte} +4 -3
  217. package/dist/plot/{ColorScaleSelect.svelte.d.ts → core/components/ColorScaleSelect.svelte.d.ts} +2 -2
  218. package/dist/plot/core/components/ControlPane.svelte +46 -0
  219. package/dist/plot/core/components/ControlPane.svelte.d.ts +13 -0
  220. package/dist/plot/{FillArea.svelte → core/components/FillArea.svelte} +17 -6
  221. package/dist/plot/{FillArea.svelte.d.ts → core/components/FillArea.svelte.d.ts} +1 -1
  222. package/dist/plot/{InteractiveAxisLabel.svelte → core/components/InteractiveAxisLabel.svelte} +3 -3
  223. package/dist/plot/{InteractiveAxisLabel.svelte.d.ts → core/components/InteractiveAxisLabel.svelte.d.ts} +2 -2
  224. package/dist/plot/{Line.svelte → core/components/Line.svelte} +33 -15
  225. package/dist/plot/{Line.svelte.d.ts → core/components/Line.svelte.d.ts} +3 -2
  226. package/dist/plot/{PlotAxis.svelte → core/components/PlotAxis.svelte} +9 -6
  227. package/dist/plot/{PlotAxis.svelte.d.ts → core/components/PlotAxis.svelte.d.ts} +5 -3
  228. package/dist/plot/{PlotControls.svelte → core/components/PlotControls.svelte} +17 -29
  229. package/dist/plot/core/components/PlotControls.svelte.d.ts +4 -0
  230. package/dist/plot/{PlotLegend.svelte → core/components/PlotLegend.svelte} +21 -10
  231. package/dist/plot/{PlotLegend.svelte.d.ts → core/components/PlotLegend.svelte.d.ts} +3 -2
  232. package/dist/plot/{PlotTooltip.svelte → core/components/PlotTooltip.svelte} +17 -1
  233. package/dist/plot/{PlotTooltip.svelte.d.ts → core/components/PlotTooltip.svelte.d.ts} +8 -0
  234. package/dist/plot/{PortalSelect.svelte → core/components/PortalSelect.svelte} +11 -7
  235. package/dist/plot/{ReferenceLine.svelte → core/components/ReferenceLine.svelte} +3 -3
  236. package/dist/plot/{ReferenceLine.svelte.d.ts → core/components/ReferenceLine.svelte.d.ts} +1 -1
  237. package/dist/plot/{ReferenceLine3D.svelte → core/components/ReferenceLine3D.svelte} +5 -5
  238. package/dist/plot/{ReferenceLine3D.svelte.d.ts → core/components/ReferenceLine3D.svelte.d.ts} +5 -5
  239. package/dist/plot/{ReferencePlane.svelte → core/components/ReferencePlane.svelte} +8 -8
  240. package/dist/plot/{ReferencePlane.svelte.d.ts → core/components/ReferencePlane.svelte.d.ts} +5 -5
  241. package/dist/plot/{ZeroLines.svelte → core/components/ZeroLines.svelte} +3 -3
  242. package/dist/plot/{ZeroLines.svelte.d.ts → core/components/ZeroLines.svelte.d.ts} +3 -3
  243. package/dist/plot/{ZoomRect.svelte → core/components/ZoomRect.svelte} +1 -1
  244. package/dist/plot/{ZoomRect.svelte.d.ts → core/components/ZoomRect.svelte.d.ts} +1 -1
  245. package/dist/plot/core/components/index.d.ts +17 -0
  246. package/dist/plot/core/components/index.js +17 -0
  247. package/dist/plot/{data-cleaning.d.ts → core/data-cleaning.d.ts} +71 -1
  248. package/dist/plot/{data-cleaning.js → core/data-cleaning.js} +21 -23
  249. package/dist/plot/{data-transform.d.ts → core/data-transform.d.ts} +2 -2
  250. package/dist/plot/{data-transform.js → core/data-transform.js} +3 -3
  251. package/dist/plot/core/fill-utils.d.ts +34 -0
  252. package/dist/plot/core/fill-utils.js +391 -0
  253. package/dist/plot/core/index.d.ts +10 -0
  254. package/dist/plot/core/index.js +11 -0
  255. package/dist/plot/core/interactions.d.ts +39 -0
  256. package/dist/plot/core/interactions.js +209 -0
  257. package/dist/plot/{layout.d.ts → core/layout.d.ts} +1 -0
  258. package/dist/plot/{layout.js → core/layout.js} +16 -8
  259. package/dist/plot/core/pan-zoom.svelte.d.ts +35 -0
  260. package/dist/plot/core/pan-zoom.svelte.js +221 -0
  261. package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
  262. package/dist/plot/core/placed-tween.svelte.js +68 -0
  263. package/dist/plot/{reference-line.d.ts → core/reference-line.d.ts} +11 -11
  264. package/dist/plot/{reference-line.js → core/reference-line.js} +29 -42
  265. package/dist/plot/core/scales.d.ts +40 -0
  266. package/dist/plot/{scales.js → core/scales.js} +94 -93
  267. package/dist/plot/core/svg.d.ts +3 -0
  268. package/dist/plot/core/svg.js +41 -0
  269. package/dist/plot/{types.d.ts → core/types.d.ts} +36 -85
  270. package/dist/plot/{types.js → core/types.js} +1 -1
  271. package/dist/plot/{utils → core/utils}/label-placement.d.ts +3 -3
  272. package/dist/plot/{utils → core/utils}/label-placement.js +3 -3
  273. package/dist/plot/core/utils/series-visibility.d.ts +26 -0
  274. package/dist/plot/{utils → core/utils}/series-visibility.js +29 -2
  275. package/dist/plot/core/utils.d.ts +12 -0
  276. package/dist/plot/core/utils.js +27 -0
  277. package/dist/plot/{Histogram.svelte → histogram/Histogram.svelte} +174 -551
  278. package/dist/plot/{Histogram.svelte.d.ts → histogram/Histogram.svelte.d.ts} +2 -2
  279. package/dist/plot/{HistogramControls.svelte → histogram/HistogramControls.svelte} +6 -6
  280. package/dist/plot/{HistogramControls.svelte.d.ts → histogram/HistogramControls.svelte.d.ts} +4 -4
  281. package/dist/plot/histogram/index.d.ts +2 -0
  282. package/dist/plot/histogram/index.js +2 -0
  283. package/dist/plot/index.d.ts +8 -41
  284. package/dist/plot/index.js +10 -39
  285. package/dist/plot/sankey/Sankey.svelte +697 -0
  286. package/dist/plot/sankey/Sankey.svelte.d.ts +74 -0
  287. package/dist/plot/sankey/SankeyControls.svelte +98 -0
  288. package/dist/plot/sankey/SankeyControls.svelte.d.ts +19 -0
  289. package/dist/plot/sankey/index.d.ts +4 -0
  290. package/dist/plot/sankey/index.js +3 -0
  291. package/dist/plot/sankey/sankey-types.d.ts +42 -0
  292. package/dist/plot/sankey/sankey-types.js +4 -0
  293. package/dist/plot/sankey/sankey.d.ts +52 -0
  294. package/dist/plot/sankey/sankey.js +189 -0
  295. package/dist/plot/{BinnedScatterPlot.svelte → scatter/BinnedScatterPlot.svelte} +64 -64
  296. package/dist/plot/{BinnedScatterPlot.svelte.d.ts → scatter/BinnedScatterPlot.svelte.d.ts} +6 -6
  297. package/dist/plot/{ElementScatter.svelte → scatter/ElementScatter.svelte} +6 -6
  298. package/dist/plot/{ElementScatter.svelte.d.ts → scatter/ElementScatter.svelte.d.ts} +2 -2
  299. package/dist/plot/{ScatterPlot.svelte → scatter/ScatterPlot.svelte} +297 -1008
  300. package/dist/plot/{ScatterPlot.svelte.d.ts → scatter/ScatterPlot.svelte.d.ts} +10 -18
  301. package/dist/plot/{ScatterPlotControls.svelte → scatter/ScatterPlotControls.svelte} +6 -5
  302. package/dist/plot/{ScatterPlotControls.svelte.d.ts → scatter/ScatterPlotControls.svelte.d.ts} +2 -2
  303. package/dist/plot/{ScatterPoint.svelte → scatter/ScatterPoint.svelte} +7 -7
  304. package/dist/plot/{ScatterPoint.svelte.d.ts → scatter/ScatterPoint.svelte.d.ts} +3 -3
  305. package/dist/plot/{adaptive-density.d.ts → scatter/adaptive-density.d.ts} +14 -4
  306. package/dist/plot/{adaptive-density.js → scatter/adaptive-density.js} +46 -20
  307. package/dist/plot/{binned-scatter-types.d.ts → scatter/binned-scatter-types.d.ts} +5 -12
  308. package/dist/plot/scatter/index.d.ts +7 -0
  309. package/dist/plot/scatter/index.js +5 -0
  310. package/dist/plot/scatter/scatter-data.d.ts +19 -0
  311. package/dist/plot/scatter/scatter-data.js +212 -0
  312. package/dist/plot/{ScatterPlot3D.svelte → scatter-3d/ScatterPlot3D.svelte} +25 -34
  313. package/dist/plot/{ScatterPlot3D.svelte.d.ts → scatter-3d/ScatterPlot3D.svelte.d.ts} +9 -17
  314. package/dist/plot/{ScatterPlot3DControls.svelte → scatter-3d/ScatterPlot3DControls.svelte} +14 -14
  315. package/dist/plot/{ScatterPlot3DControls.svelte.d.ts → scatter-3d/ScatterPlot3DControls.svelte.d.ts} +6 -6
  316. package/dist/plot/{ScatterPlot3DScene.svelte → scatter-3d/ScatterPlot3DScene.svelte} +129 -128
  317. package/dist/plot/{ScatterPlot3DScene.svelte.d.ts → scatter-3d/ScatterPlot3DScene.svelte.d.ts} +6 -15
  318. package/dist/plot/{Surface3D.svelte → scatter-3d/Surface3D.svelte} +7 -6
  319. package/dist/plot/{Surface3D.svelte.d.ts → scatter-3d/Surface3D.svelte.d.ts} +5 -4
  320. package/dist/plot/scatter-3d/index.d.ts +4 -0
  321. package/dist/plot/scatter-3d/index.js +4 -0
  322. package/dist/plot/sunburst/Sunburst.svelte +1041 -0
  323. package/dist/plot/sunburst/Sunburst.svelte.d.ts +97 -0
  324. package/dist/plot/sunburst/SunburstControls.svelte +200 -0
  325. package/dist/plot/sunburst/SunburstControls.svelte.d.ts +26 -0
  326. package/dist/plot/sunburst/index.d.ts +4 -0
  327. package/dist/plot/sunburst/index.js +4 -0
  328. package/dist/plot/sunburst/render.d.ts +34 -0
  329. package/dist/plot/sunburst/render.js +122 -0
  330. package/dist/plot/sunburst/sunburst.d.ts +62 -0
  331. package/dist/plot/sunburst/sunburst.js +269 -0
  332. package/dist/rdf/RdfPlot.svelte +2 -1
  333. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  334. package/dist/rdf/calc-rdf.js +11 -24
  335. package/dist/sanitize.js +14 -3
  336. package/dist/scene/SceneCamera.svelte +62 -0
  337. package/dist/scene/SceneCamera.svelte.d.ts +19 -0
  338. package/dist/scene/bind-renderer.svelte.d.ts +2 -0
  339. package/dist/scene/bind-renderer.svelte.js +14 -0
  340. package/dist/scene/index.d.ts +4 -0
  341. package/dist/scene/index.js +5 -0
  342. package/dist/scene/props.js +52 -0
  343. package/dist/scene/types.d.ts +26 -0
  344. package/dist/scene/types.js +1 -0
  345. package/dist/settings.d.ts +79 -3
  346. package/dist/settings.js +321 -1
  347. package/dist/spectral/Bands.svelte +47 -36
  348. package/dist/spectral/Bands.svelte.d.ts +6 -6
  349. package/dist/spectral/BandsAndDos.svelte +23 -25
  350. package/dist/spectral/BrillouinBandsDos.svelte +42 -30
  351. package/dist/spectral/Dos.svelte +15 -23
  352. package/dist/spectral/Dos.svelte.d.ts +4 -3
  353. package/dist/spectral/helpers.d.ts +8 -6
  354. package/dist/spectral/helpers.js +137 -65
  355. package/dist/state.svelte.d.ts +0 -7
  356. package/dist/state.svelte.js +0 -6
  357. package/dist/structure/Arrow.svelte +2 -4
  358. package/dist/structure/AtomLegend.svelte +8 -9
  359. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  360. package/dist/structure/CanvasTooltip.svelte +1 -0
  361. package/dist/structure/CellSelect.svelte +12 -5
  362. package/dist/structure/CellSelect.svelte.d.ts +2 -1
  363. package/dist/structure/Cylinder.svelte +12 -8
  364. package/dist/structure/Cylinder.svelte.d.ts +4 -1
  365. package/dist/structure/Lattice.svelte +2 -2
  366. package/dist/structure/Structure.svelte +365 -423
  367. package/dist/structure/Structure.svelte.d.ts +5 -15
  368. package/dist/structure/StructureControls.svelte +217 -2
  369. package/dist/structure/StructureControls.svelte.d.ts +5 -3
  370. package/dist/structure/StructureExportPane.svelte +54 -156
  371. package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
  372. package/dist/structure/StructureInfoPane.svelte +10 -9
  373. package/dist/structure/StructureInfoPane.svelte.d.ts +5 -5
  374. package/dist/structure/StructureScene.svelte +376 -208
  375. package/dist/structure/StructureScene.svelte.d.ts +22 -20
  376. package/dist/structure/{label-placement.d.ts → atom-label-placement.d.ts} +3 -3
  377. package/dist/structure/{label-placement.js → atom-label-placement.js} +15 -5
  378. package/dist/structure/atom-properties.d.ts +1 -1
  379. package/dist/structure/atom-properties.js +17 -22
  380. package/dist/structure/bond-order-perception.js +3 -5
  381. package/dist/structure/bonding.d.ts +4 -0
  382. package/dist/structure/bonding.js +134 -63
  383. package/dist/structure/export.d.ts +24 -4
  384. package/dist/structure/export.js +89 -143
  385. package/dist/structure/index.d.ts +4 -4
  386. package/dist/structure/index.js +3 -3
  387. package/dist/structure/measure.d.ts +3 -2
  388. package/dist/structure/measure.js +6 -5
  389. package/dist/structure/parse.d.ts +3 -2
  390. package/dist/structure/parse.js +419 -438
  391. package/dist/structure/partial-occupancy.d.ts +0 -1
  392. package/dist/structure/partial-occupancy.js +1 -1
  393. package/dist/structure/pbc.d.ts +1 -1
  394. package/dist/structure/pbc.js +190 -13
  395. package/dist/structure/polyhedra.d.ts +41 -0
  396. package/dist/structure/polyhedra.js +602 -0
  397. package/dist/structure/site.d.ts +4 -0
  398. package/dist/structure/site.js +1 -0
  399. package/dist/structure/supercell.js +3 -2
  400. package/dist/structure/validation.js +5 -6
  401. package/dist/symmetry/SymmetryElementControls.svelte +69 -0
  402. package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
  403. package/dist/symmetry/SymmetryElements.svelte +354 -0
  404. package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
  405. package/dist/symmetry/SymmetryStats.svelte +113 -8
  406. package/dist/symmetry/WyckoffTable.svelte +68 -7
  407. package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
  408. package/dist/symmetry/cell-transform.js +7 -14
  409. package/dist/symmetry/index.d.ts +14 -4
  410. package/dist/symmetry/index.js +291 -72
  411. package/dist/symmetry/spacegroups.d.ts +12 -1
  412. package/dist/symmetry/spacegroups.js +63 -14
  413. package/dist/symmetry/symmetry-elements.d.ts +33 -0
  414. package/dist/symmetry/symmetry-elements.js +521 -0
  415. package/dist/symmetry/wyckoff-db.d.ts +9 -0
  416. package/dist/symmetry/wyckoff-db.js +87 -0
  417. package/dist/table/HeatmapTable.svelte +66 -25
  418. package/dist/table/HeatmapTable.svelte.d.ts +1 -1
  419. package/dist/table/index.d.ts +1 -3
  420. package/dist/table/index.js +1 -1
  421. package/dist/theme/index.js +8 -8
  422. package/dist/tooltip/KCoords.svelte +45 -0
  423. package/dist/tooltip/KCoords.svelte.d.ts +8 -0
  424. package/dist/tooltip/index.d.ts +1 -0
  425. package/dist/tooltip/index.js +1 -0
  426. package/dist/trajectory/Trajectory.svelte +123 -100
  427. package/dist/trajectory/Trajectory.svelte.d.ts +11 -22
  428. package/dist/trajectory/TrajectoryExportPane.svelte +17 -25
  429. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
  430. package/dist/trajectory/TrajectoryInfoPane.svelte +5 -3
  431. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
  432. package/dist/trajectory/constants.js +6 -2
  433. package/dist/trajectory/extract.js +17 -37
  434. package/dist/trajectory/format-detect.d.ts +1 -1
  435. package/dist/trajectory/format-detect.js +27 -19
  436. package/dist/trajectory/frame-reader.d.ts +0 -1
  437. package/dist/trajectory/frame-reader.js +63 -162
  438. package/dist/trajectory/helpers.d.ts +10 -2
  439. package/dist/trajectory/helpers.js +56 -36
  440. package/dist/trajectory/index.js +1 -1
  441. package/dist/trajectory/parse/ase.d.ts +9 -1
  442. package/dist/trajectory/parse/ase.js +47 -32
  443. package/dist/trajectory/parse/diagnostics.d.ts +3 -0
  444. package/dist/trajectory/parse/diagnostics.js +14 -0
  445. package/dist/trajectory/parse/hdf5.js +1 -1
  446. package/dist/trajectory/parse/index.d.ts +1 -1
  447. package/dist/trajectory/parse/index.js +65 -105
  448. package/dist/trajectory/parse/lammps.d.ts +0 -2
  449. package/dist/trajectory/parse/lammps.js +8 -6
  450. package/dist/trajectory/parse/pymatgen.d.ts +2 -0
  451. package/dist/trajectory/parse/pymatgen.js +74 -0
  452. package/dist/trajectory/parse/vasp.js +38 -18
  453. package/dist/trajectory/parse/xyz.d.ts +13 -1
  454. package/dist/trajectory/parse/xyz.js +102 -94
  455. package/dist/trajectory/plotting.d.ts +1 -2
  456. package/dist/trajectory/plotting.js +16 -113
  457. package/dist/utils.d.ts +2 -0
  458. package/dist/utils.js +7 -5
  459. package/dist/xrd/XrdPlot.svelte +16 -30
  460. package/dist/xrd/broadening.d.ts +2 -1
  461. package/dist/xrd/calc-xrd.js +18 -20
  462. package/dist/xrd/index.d.ts +2 -2
  463. package/dist/xrd/parse.js +2 -2
  464. package/package.json +43 -26
  465. package/dist/element/data.json +0 -11864
  466. package/dist/fermi-surface/marching-cubes.d.ts +0 -2
  467. package/dist/fermi-surface/marching-cubes.js +0 -2
  468. package/dist/plot/PlotControls.svelte.d.ts +0 -4
  469. package/dist/plot/axis-utils.d.ts +0 -19
  470. package/dist/plot/axis-utils.js +0 -78
  471. package/dist/plot/defaults.d.ts +0 -19
  472. package/dist/plot/defaults.js +0 -9
  473. package/dist/plot/fill-utils.d.ts +0 -46
  474. package/dist/plot/fill-utils.js +0 -322
  475. package/dist/plot/hover-lock.svelte.d.ts +0 -14
  476. package/dist/plot/hover-lock.svelte.js +0 -46
  477. package/dist/plot/interactions.d.ts +0 -12
  478. package/dist/plot/interactions.js +0 -101
  479. package/dist/plot/scales.d.ts +0 -48
  480. package/dist/plot/svg.d.ts +0 -1
  481. package/dist/plot/svg.js +0 -11
  482. package/dist/plot/utils/series-visibility.d.ts +0 -15
  483. package/dist/plot/utils.d.ts +0 -1
  484. package/dist/plot/utils.js +0 -14
  485. /package/dist/plot/{PortalSelect.svelte.d.ts → core/components/PortalSelect.svelte.d.ts} +0 -0
  486. /package/dist/plot/{binned-scatter-types.js → scatter/binned-scatter-types.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { D3InterpolateName } from '../colors'
3
- import { AXIS_COLORS, get_d3_interpolator, NEG_AXIS_COLORS } from '../colors'
3
+ import { get_d3_interpolator } from '../colors'
4
4
  import type { ElementSymbol } from '../element'
5
5
  import { element_data } from '../element'
6
6
  import Isosurface from '../isosurface/Isosurface.svelte'
@@ -9,12 +9,9 @@
9
9
  import { format_num } from '../labels'
10
10
  import type { Vec3 } from '../math'
11
11
  import * as math from '../math'
12
- import type {
13
- CameraProjection,
14
- ShowBonds,
15
- VectorColorMode,
16
- VectorLayerConfig,
17
- } from '../settings'
12
+ import { bind_renderer, build_orbit_props, SceneCamera } from '../scene'
13
+ import type { SceneControlProps } from '../scene'
14
+ import type { ShowBonds, VectorColorMode, VectorLayerConfig } from '../settings'
18
15
  import { DEFAULTS } from '../settings'
19
16
  import { create_pulse_animation } from '../effects.svelte'
20
17
  import { colors } from '../state.svelte'
@@ -42,6 +39,9 @@
42
39
  get_orig_site_idx,
43
40
  get_property_colors,
44
41
  } from './atom-properties'
42
+ import type { SymmetryElement } from '../symmetry'
43
+ import { has_visible_symmetry_overlay } from '../symmetry/symmetry-elements'
44
+ import SymmetryElements from '../symmetry/SymmetryElements.svelte'
45
45
  import * as measure from './measure'
46
46
  import {
47
47
  compute_slice_geometry,
@@ -49,11 +49,12 @@
49
49
  PARTIAL_OCCUPANCY_CAP_ARC,
50
50
  } from './partial-occupancy'
51
51
  import type { MoyoDataset } from '@spglib/moyo-wasm'
52
- import { T, useThrelte } from '@threlte/core'
52
+ import { T } from '@threlte/core'
53
53
  import * as extras from '@threlte/extras'
54
54
  import { type ComponentProps, type Snippet, untrack } from 'svelte'
55
55
  import { SvelteMap, SvelteSet } from 'svelte/reactivity'
56
- import { type Camera, Color, type Mesh, type Object3D, type Scene, Vector3 } from 'three'
56
+ import { BufferAttribute, BufferGeometry, Color, DoubleSide, Vector3 } from 'three'
57
+ import type { Mesh, Object3D } from 'three'
57
58
  import Bond from './Bond.svelte'
58
59
  import type { BondEditResult, BondingStrategy, BondKeyTarget } from './bonding'
59
60
  import {
@@ -65,6 +66,7 @@
65
66
  get_bond_key,
66
67
  get_bond_render_matrices,
67
68
  get_explicit_bond_metadata,
69
+ get_majority_element,
68
70
  set_bond_order as apply_set_bond_order,
69
71
  structure_bond_to_bond_pair,
70
72
  } from './bonding'
@@ -77,7 +79,9 @@
77
79
  choose_site_label_offset,
78
80
  LABEL_OFFSET_EPS,
79
81
  make_label_position_calculator,
80
- } from './label-placement'
82
+ } from './atom-label-placement'
83
+ import type { PolyhedraColorMode, Polyhedron } from './polyhedra'
84
+ import { compute_polyhedra, merge_polyhedra_buffers } from './polyhedra'
81
85
 
82
86
  type InstancedAtomGroup = {
83
87
  element: string
@@ -168,12 +172,27 @@
168
172
  auto_bond_order = DEFAULTS.structure.auto_bond_order,
169
173
  aromatic_display = DEFAULTS.structure.aromatic_display,
170
174
  bonding_options = {},
175
+ show_polyhedra = DEFAULTS.structure.show_polyhedra,
176
+ polyhedra_opacity = DEFAULTS.structure.polyhedra_opacity,
177
+ polyhedra_show_edges = DEFAULTS.structure.polyhedra_show_edges,
178
+ polyhedra_edge_color = DEFAULTS.structure.polyhedra_edge_color,
179
+ polyhedra_color_mode = DEFAULTS.structure.polyhedra_color_mode,
180
+ polyhedra_color = DEFAULTS.structure.polyhedra_color,
181
+ polyhedra_hide_center_atoms = DEFAULTS.structure.polyhedra_hide_center_atoms,
182
+ polyhedra_min_neighbors = DEFAULTS.structure.polyhedra_min_neighbors,
183
+ polyhedra_max_neighbors = DEFAULTS.structure.polyhedra_max_neighbors,
184
+ polyhedra_excluded_elements = DEFAULTS.structure.polyhedra_excluded_elements,
185
+ polyhedra_included_elements = DEFAULTS.structure.polyhedra_included_elements,
186
+ polyhedra_rendered_elements = $bindable<string[]>([]),
171
187
  fov = DEFAULTS.structure.fov,
172
188
  initial_zoom = DEFAULTS.structure.initial_zoom,
173
189
  ambient_light = DEFAULTS.structure.ambient_light,
174
190
  directional_light = DEFAULTS.structure.directional_light,
175
191
  sphere_segments = DEFAULTS.structure.sphere_segments,
176
192
  lattice_props = {},
193
+ symmetry_elements = [],
194
+ symmetry_elements_props = {},
195
+ symmetry_declutter = true,
177
196
  atom_label,
178
197
  camera_is_moving = $bindable(false),
179
198
  width = 0,
@@ -218,7 +237,7 @@
218
237
  dragging_atoms = $bindable(false),
219
238
  volumetric_data = undefined,
220
239
  isosurface_settings = DEFAULT_ISOSURFACE_SETTINGS,
221
- }: {
240
+ }: SceneControlProps & {
222
241
  structure?: AnyStructure
223
242
  base_structure?: AnyStructure // The original structure without image atoms, used for property color calculation
224
243
  atom_radius?: number // scale factor for atomic radii
@@ -226,15 +245,6 @@
226
245
  // determined by the atomic radius of the element
227
246
  camera_position?: [x: number, y: number, z: number] // initial camera position from which to render the scene
228
247
  camera_target?: Vec3 // external orbit-controls target for pan synchronization
229
- camera_projection?: CameraProjection // camera projection type
230
- rotation_damping?: number // rotation damping factor (how quickly the rotation comes to rest after mouse release)
231
- // zoom level of the camera
232
- max_zoom?: number
233
- min_zoom?: number
234
- rotate_speed?: number // rotation speed. set to 0 to disable rotation.
235
- zoom_speed?: number // zoom speed. set to 0 to disable zooming.
236
- pan_speed?: number // pan speed. set to 0 to disable panning.
237
- zoom_to_cursor?: boolean // zoom toward cursor position instead of scene center
238
248
  show_atoms?: boolean
239
249
  show_bonds?: ShowBonds
240
250
  show_site_labels?: boolean
@@ -250,30 +260,46 @@
250
260
  vector_shaft_radius?: number
251
261
  vector_arrow_head_radius?: number
252
262
  vector_arrow_head_length?: number
253
- gizmo?: boolean | ComponentProps<typeof extras.Gizmo>
254
263
  hovered_idx?: number | null
255
264
  hovered_site?: Site | null
256
265
  float_fmt?: string
257
- auto_rotate?: number
258
- initial_zoom?: number
259
266
  bond_thickness?: number
260
267
  bond_color?: string
261
268
  bonding_strategy?: BondingStrategy
262
269
  auto_bond_order?: boolean
263
270
  aromatic_display?: `aromatic` | `kekule`
264
271
  bonding_options?: Record<string, unknown>
265
- fov?: number
266
- ambient_light?: number
267
- directional_light?: number
272
+ show_polyhedra?: ShowBonds // when to render coordination polyhedra
273
+ polyhedra_opacity?: number
274
+ polyhedra_show_edges?: boolean
275
+ polyhedra_edge_color?: string
276
+ polyhedra_color_mode?: PolyhedraColorMode
277
+ polyhedra_color?: string // custom color used when polyhedra_color_mode is 'uniform'
278
+ polyhedra_hide_center_atoms?: boolean
279
+ polyhedra_min_neighbors?: number // min coordination number to form a polyhedron
280
+ polyhedra_max_neighbors?: number // max CN - skips e.g. CN-12 cuboctahedra
281
+ polyhedra_excluded_elements?: readonly string[] // elements never used as polyhedra centers
282
+ polyhedra_included_elements?: readonly string[] // force-include (bypasses spectator hiding)
283
+ polyhedra_rendered_elements?: string[] // (output) elements that currently have polyhedra
268
284
  sphere_segments?: number
269
285
  lattice_props?: ComponentProps<typeof Lattice>
286
+ // Symmetry elements (from symmetry_elements_from_ops) to overlay on the structure.
287
+ // Fractional coords must refer to the SAME cell as the rendered lattice (moyo
288
+ // operations are in the input-cell frame, i.e. the original untransformed cell).
289
+ symmetry_elements?: SymmetryElement[]
290
+ symmetry_elements_props?: Omit<ComponentProps<typeof SymmetryElements>, `elements` | `lattice`>
291
+ // Auto-reduce visual clutter while a symmetry-element overlay is visible: hides
292
+ // coordination polyhedra and calculated bonds, and shrinks atoms so axes/planes/
293
+ // centers stay readable. Purely derived — toggling the overlay off restores the
294
+ // configured appearance.
295
+ symmetry_declutter?: boolean
270
296
  atom_label?: Snippet<[{ site: Site; site_idx: number }]>
271
297
  site_label_size?: number
272
298
  site_label_offset?: Vec3
273
299
  site_label_bg_color?: string
274
300
  site_label_color?: string
275
301
  site_label_padding?: number
276
- camera_is_moving?: boolean // used to prevent tooltip from showing while camera is moving
302
+ camera_is_moving?: boolean // bindable: true while orbit controls are active
277
303
  width?: number // Viewer dimensions for responsive zoom
278
304
  height?: number
279
305
  // measurement props
@@ -291,9 +317,6 @@
291
317
  active_sites?: number[]
292
318
  active_highlight_color?: string
293
319
  rotation?: Vec3 // rotation control prop
294
- // Expose scene and camera for external use (e.g. export pane)
295
- scene?: Scene
296
- camera?: Camera
297
320
  orbit_controls?: ComponentProps<typeof extras.OrbitControls>[`ref`] // OrbitControls instance
298
321
  rotation_target_ref?: Vec3 // Expose rotation target for reset
299
322
  initial_computed_zoom?: number // Expose initial zoom for reset
@@ -322,13 +345,9 @@
322
345
  )
323
346
  let pulse_opacity = $derived(0.15 + 0.25 * pulse.unit)
324
347
 
325
- const threlte = useThrelte()
326
- $effect(() => {
327
- scene = threlte.scene
328
- camera = threlte.camera.current
329
- if (threlte.renderer) {
330
- Object.assign(threlte.renderer.domElement, { __renderer: threlte.renderer })
331
- }
348
+ bind_renderer((threlte_scene, threlte_camera) => {
349
+ scene = threlte_scene
350
+ camera = threlte_camera
332
351
  })
333
352
 
334
353
  // Expose rotation target for external reset
@@ -409,9 +428,8 @@
409
428
 
410
429
  // Desaturate a color by blending it toward gray (for ghosting image atoms in edit mode)
411
430
  const gray = new Color(0x999999)
412
- function desaturate(hex: string | undefined, amount = 0.4): string {
413
- return `#${new Color(hex ?? 0x999999).lerp(gray, amount).getHexString()}`
414
- }
431
+ const desaturate = (hex: string | undefined, amount = 0.4): string =>
432
+ `#${new Color(hex ?? 0x999999).lerp(gray, amount).getHexString()}`
415
433
 
416
434
  // === Edit-atoms mode state ===
417
435
  let transform_object = $state<Mesh | undefined>(undefined)
@@ -738,6 +756,27 @@
738
756
  return true
739
757
  }
740
758
 
759
+ // Pointer props (hover + select) shared by instanced atoms and partial-occupancy
760
+ // hit targets. is_edit_image disables interaction for ghosted PBC image atoms.
761
+ const atom_pointer_props = (site_idx: number, is_edit_image: boolean) => ({
762
+ ...atom_hover_props(site_idx, is_edit_image),
763
+ onpointerdown(event: PointerEvent) {
764
+ if (is_edit_image || measure_mode !== `edit-bonds` || bond_edit_mode !== `add`) return
765
+ select_edit_bonds_site(site_idx, event)
766
+ },
767
+ onclick(event: MouseEvent) {
768
+ if (is_edit_image) return
769
+ if (measure_mode === `edit-bonds`) {
770
+ if (bond_edit_mode !== `add`) return
771
+ if (skip_duplicate_edit_bonds_click(site_idx)) {
772
+ event.stopPropagation()
773
+ return
774
+ }
775
+ }
776
+ toggle_selection(site_idx, event)
777
+ },
778
+ })
779
+
741
780
  function toggle_selection(site_index: number, evt?: Event) {
742
781
  evt?.stopPropagation?.()
743
782
  const event_with_native = evt as Event & { nativeEvent?: unknown } | undefined
@@ -853,7 +892,7 @@
853
892
 
854
893
  let rotation_target = $derived(
855
894
  lattice
856
- ? (math.scale(math.add(...lattice.matrix), 0.5) as Vec3)
895
+ ? math.scale(math.add(...lattice.matrix), 0.5)
857
896
  : structure
858
897
  ? get_center_of_mass(structure)
859
898
  : [0, 0, 0] as Vec3,
@@ -924,16 +963,43 @@
924
963
  camera_position = [distance, distance * 0.3, distance * 0.8]
925
964
  }
926
965
  })
966
+ // Whether a never|always|crystals|molecules setting applies to the current structure
967
+ const applies_to_structure = (when: ShowBonds): boolean =>
968
+ when === `always` ||
969
+ (when === `crystals` && Boolean(lattice)) ||
970
+ (when === `molecules` && !lattice)
971
+
972
+ // Declutter while a symmetry-element overlay actually draws something (elements present
973
+ // AND an enabled kind among them): hide coordination polyhedra/bonds and shrink atoms so
974
+ // axes/planes/centers stay readable. Gating on visibility (not just `symmetry_elements`)
975
+ // avoids hiding everything when nothing renders in its place — e.g. an inversion-only
976
+ // cell under the rotation-only default. Pure derived overrides: the configured
977
+ // appearance returns untouched the moment the overlay goes away.
978
+ const declutter_active = $derived(
979
+ symmetry_declutter &&
980
+ has_visible_symmetry_overlay(symmetry_elements, symmetry_elements_props.show_kinds),
981
+ )
982
+ const effective_show_polyhedra: ShowBonds = $derived(
983
+ declutter_active ? `never` : show_polyhedra,
984
+ )
985
+ // Calculated bonds are hidden in declutter mode (only their cylinders — bond_pairs
986
+ // stay computed so tooltips and manually added bonds keep working)
987
+ const effective_show_bonds: ShowBonds = $derived(
988
+ declutter_active ? `never` : show_bonds,
989
+ )
990
+ const effective_atom_radius = $derived(declutter_active ? atom_radius * 0.6 : atom_radius)
991
+
927
992
  $effect(() => {
928
- if (structure && show_bonds !== `never`) {
929
- // Determine if we should show bonds based on the setting and structure type
930
- const should_show_bonds = show_bonds === `always` ||
931
- (show_bonds === `crystals` && lattice) ||
932
- (show_bonds === `molecules` && !lattice)
933
-
934
- if (should_show_bonds) {
935
- bond_pairs = BONDING_STRATEGIES[bonding_strategy](structure, bonding_options)
936
- } else bond_pairs = []
993
+ // Bonds are computed when either bond rendering or polyhedra need them. The
994
+ // raw/effective mix is deliberate: RAW show_bonds keeps bond_pairs available
995
+ // during symmetry declutter (cylinders hide via effective_show_bonds in
996
+ // bonds_to_render, but tooltips + manually added bonds still need the data),
997
+ // while EFFECTIVE show_polyhedra skips computing bonds whose only consumer —
998
+ // the polyhedra $derived below, gated on the same effective value — won't run.
999
+ const want_bonds = applies_to_structure(show_bonds)
1000
+ const want_polyhedra = applies_to_structure(effective_show_polyhedra)
1001
+ if (structure && (want_bonds || want_polyhedra)) {
1002
+ bond_pairs = BONDING_STRATEGIES[bonding_strategy](structure, bonding_options)
937
1003
  } else bond_pairs = []
938
1004
  })
939
1005
 
@@ -958,9 +1024,16 @@
958
1024
  return total_occu > 0 ? weighted_sum / total_occu : 1
959
1025
  }
960
1026
 
1027
+ // Disordered sites are often stored as separate split sites (one species each)
1028
+ // at the same position; merge_split_partial_sites groups them into one render
1029
+ // site whose `species` holds every element. Shared by atom rendering and the
1030
+ // hover tooltip so it lists all elements, not just the majority one.
1031
+ let render_sites = $derived(
1032
+ structure?.sites ? merge_split_partial_sites(structure.sites, hidden_elements) : [],
1033
+ )
1034
+
961
1035
  let atom_data = $derived.by(() => {
962
- if (!show_atoms || !structure?.sites) return []
963
- const render_sites = merge_split_partial_sites(structure.sites, hidden_elements)
1036
+ if (!show_atoms) return []
964
1037
  return render_sites.flatMap(({ site_idx, site, is_image_atom }) => {
965
1038
  const orig_idx = get_orig_site_idx(site, site_idx)
966
1039
 
@@ -968,12 +1041,26 @@
968
1041
  const prop_val = property_colors?.values[orig_idx]
969
1042
  if (prop_val !== undefined && hidden_prop_vals.has(prop_val)) return []
970
1043
 
1044
+ // Optionally hide atoms at the center of a rendered polyhedron
1045
+ if (polyhedra_hide_center_atoms && polyhedra_center_site_idxs.has(site_idx)) {
1046
+ return []
1047
+ }
1048
+
1049
+ // Phase-2 PBC images exist only to complete bonds/coordination polyhedra at
1050
+ // cell faces. When neither renders (polyhedra toggled off, symmetry declutter,
1051
+ // …) they'd float disconnected outside the cell — hide them.
1052
+ if (
1053
+ site.properties?.completion_image &&
1054
+ !applies_to_structure(effective_show_bonds) &&
1055
+ !applies_to_structure(effective_show_polyhedra)
1056
+ ) return []
1057
+
971
1058
  // Calculate radius: same_size > site override > element override > default
972
1059
  // All radii scale uniformly with atom_radius for consistent slider behavior
973
1060
  const base_radius = same_size_atoms
974
1061
  ? 1
975
1062
  : site_radius_overrides?.get(site_idx) ?? calc_weighted_radius(site)
976
- const radius = base_radius * atom_radius
1063
+ const radius = base_radius * effective_atom_radius
977
1064
 
978
1065
  // Use property color if available (e.g. coordination number, Wyckoff position)
979
1066
  // Otherwise, each species gets its own element color (important for disordered sites)
@@ -1080,17 +1167,110 @@
1080
1167
  return [...calculated, ...added]
1081
1168
  })
1082
1169
 
1170
+ // Bonds drawn as cylinders. When show_bonds doesn't apply, calculated bonds are
1171
+ // hidden but manually added bonds stay visible (bond_pairs may still be computed
1172
+ // for polyhedra, so this can't rely on bond_pairs being empty).
1173
+ let bonds_to_render = $derived.by(() => {
1174
+ if (applies_to_structure(effective_show_bonds)) return filtered_bond_pairs
1175
+ const added_keys = new Set(added_bonds.map(bond_key_for))
1176
+ return filtered_bond_pairs.filter((bond) => added_keys.has(bond_key_for(bond)))
1177
+ })
1178
+
1083
1179
  let editable_bond_pairs = $derived(
1084
- bond_edits_enabled ? filtered_bond_pairs.filter(can_edit_bond) : [],
1180
+ bond_edits_enabled ? bonds_to_render.filter(can_edit_bond) : [],
1181
+ )
1182
+
1183
+ // Coordination polyhedra around cation-like centers, derived from the same
1184
+ // (edited, filtered) bond graph as rendered bonds so the two never disagree.
1185
+ // Colors are resolved in polyhedra_buffers below, so color-scheme/mode changes
1186
+ // never recompute the hull geometry.
1187
+ let polyhedra: Polyhedron[] = $derived.by(() => {
1188
+ if (
1189
+ !structure?.sites || dragging_atoms ||
1190
+ !applies_to_structure(effective_show_polyhedra) ||
1191
+ filtered_bond_pairs.length === 0
1192
+ ) return []
1193
+ return compute_polyhedra(structure, filtered_bond_pairs, {
1194
+ min_neighbors: polyhedra_min_neighbors,
1195
+ max_neighbors: polyhedra_max_neighbors,
1196
+ excluded_center_elements: polyhedra_excluded_elements,
1197
+ included_center_elements: polyhedra_included_elements,
1198
+ })
1199
+ })
1200
+
1201
+ // Color of a site: property color (coordination/Wyckoff modes) or element color
1202
+ const polyhedra_site_color = (site_idx: number): string => {
1203
+ const site = structure?.sites[site_idx]
1204
+ const orig_idx = get_orig_site_idx(site, site_idx)
1205
+ const element = get_majority_element(site)
1206
+ return property_colors?.colors[orig_idx] ??
1207
+ (element && colors.element?.[element]) ?? `#808080`
1208
+ }
1209
+
1210
+ // Separate derived so material-only changes (opacity, edge color) don't rebuild
1211
+ // buffers and color changes don't rebuild hulls
1212
+ let polyhedra_buffers = $derived.by(() => {
1213
+ if (polyhedra.length === 0) return null
1214
+ const get_vertex_color = (poly: Polyhedron, vertex_idx: number): string => {
1215
+ if (polyhedra_color_mode === `uniform`) return polyhedra_color
1216
+ if (polyhedra_color_mode === `center`) {
1217
+ return polyhedra_site_color(poly.center_site_idx)
1218
+ }
1219
+ // 'vertex' (default): each corner takes the color of the atom that forms it
1220
+ return polyhedra_site_color(poly.vertex_site_idxs[vertex_idx])
1221
+ }
1222
+ return merge_polyhedra_buffers(polyhedra, get_vertex_color)
1223
+ })
1224
+
1225
+ let polyhedra_center_site_idxs = $derived(
1226
+ new Set(polyhedra.map((poly) => poly.center_site_idx)),
1085
1227
  )
1086
1228
 
1229
+ // Publish which elements currently anchor polyhedra (consumed by controls so
1230
+ // per-element toggles reflect the actual render state incl. spectator hiding)
1231
+ $effect(() => {
1232
+ const elems = [...new Set(polyhedra.map((poly) => poly.center_element))].sort()
1233
+ if (elems.join(`,`) !== polyhedra_rendered_elements.join(`,`)) {
1234
+ polyhedra_rendered_elements = elems
1235
+ }
1236
+ })
1237
+
1238
+ // Geometries with proper disposal on dependency change (same pattern as ReferencePlane)
1239
+ const buffer_geometry = (attrs: Record<string, Float32Array>): BufferGeometry => {
1240
+ const geo = new BufferGeometry()
1241
+ for (const [name, array] of Object.entries(attrs)) {
1242
+ geo.setAttribute(name, new BufferAttribute(array, 3))
1243
+ }
1244
+ return geo
1245
+ }
1246
+ let polyhedra_geometry: BufferGeometry | null = $state(null)
1247
+ $effect(() => {
1248
+ let geo: BufferGeometry | null = null
1249
+ if (polyhedra_buffers && polyhedra_buffers.triangle_count > 0) {
1250
+ const { positions: position, colors: color } = polyhedra_buffers
1251
+ geo = buffer_geometry({ position, color })
1252
+ geo.computeVertexNormals() // non-indexed -> per-face normals (flat shading)
1253
+ }
1254
+ polyhedra_geometry = geo
1255
+ return () => geo?.dispose()
1256
+ })
1257
+
1258
+ let polyhedra_edge_geometry: BufferGeometry | null = $state(null)
1259
+ $effect(() => {
1260
+ const geo = polyhedra_show_edges && polyhedra_buffers && polyhedra_buffers.edge_count > 0
1261
+ ? buffer_geometry({ position: polyhedra_buffers.edge_positions })
1262
+ : null
1263
+ polyhedra_edge_geometry = geo
1264
+ return () => geo?.dispose()
1265
+ })
1266
+
1087
1267
  let smart_site_label_offsets = $derived.by(() => {
1088
1268
  const offsets = new SvelteMap<number, Vec3>()
1089
- if (filtered_bond_pairs.length === 0) return offsets
1269
+ if (bonds_to_render.length === 0) return offsets
1090
1270
 
1091
1271
  const bond_directions_by_site = new SvelteMap<number, Vec3[]>()
1092
1272
  const add_bond_direction = (site_idx: number, pos_1: Vec3, pos_2: Vec3) => {
1093
- const direction = math.normalize_vec3(
1273
+ const direction = math.normalize_vec(
1094
1274
  math.subtract(pos_2, pos_1),
1095
1275
  [0, 0, 0],
1096
1276
  )
@@ -1101,7 +1281,7 @@
1101
1281
  ])
1102
1282
  }
1103
1283
 
1104
- for (const { site_idx_1, site_idx_2, pos_1, pos_2 } of filtered_bond_pairs) {
1284
+ for (const { site_idx_1, site_idx_2, pos_1, pos_2 } of bonds_to_render) {
1105
1285
  add_bond_direction(site_idx_1, pos_1, pos_2)
1106
1286
  add_bond_direction(site_idx_2, pos_2, pos_1)
1107
1287
  }
@@ -1112,7 +1292,7 @@
1112
1292
  })
1113
1293
 
1114
1294
  let instanced_bond_groups = $derived.by(() => {
1115
- if (!structure?.sites || filtered_bond_pairs.length === 0) return []
1295
+ if (!structure?.sites || bonds_to_render.length === 0) return []
1116
1296
 
1117
1297
  const group = {
1118
1298
  thickness: bond_thickness,
@@ -1125,7 +1305,7 @@
1125
1305
  }[],
1126
1306
  }
1127
1307
 
1128
- for (const bond_data of filtered_bond_pairs) {
1308
+ for (const bond_data of bonds_to_render) {
1129
1309
  const site_a = structure.sites[bond_data.site_idx_1]
1130
1310
  const site_b = structure.sites[bond_data.site_idx_2]
1131
1311
 
@@ -1155,6 +1335,24 @@
1155
1335
  return map
1156
1336
  })
1157
1337
 
1338
+ // Partial-occupancy atoms render as separate wedge (lune) meshes that converge
1339
+ // to a point at the sphere's poles, leaving the ball hard to hover from some
1340
+ // angles. Give each such site one invisible full-sphere hit target so it's as
1341
+ // reliably hoverable as an ordered atom (single solid sphere). One per site.
1342
+ let partial_hit_targets = $derived.by(() => {
1343
+ const targets = new SvelteMap<number, EditableAtomHitTarget & { is_image_atom: boolean }>()
1344
+ for (const atom of atom_data) {
1345
+ if (!atom.has_partial_occupancy || targets.has(atom.site_idx)) continue
1346
+ targets.set(atom.site_idx, {
1347
+ site_idx: atom.site_idx,
1348
+ position: atom.position,
1349
+ radius: atom.radius,
1350
+ is_image_atom: atom.is_image_atom,
1351
+ })
1352
+ }
1353
+ return [...targets.values()]
1354
+ })
1355
+
1158
1356
  let editable_atom_hit_targets = $derived.by(() => {
1159
1357
  if (
1160
1358
  measure_mode !== `edit-bonds` ||
@@ -1184,7 +1382,7 @@
1184
1382
  ? site_radius_overrides?.get(site_idx)
1185
1383
  : undefined
1186
1384
  const base_radius = same_size_atoms ? 1 : override ?? calc_weighted_radius(site)
1187
- return base_radius * atom_radius
1385
+ return base_radius * effective_atom_radius
1188
1386
  }
1189
1387
 
1190
1388
  // Interpolate between spin-down (#3498db blue) and spin-up (#e74c3c red)
@@ -1253,16 +1451,16 @@
1253
1451
  let mean: Vec3 = [0, 0, 0]
1254
1452
  for (const key of site_keys) {
1255
1453
  const vec = vec_map.get(key)
1256
- if (vec) mean = math.add(mean, math.normalize_vec3(vec)) as Vec3
1454
+ if (vec) mean = math.add(mean, math.normalize_vec(vec)) as Vec3
1257
1455
  }
1258
- const mean_dir = math.normalize_vec3(mean, [0, 1, 0] as Vec3)
1456
+ const mean_dir = math.normalize_vec(mean, [0, 1, 0] as Vec3)
1259
1457
  const [u_vec, v_vec] = math.compute_in_plane_basis(mean_dir)
1260
1458
  const offsets = new SvelteMap<string, Vec3>()
1261
1459
  for (const [idx, key] of site_keys.entries()) {
1262
1460
  const angle = (2 * Math.PI * idx) / n_keys
1263
- const dx = math.scale(u_vec, gap_abs * Math.cos(angle)) as Vec3
1264
- const dy = math.scale(v_vec, gap_abs * Math.sin(angle)) as Vec3
1265
- offsets.set(key, math.add(dx, dy) as Vec3)
1461
+ const dx = math.scale(u_vec, gap_abs * Math.cos(angle))
1462
+ const dy = math.scale(v_vec, gap_abs * Math.sin(angle))
1463
+ offsets.set(key, math.add(dx, dy))
1266
1464
  }
1267
1465
  return offsets
1268
1466
  })
@@ -1316,8 +1514,8 @@
1316
1514
  }
1317
1515
 
1318
1516
  const offset = site_offsets?.[site_idx]?.get(key)
1319
- const position = offset ? math.add(site.xyz, offset) as Vec3 : site.xyz
1320
- const arrow_vec = vector_normalize ? math.normalize_vec3(vec) : vec
1517
+ const position = offset ? math.add(site.xyz, offset) : site.xyz
1518
+ const arrow_vec = vector_normalize ? math.normalize_vec(vec) : vec
1321
1519
 
1322
1520
  return {
1323
1521
  site_idx,
@@ -1365,57 +1563,29 @@
1365
1563
  ),
1366
1564
  )
1367
1565
 
1368
- let gizmo_props = $derived.by(() => {
1369
- const axis_options = Object.fromEntries(
1370
- [...AXIS_COLORS, ...NEG_AXIS_COLORS].map(([axis, color, hover_color]) => [
1371
- axis,
1372
- {
1373
- color,
1374
- labelColor: `#111`,
1375
- opacity: axis.startsWith(`n`) ? 0.9 : 0.8,
1376
- hover: {
1377
- color: hover_color,
1378
- labelColor: `#222222`,
1379
- opacity: axis.startsWith(`n`) ? 1 : 0.9,
1380
- },
1381
- },
1382
- ]),
1383
- )
1384
- return {
1385
- background: { enabled: false },
1386
- className: `responsive-gizmo`,
1387
- ...axis_options,
1388
- ...(typeof gizmo === `boolean` ? {} : gizmo),
1389
- offset: { left: 5, bottom: 5 },
1390
- }
1391
- })
1392
-
1393
- let orbit_controls_props = $derived({
1394
- position: [0, 0, 0],
1395
- enableRotate: rotate_speed > 0,
1396
- rotateSpeed: rotate_speed,
1397
- enableZoom: zoom_speed > 0,
1398
- zoomSpeed: camera_projection === `orthographic` ? zoom_speed * 2 : zoom_speed,
1399
- zoomToCursor: zoom_to_cursor,
1400
- enablePan: pan_speed > 0,
1401
- panSpeed: pan_speed,
1566
+ let orbit_controls_props = $derived(build_orbit_props({
1567
+ camera_projection,
1402
1568
  target: camera_target ?? rotation_target,
1403
- maxZoom: max_zoom,
1404
- minZoom: min_zoom,
1405
- autoRotate: Boolean(auto_rotate),
1406
- autoRotateSpeed: auto_rotate,
1407
- enableDamping: Boolean(rotation_damping),
1408
- dampingFactor: rotation_damping,
1409
- onstart: () => {
1410
- camera_is_moving = true
1569
+ rotate_speed,
1570
+ zoom_speed,
1571
+ zoom_to_cursor,
1572
+ pan_speed,
1573
+ max_zoom,
1574
+ min_zoom,
1575
+ auto_rotate,
1576
+ rotation_damping,
1577
+ set_camera_is_moving: (moving) => (camera_is_moving = moving),
1578
+ // Close hover tooltips + bond context menu while the camera moves. Only hide the
1579
+ // VISIBLE menu (not bond_context_target): clicking a menu button fires this
1580
+ // orbit-controls start handler before the button's own handler runs, which still
1581
+ // needs the target bond to apply the edit (see bond_context_target comment).
1582
+ onstart_extra: () => {
1411
1583
  cancel_atom_hover_clear()
1412
1584
  hovered_idx = null
1585
+ hovered_bond_key = null
1413
1586
  bond_context_menu = null
1414
1587
  },
1415
- onend: () => {
1416
- camera_is_moving = false
1417
- },
1418
- })
1588
+ }))
1419
1589
 
1420
1590
  let measure_line_color = $derived.by(() => {
1421
1591
  if (typeof window === `undefined`) return
@@ -1492,31 +1662,17 @@
1492
1662
  </extras.HTML>
1493
1663
  {/snippet}
1494
1664
 
1495
- {#if camera_projection === `perspective`}
1496
- <T.PerspectiveCamera
1497
- makeDefault
1498
- position={camera_position}
1499
- {fov}
1500
- near={camera_near}
1501
- far={camera_far}
1502
- >
1503
- <extras.OrbitControls bind:ref={orbit_controls} {...orbit_controls_props}>
1504
- {#if gizmo}<extras.Gizmo {...gizmo_props} />{/if}
1505
- </extras.OrbitControls>
1506
- </T.PerspectiveCamera>
1507
- {:else}
1508
- <T.OrthographicCamera
1509
- makeDefault
1510
- position={camera_position}
1511
- zoom={computed_zoom}
1512
- near={-100}
1513
- far={camera_far}
1514
- >
1515
- <extras.OrbitControls bind:ref={orbit_controls} {...orbit_controls_props}>
1516
- {#if gizmo}<extras.Gizmo {...gizmo_props} />{/if}
1517
- </extras.OrbitControls>
1518
- </T.OrthographicCamera>
1519
- {/if}
1665
+ <SceneCamera
1666
+ {camera_projection}
1667
+ position={camera_position}
1668
+ {fov}
1669
+ zoom={computed_zoom}
1670
+ near={camera_near}
1671
+ far={camera_far}
1672
+ orbit_props={orbit_controls_props}
1673
+ {gizmo}
1674
+ bind:orbit_controls
1675
+ />
1520
1676
 
1521
1677
  <T.DirectionalLight position={[3, 10, 10]} intensity={directional_light} />
1522
1678
  <T.AmbientLight intensity={ambient_light} />
@@ -1546,28 +1702,7 @@
1546
1702
  <extras.Instance
1547
1703
  position={atom.position}
1548
1704
  scale={atom.radius}
1549
- {...atom_hover_props(atom.site_idx, edit_mode_image)}
1550
- onpointerdown={(event: PointerEvent) => {
1551
- if (
1552
- edit_mode_image ||
1553
- measure_mode !== `edit-bonds` ||
1554
- bond_edit_mode !== `add`
1555
- ) {
1556
- return
1557
- }
1558
- select_edit_bonds_site(atom.site_idx, event)
1559
- }}
1560
- onclick={(event: MouseEvent) => {
1561
- if (edit_mode_image) return
1562
- if (measure_mode === `edit-bonds`) {
1563
- if (bond_edit_mode !== `add`) return
1564
- if (skip_duplicate_edit_bonds_click(atom.site_idx)) {
1565
- event.stopPropagation()
1566
- return
1567
- }
1568
- }
1569
- toggle_selection(atom.site_idx, event)
1570
- }}
1705
+ {...atom_pointer_props(atom.site_idx, edit_mode_image)}
1571
1706
  />
1572
1707
  {/each}
1573
1708
  </extras.InstancedMesh>
@@ -1580,32 +1715,9 @@
1580
1715
  }
1581
1716
  {@const partial_edit_image = measure_mode === `edit-atoms` && atom.is_image_atom}
1582
1717
  {@const ghost_opacity = partial_edit_image ? 0.5 : 1}
1583
- <T.Group
1584
- position={atom.position}
1585
- scale={atom.radius}
1586
- {...atom_hover_props(atom.site_idx, partial_edit_image)}
1587
- onpointerdown={(event: PointerEvent) => {
1588
- if (
1589
- partial_edit_image ||
1590
- measure_mode !== `edit-bonds` ||
1591
- bond_edit_mode !== `add`
1592
- ) {
1593
- return
1594
- }
1595
- select_edit_bonds_site(atom.site_idx, event)
1596
- }}
1597
- onclick={(event: MouseEvent) => {
1598
- if (partial_edit_image) return
1599
- if (measure_mode === `edit-bonds`) {
1600
- if (bond_edit_mode !== `add`) return
1601
- if (skip_duplicate_edit_bonds_click(atom.site_idx)) {
1602
- event.stopPropagation()
1603
- return
1604
- }
1605
- }
1606
- toggle_selection(atom.site_idx, event)
1607
- }}
1608
- >
1718
+ <!-- Visual only: pointer interaction handled by the invisible full-sphere
1719
+ hit targets below (wedge meshes leave gaps at the poles). -->
1720
+ <T.Group position={atom.position} scale={atom.radius}>
1609
1721
  {@const partial_color = partial_edit_image
1610
1722
  ? desaturate(atom.color)
1611
1723
  : atom.color}
@@ -1626,7 +1738,7 @@
1626
1738
  />
1627
1739
  </T.Mesh>
1628
1740
 
1629
- {#if atom.has_partial_occupancy && atom.render_start_cap}
1741
+ {#if atom.render_start_cap}
1630
1742
  <T.Mesh rotation={[0, atom.start_phi, 0]}>
1631
1743
  <T.CircleGeometry
1632
1744
  args={[
@@ -1644,7 +1756,7 @@
1644
1756
  />
1645
1757
  </T.Mesh>
1646
1758
  {/if}
1647
- {#if atom.has_partial_occupancy && atom.render_end_cap}
1759
+ {#if atom.render_end_cap}
1648
1760
  <T.Mesh rotation={[0, atom.end_phi, 0]}>
1649
1761
  <T.CircleGeometry
1650
1762
  args={[
@@ -1671,6 +1783,20 @@
1671
1783
  {/if}
1672
1784
  {/each}
1673
1785
 
1786
+ <!-- Invisible full-sphere hit targets for partial-occupancy sites so the
1787
+ whole ball is hoverable/clickable (wedge meshes leave gaps at the poles). -->
1788
+ {#each partial_hit_targets as hit (hit.site_idx)}
1789
+ {@const hit_edit_image = measure_mode === `edit-atoms` && hit.is_image_atom}
1790
+ <T.Mesh
1791
+ position={hit.position}
1792
+ scale={hit.radius}
1793
+ {...atom_pointer_props(hit.site_idx, hit_edit_image)}
1794
+ >
1795
+ <T.SphereGeometry args={[0.5, sphere_segments, sphere_segments]} />
1796
+ <T.MeshBasicMaterial transparent opacity={0} depthWrite={false} />
1797
+ </T.Mesh>
1798
+ {/each}
1799
+
1674
1800
  <!-- Site labels/indices for instanced atoms -->
1675
1801
  {#if show_site_labels || show_site_indices}
1676
1802
  {#each unique_instanced_atoms as atom (atom.site_idx)}
@@ -1697,6 +1823,32 @@
1697
1823
  {/each}
1698
1824
  {/if}
1699
1825
 
1826
+ <!-- Coordination polyhedra: all faces in one merged mesh, edges in one
1827
+ LineSegments (1-2 draw calls regardless of supercell size) -->
1828
+ {#if polyhedra_geometry}
1829
+ <T.Mesh geometry={polyhedra_geometry} frustumCulled={false} raycast={() => null}>
1830
+ <!-- depthWrite when mostly opaque: VESTA-like occlusion between polyhedra;
1831
+ fully translucent settings fall back to see-through blending -->
1832
+ <T.MeshStandardMaterial
1833
+ vertexColors
1834
+ transparent={polyhedra_opacity < 1}
1835
+ opacity={polyhedra_opacity}
1836
+ side={DoubleSide}
1837
+ depthWrite={polyhedra_opacity >= 0.65}
1838
+ flatShading
1839
+ />
1840
+ </T.Mesh>
1841
+ {#if polyhedra_edge_geometry}
1842
+ <T.LineSegments
1843
+ geometry={polyhedra_edge_geometry}
1844
+ frustumCulled={false}
1845
+ raycast={() => null}
1846
+ >
1847
+ <T.LineBasicMaterial color={polyhedra_edge_color} />
1848
+ </T.LineSegments>
1849
+ {/if}
1850
+ {/if}
1851
+
1700
1852
  <!-- Clickable bond hit-test cylinders in edit-bonds mode -->
1701
1853
  {#if measure_mode === `edit-bonds` && editable_bond_pairs.length > 0}
1702
1854
  {#each editable_bond_pairs as
@@ -1911,8 +2063,8 @@
1911
2063
  {@const site = structure.sites[site_index]}
1912
2064
  {#if site}
1913
2065
  <!-- shift selected site labels down to avoid overlapping regular site labels-->
1914
- {@const selection_offset = math.add(site_label_offset, [0, -0.5, 0])}
1915
- {@const pos = math.add(site.xyz, selection_offset) as Vec3}
2066
+ {@const selection_offset = math.add<Vec3>(site_label_offset, [0, -0.5, 0])}
2067
+ {@const pos = math.add(site.xyz, selection_offset)}
1916
2068
  <extras.HTML center position={pos}>
1917
2069
  <span class="selection-label">{loop_idx + 1}</span>
1918
2070
  </extras.HTML>
@@ -1953,10 +2105,13 @@
1953
2105
  .map(([elem, count]) => `${elem}: ${count}`)
1954
2106
  return ` (${parts.join(`, `)})`
1955
2107
  })()}
2108
+ {@const tooltip_species =
2109
+ render_sites.find((rs) => rs.site_idx === hovered_idx)?.site.species ??
2110
+ hovered_site.species ?? []}
1956
2111
  <CanvasTooltip position={hovered_site.xyz}>
1957
2112
  <!-- Element symbols with occupancies for disordered sites -->
1958
2113
  <div class="elements">
1959
- {#each hovered_site.species ?? [] as
2114
+ {#each tooltip_species as
1960
2115
  { element, occu, oxidation_state: oxi_state },
1961
2116
  idx
1962
2117
  (`${element ?? ``}-${occu ?? ``}-${oxi_state ?? ``}-${idx}`)
@@ -1965,16 +2120,17 @@
1965
2120
  elem.symbol === element
1966
2121
  )?.name ??
1967
2122
  ``}
1968
- {#if idx > 0}&thinsp;{/if}
1969
- {#if occu !== 1}<span class="occupancy">{
1970
- format_num(occu, `.3~f`)
1971
- }</span>{/if}
1972
- <strong>
1973
- {element}{#if oxi_state != null && oxi_state !== 0}<sup>{Math.abs(
1974
- oxi_state,
1975
- )}{oxi_state > 0 ? `+` : `−`}</sup>{/if}
1976
- </strong>
1977
- {#if element_name}<span class="elem-name">{element_name}</span>{/if}
2123
+ <span class="species">
2124
+ {#if occu !== 1}<span class="occupancy">{
2125
+ format_num(occu, `.3~f`)
2126
+ }</span>{/if}
2127
+ <strong>
2128
+ {element}{#if oxi_state != null && oxi_state !== 0}<sup>{Math.abs(
2129
+ oxi_state,
2130
+ )}{oxi_state > 0 ? `+` : `−`}</sup>{/if}
2131
+ </strong>
2132
+ {#if element_name}<span class="elem-name">{element_name}</span>{/if}
2133
+ </span>
1978
2134
  {/each}
1979
2135
  </div>
1980
2136
  <div class="coordinates fractional">abc: ({abc})</div>
@@ -1987,6 +2143,13 @@
1987
2143
 
1988
2144
  {#if visual_lattice}
1989
2145
  <Lattice matrix={visual_lattice.matrix} {...lattice_props} />
2146
+ {#if symmetry_elements.length > 0}
2147
+ <SymmetryElements
2148
+ elements={symmetry_elements}
2149
+ lattice={visual_lattice.matrix}
2150
+ {...symmetry_elements_props}
2151
+ />
2152
+ {/if}
1990
2153
  {/if}
1991
2154
 
1992
2155
  <!-- TransformControls for editing atoms in edit-atoms mode -->
@@ -2096,7 +2259,7 @@
2096
2259
  ] as Vec3}
2097
2260
  {@const direct = math.euclidean_dist(pos_i, pos_j)}
2098
2261
  {@const pbc = lattice
2099
- ? measure.distance_pbc(pos_i, pos_j, lattice.matrix)
2262
+ ? measure.distance_pbc(pos_i, pos_j, lattice.matrix, undefined, lattice.pbc)
2100
2263
  : direct}
2101
2264
  {@const differ = lattice ? Math.abs(pbc - direct) > 1e-6 : false}
2102
2265
  <extras.HTML center position={midpoint}>
@@ -2125,8 +2288,10 @@
2125
2288
  }
2126
2289
  {@const site_a = structure.sites[idx_a]}
2127
2290
  {@const site_b = structure.sites[idx_b]}
2128
- {@const v1 = measure.displacement_pbc(center.xyz, site_a.xyz, lattice?.matrix)}
2129
- {@const v2 = measure.displacement_pbc(center.xyz, site_b.xyz, lattice?.matrix)}
2291
+ {@const disp = (to: Vec3) =>
2292
+ measure.displacement_pbc(center.xyz, to, lattice?.matrix, undefined, lattice?.pbc)}
2293
+ {@const v1 = disp(site_a.xyz)}
2294
+ {@const v2 = disp(site_b.xyz)}
2130
2295
  {@const n1 = Math.hypot(v1[0], v1[1], v1[2])}
2131
2296
  {@const n2 = Math.hypot(v2[0], v2[1], v2[2])}
2132
2297
  {@const angle_deg = measure.angle_between_vectors(v1, v2, `degrees`)}
@@ -2162,10 +2327,6 @@
2162
2327
  </T.Group>
2163
2328
 
2164
2329
  <style>
2165
- :global(.structure .responsive-gizmo) {
2166
- width: clamp(70px, 18cqmin, 100px) !important;
2167
- height: clamp(70px, 18cqmin, 100px) !important;
2168
- }
2169
2330
  .atom-label {
2170
2331
  background: var(--struct-atom-label-bg, rgba(0, 0, 0, 0.1));
2171
2332
  border: 0;
@@ -2179,6 +2340,13 @@
2179
2340
  .elements {
2180
2341
  margin-bottom: var(--canvas-tooltip-elements-margin);
2181
2342
  }
2343
+ .species {
2344
+ display: inline-block;
2345
+ white-space: nowrap;
2346
+ &:not(:first-child) {
2347
+ margin-left: var(--canvas-tooltip-species-gap, 0.5em);
2348
+ }
2349
+ }
2182
2350
  .occupancy {
2183
2351
  font-size: var(--canvas-tooltip-occu-font-size);
2184
2352
  opacity: var(--canvas-tooltip-occu-opacity);