matterviz 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/dist/EmptyState.svelte +10 -2
  2. package/dist/FilePicker.svelte +154 -96
  3. package/dist/Icon.svelte +20 -14
  4. package/dist/MillerIndexInput.svelte +27 -21
  5. package/dist/api/optimade.js +6 -6
  6. package/dist/app.css +216 -178
  7. package/dist/brillouin/BrillouinZone.svelte +299 -198
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +74 -55
  11. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  12. package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte +277 -165
  14. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  15. package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
  16. package/dist/brillouin/compute.js +11 -6
  17. package/dist/chempot-diagram/ChemPotDiagram.svelte +327 -0
  18. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  19. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +847 -0
  20. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  21. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +3194 -0
  22. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  23. package/dist/chempot-diagram/ChemPotScene3D.svelte +11 -0
  24. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  25. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  26. package/dist/chempot-diagram/async-compute.svelte.js +77 -0
  27. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  28. package/dist/chempot-diagram/chempot-worker.js +11 -0
  29. package/dist/chempot-diagram/color.d.ts +10 -0
  30. package/dist/chempot-diagram/color.js +32 -0
  31. package/dist/chempot-diagram/compute.d.ts +48 -0
  32. package/dist/chempot-diagram/compute.js +812 -0
  33. package/dist/chempot-diagram/index.d.ts +6 -0
  34. package/dist/chempot-diagram/index.js +6 -0
  35. package/dist/chempot-diagram/pointer.d.ts +16 -0
  36. package/dist/chempot-diagram/pointer.js +40 -0
  37. package/dist/chempot-diagram/temperature.d.ts +15 -0
  38. package/dist/chempot-diagram/temperature.js +36 -0
  39. package/dist/chempot-diagram/types.d.ts +86 -0
  40. package/dist/chempot-diagram/types.js +28 -0
  41. package/dist/colors/index.d.ts +3 -1
  42. package/dist/colors/index.js +9 -3
  43. package/dist/composition/BarChart.svelte +141 -77
  44. package/dist/composition/BubbleChart.svelte +107 -52
  45. package/dist/composition/Composition.svelte +100 -79
  46. package/dist/composition/Formula.svelte +108 -62
  47. package/dist/composition/FormulaFilter.svelte +973 -353
  48. package/dist/composition/FormulaFilter.svelte.d.ts +35 -1
  49. package/dist/composition/PieChart.svelte +199 -99
  50. package/dist/composition/PieChart.svelte.d.ts +1 -1
  51. package/dist/composition/format.d.ts +5 -0
  52. package/dist/composition/format.js +20 -3
  53. package/dist/composition/parse.js +14 -9
  54. package/dist/convex-hull/ConvexHull.svelte +93 -38
  55. package/dist/convex-hull/ConvexHull2D.svelte +551 -393
  56. package/dist/convex-hull/ConvexHull3D.svelte +1303 -825
  57. package/dist/convex-hull/ConvexHull4D.svelte +1012 -686
  58. package/dist/convex-hull/ConvexHullControls.svelte +115 -28
  59. package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
  60. package/dist/convex-hull/ConvexHullStats.svelte +821 -249
  61. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
  62. package/dist/convex-hull/ConvexHullTooltip.svelte +41 -16
  63. package/dist/convex-hull/GasPressureControls.svelte +104 -61
  64. package/dist/convex-hull/StructurePopup.svelte +25 -4
  65. package/dist/convex-hull/TemperatureSlider.svelte +45 -25
  66. package/dist/convex-hull/barycentric-coords.js +13 -7
  67. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  68. package/dist/convex-hull/demo-temperature.js +40 -0
  69. package/dist/convex-hull/gas-thermodynamics.js +17 -12
  70. package/dist/convex-hull/helpers.d.ts +10 -1
  71. package/dist/convex-hull/helpers.js +79 -38
  72. package/dist/convex-hull/index.d.ts +1 -0
  73. package/dist/convex-hull/index.js +1 -0
  74. package/dist/convex-hull/thermodynamics.d.ts +8 -21
  75. package/dist/convex-hull/thermodynamics.js +163 -69
  76. package/dist/convex-hull/types.d.ts +12 -12
  77. package/dist/convex-hull/types.js +0 -12
  78. package/dist/coordination/CoordinationBarPlot.svelte +232 -176
  79. package/dist/element/BohrAtom.svelte +56 -13
  80. package/dist/element/ElementHeading.svelte +7 -2
  81. package/dist/element/ElementPhoto.svelte +15 -9
  82. package/dist/element/ElementStats.svelte +10 -4
  83. package/dist/element/ElementTile.svelte +137 -73
  84. package/dist/element/Nucleus.svelte +39 -11
  85. package/dist/element/data.js +2 -14
  86. package/dist/element/data.json.gz +0 -0
  87. package/dist/element/types.d.ts +1 -0
  88. package/dist/feedback/ClickFeedback.svelte +16 -5
  89. package/dist/feedback/DragOverlay.svelte +10 -2
  90. package/dist/feedback/Spinner.svelte +4 -2
  91. package/dist/feedback/StatusMessage.svelte +8 -2
  92. package/dist/fermi-surface/FermiSlice.svelte +118 -88
  93. package/dist/fermi-surface/FermiSurface.svelte +336 -239
  94. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  95. package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
  96. package/dist/fermi-surface/FermiSurfaceScene.svelte +536 -343
  97. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  98. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
  99. package/dist/fermi-surface/compute.js +16 -20
  100. package/dist/fermi-surface/parse.js +37 -33
  101. package/dist/fermi-surface/symmetry.js +2 -7
  102. package/dist/fermi-surface/types.d.ts +3 -5
  103. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1527 -0
  104. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  105. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
  106. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +30 -0
  107. package/dist/heatmap-matrix/index.d.ts +53 -0
  108. package/dist/heatmap-matrix/index.js +100 -0
  109. package/dist/heatmap-matrix/shared.d.ts +2 -0
  110. package/dist/heatmap-matrix/shared.js +4 -0
  111. package/dist/icons.d.ts +111 -0
  112. package/dist/icons.js +158 -0
  113. package/dist/index.d.ts +5 -2
  114. package/dist/index.js +5 -2
  115. package/dist/io/decompress.js +1 -1
  116. package/dist/io/export.d.ts +3 -0
  117. package/dist/io/export.js +138 -140
  118. package/dist/io/file-drop.d.ts +7 -0
  119. package/dist/io/file-drop.js +43 -0
  120. package/dist/io/index.d.ts +2 -2
  121. package/dist/io/index.js +2 -112
  122. package/dist/io/is-binary.js +2 -3
  123. package/dist/io/types.d.ts +1 -0
  124. package/dist/io/url-drop.d.ts +2 -0
  125. package/dist/io/url-drop.js +117 -0
  126. package/dist/isosurface/Isosurface.svelte +220 -110
  127. package/dist/isosurface/IsosurfaceControls.svelte +65 -28
  128. package/dist/isosurface/parse.js +104 -56
  129. package/dist/isosurface/slice.d.ts +2 -1
  130. package/dist/isosurface/slice.js +8 -13
  131. package/dist/isosurface/types.d.ts +14 -1
  132. package/dist/isosurface/types.js +152 -5
  133. package/dist/labels.d.ts +2 -1
  134. package/dist/labels.js +12 -8
  135. package/dist/layout/FullscreenToggle.svelte +11 -2
  136. package/dist/layout/InfoCard.svelte +38 -6
  137. package/dist/layout/InfoTag.svelte +125 -94
  138. package/dist/layout/PropertyFilter.svelte +82 -37
  139. package/dist/layout/SettingsSection.svelte +85 -55
  140. package/dist/layout/SubpageGrid.svelte +82 -0
  141. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  142. package/dist/layout/index.d.ts +1 -0
  143. package/dist/layout/index.js +1 -0
  144. package/dist/layout/json-tree/JsonNode.svelte +266 -223
  145. package/dist/layout/json-tree/JsonTree.svelte +516 -429
  146. package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
  147. package/dist/layout/json-tree/JsonValue.svelte +281 -173
  148. package/dist/layout/json-tree/types.d.ts +10 -2
  149. package/dist/layout/json-tree/utils.d.ts +2 -0
  150. package/dist/layout/json-tree/utils.js +37 -2
  151. package/dist/marching-cubes.js +25 -2
  152. package/dist/math.d.ts +20 -17
  153. package/dist/math.js +474 -57
  154. package/dist/overlays/ContextMenu.svelte +66 -40
  155. package/dist/overlays/DraggablePane.svelte +331 -154
  156. package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
  157. package/dist/periodic-table/PeriodicTable.svelte +278 -145
  158. package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
  159. package/dist/periodic-table/PropertySelect.svelte +25 -7
  160. package/dist/periodic-table/TableInset.svelte +8 -3
  161. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +559 -267
  162. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
  163. package/dist/phase-diagram/PhaseDiagramControls.svelte +131 -51
  164. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
  165. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
  166. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  167. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +160 -110
  168. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +8 -1
  169. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +217 -86
  170. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
  171. package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
  172. package/dist/phase-diagram/build-diagram.js +9 -9
  173. package/dist/phase-diagram/colors.js +1 -3
  174. package/dist/phase-diagram/index.d.ts +2 -0
  175. package/dist/phase-diagram/index.js +2 -0
  176. package/dist/phase-diagram/parse.js +10 -9
  177. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  178. package/dist/phase-diagram/svg-to-diagram.js +869 -0
  179. package/dist/phase-diagram/types.d.ts +10 -0
  180. package/dist/phase-diagram/utils.d.ts +8 -4
  181. package/dist/phase-diagram/utils.js +219 -74
  182. package/dist/plot/AxisLabel.svelte +51 -0
  183. package/dist/plot/AxisLabel.svelte.d.ts +16 -0
  184. package/dist/plot/BarPlot.svelte +1461 -768
  185. package/dist/plot/BarPlot.svelte.d.ts +3 -3
  186. package/dist/plot/BarPlotControls.svelte +33 -6
  187. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  188. package/dist/plot/ColorBar.svelte +533 -383
  189. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  190. package/dist/plot/ColorScaleSelect.svelte +28 -7
  191. package/dist/plot/ElementScatter.svelte +38 -16
  192. package/dist/plot/FillArea.svelte +152 -92
  193. package/dist/plot/Histogram.svelte +1162 -709
  194. package/dist/plot/Histogram.svelte.d.ts +1 -1
  195. package/dist/plot/HistogramControls.svelte +81 -18
  196. package/dist/plot/HistogramControls.svelte.d.ts +6 -2
  197. package/dist/plot/InteractiveAxisLabel.svelte +34 -11
  198. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  199. package/dist/plot/Line.svelte +63 -28
  200. package/dist/plot/PlotControls.svelte +221 -96
  201. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  202. package/dist/plot/PlotLegend.svelte +174 -91
  203. package/dist/plot/PlotTooltip.svelte +45 -6
  204. package/dist/plot/PortalSelect.svelte +175 -146
  205. package/dist/plot/ReferenceLine.svelte +77 -22
  206. package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
  207. package/dist/plot/ReferenceLine3D.svelte +132 -107
  208. package/dist/plot/ReferencePlane.svelte +146 -123
  209. package/dist/plot/ScatterPlot.svelte +1880 -1156
  210. package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
  211. package/dist/plot/ScatterPlot3D.svelte +256 -131
  212. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  213. package/dist/plot/ScatterPlot3DControls.svelte +300 -297
  214. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
  215. package/dist/plot/ScatterPlot3DScene.svelte +608 -406
  216. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  217. package/dist/plot/ScatterPlotControls.svelte +150 -70
  218. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  219. package/dist/plot/ScatterPoint.svelte +98 -26
  220. package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
  221. package/dist/plot/SpacegroupBarPlot.svelte +142 -85
  222. package/dist/plot/Surface3D.svelte +159 -108
  223. package/dist/plot/ZeroLines.svelte +96 -0
  224. package/dist/plot/ZeroLines.svelte.d.ts +32 -0
  225. package/dist/plot/ZoomRect.svelte +23 -0
  226. package/dist/plot/ZoomRect.svelte.d.ts +8 -0
  227. package/dist/plot/axis-utils.d.ts +1 -1
  228. package/dist/plot/axis-utils.js +1 -3
  229. package/dist/plot/data-cleaning.js +12 -28
  230. package/dist/plot/data-transform.js +2 -1
  231. package/dist/plot/fill-utils.js +2 -0
  232. package/dist/plot/index.d.ts +6 -2
  233. package/dist/plot/index.js +6 -2
  234. package/dist/plot/interactions.d.ts +8 -10
  235. package/dist/plot/interactions.js +2 -3
  236. package/dist/plot/layout.d.ts +11 -2
  237. package/dist/plot/layout.js +44 -17
  238. package/dist/plot/reference-line.d.ts +5 -22
  239. package/dist/plot/reference-line.js +12 -84
  240. package/dist/plot/scales.js +24 -36
  241. package/dist/plot/types.d.ts +53 -40
  242. package/dist/plot/types.js +12 -7
  243. package/dist/plot/utils/label-placement.d.ts +32 -15
  244. package/dist/plot/utils/label-placement.js +227 -63
  245. package/dist/plot/utils/series-visibility.js +2 -3
  246. package/dist/plot/utils.d.ts +1 -0
  247. package/dist/plot/utils.js +14 -0
  248. package/dist/rdf/RdfPlot.svelte +173 -132
  249. package/dist/rdf/calc-rdf.js +4 -5
  250. package/dist/sanitize.d.ts +4 -0
  251. package/dist/sanitize.js +107 -0
  252. package/dist/settings.d.ts +21 -6
  253. package/dist/settings.js +63 -19
  254. package/dist/spectral/Bands.svelte +963 -412
  255. package/dist/spectral/Bands.svelte.d.ts +22 -2
  256. package/dist/spectral/BandsAndDos.svelte +90 -49
  257. package/dist/spectral/BrillouinBandsDos.svelte +151 -93
  258. package/dist/spectral/Dos.svelte +389 -258
  259. package/dist/spectral/helpers.d.ts +23 -1
  260. package/dist/spectral/helpers.js +119 -51
  261. package/dist/spectral/types.d.ts +2 -0
  262. package/dist/state.svelte.d.ts +1 -1
  263. package/dist/state.svelte.js +3 -2
  264. package/dist/structure/Arrow.svelte +59 -20
  265. package/dist/structure/AtomLegend.svelte +231 -129
  266. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  267. package/dist/structure/Bond.svelte +73 -47
  268. package/dist/structure/CanvasTooltip.svelte +10 -2
  269. package/dist/structure/CellSelect.svelte +148 -51
  270. package/dist/structure/Cylinder.svelte +33 -17
  271. package/dist/structure/Lattice.svelte +88 -33
  272. package/dist/structure/Structure.svelte +1077 -821
  273. package/dist/structure/Structure.svelte.d.ts +1 -1
  274. package/dist/structure/StructureControls.svelte +373 -139
  275. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  276. package/dist/structure/StructureExportPane.svelte +124 -89
  277. package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
  278. package/dist/structure/StructureInfoPane.svelte +304 -231
  279. package/dist/structure/StructureScene.svelte +919 -445
  280. package/dist/structure/StructureScene.svelte.d.ts +16 -7
  281. package/dist/structure/atom-properties.d.ts +6 -2
  282. package/dist/structure/atom-properties.js +42 -29
  283. package/dist/structure/bonding.js +6 -7
  284. package/dist/structure/export.js +22 -34
  285. package/dist/structure/ferrox-wasm-types.d.ts +3 -2
  286. package/dist/structure/ferrox-wasm-types.js +0 -3
  287. package/dist/structure/ferrox-wasm.d.ts +3 -2
  288. package/dist/structure/ferrox-wasm.js +2 -3
  289. package/dist/structure/index.d.ts +16 -0
  290. package/dist/structure/index.js +88 -6
  291. package/dist/structure/measure.d.ts +2 -2
  292. package/dist/structure/measure.js +4 -44
  293. package/dist/structure/parse.js +130 -155
  294. package/dist/structure/partial-occupancy.d.ts +25 -0
  295. package/dist/structure/partial-occupancy.js +99 -0
  296. package/dist/structure/pbc.d.ts +1 -0
  297. package/dist/structure/pbc.js +16 -6
  298. package/dist/structure/supercell.d.ts +2 -2
  299. package/dist/structure/supercell.js +12 -22
  300. package/dist/structure/validation.js +5 -3
  301. package/dist/symmetry/SymmetryStats.svelte +94 -37
  302. package/dist/symmetry/WyckoffTable.svelte +42 -14
  303. package/dist/symmetry/cell-transform.js +5 -3
  304. package/dist/symmetry/index.d.ts +7 -4
  305. package/dist/symmetry/index.js +87 -21
  306. package/dist/symmetry/spacegroups.js +148 -148
  307. package/dist/table/HeatmapTable.svelte +1112 -516
  308. package/dist/table/HeatmapTable.svelte.d.ts +12 -1
  309. package/dist/table/ToggleMenu.svelte +125 -90
  310. package/dist/table/index.d.ts +2 -0
  311. package/dist/table/index.js +2 -4
  312. package/dist/theme/ThemeControl.svelte +21 -12
  313. package/dist/time.js +4 -1
  314. package/dist/tooltip/TooltipContent.svelte +33 -8
  315. package/dist/trajectory/Trajectory.svelte +889 -687
  316. package/dist/trajectory/TrajectoryError.svelte +14 -3
  317. package/dist/trajectory/TrajectoryExportPane.svelte +148 -90
  318. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
  319. package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
  320. package/dist/trajectory/constants.d.ts +6 -0
  321. package/dist/trajectory/constants.js +7 -0
  322. package/dist/trajectory/extract.js +13 -31
  323. package/dist/trajectory/format-detect.d.ts +9 -0
  324. package/dist/trajectory/format-detect.js +76 -0
  325. package/dist/trajectory/frame-reader.d.ts +17 -0
  326. package/dist/trajectory/frame-reader.js +332 -0
  327. package/dist/trajectory/helpers.d.ts +14 -0
  328. package/dist/trajectory/helpers.js +172 -0
  329. package/dist/trajectory/index.d.ts +1 -0
  330. package/dist/trajectory/index.js +23 -14
  331. package/dist/trajectory/parse/ase.d.ts +2 -0
  332. package/dist/trajectory/parse/ase.js +77 -0
  333. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  334. package/dist/trajectory/parse/hdf5.js +129 -0
  335. package/dist/trajectory/parse/index.d.ts +12 -0
  336. package/dist/trajectory/parse/index.js +299 -0
  337. package/dist/trajectory/parse/lammps.d.ts +5 -0
  338. package/dist/trajectory/parse/lammps.js +179 -0
  339. package/dist/trajectory/parse/vasp.d.ts +2 -0
  340. package/dist/trajectory/parse/vasp.js +68 -0
  341. package/dist/trajectory/parse/xyz.d.ts +2 -0
  342. package/dist/trajectory/parse/xyz.js +110 -0
  343. package/dist/trajectory/plotting.js +13 -8
  344. package/dist/trajectory/types.d.ts +11 -0
  345. package/dist/trajectory/types.js +1 -0
  346. package/dist/utils.d.ts +3 -0
  347. package/dist/utils.js +17 -0
  348. package/dist/xrd/XrdPlot.svelte +337 -245
  349. package/dist/xrd/broadening.js +14 -9
  350. package/dist/xrd/calc-xrd.js +12 -19
  351. package/dist/xrd/parse.d.ts +1 -1
  352. package/dist/xrd/parse.js +17 -17
  353. package/package.json +103 -101
  354. package/readme.md +4 -4
  355. package/dist/trajectory/parse.d.ts +0 -42
  356. package/dist/trajectory/parse.js +0 -1267
  357. /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
  358. /package/dist/theme/{themes.js → themes.mjs} +0 -0
@@ -1,466 +1,660 @@
1
1
  <script
2
2
  lang="ts"
3
3
  generics="Metadata extends Record<string, unknown> = Record<string, unknown>"
4
- >import { format_num } from '../labels';
5
- import { T, useTask, useThrelte } from '@threlte/core';
6
- import * as extras from '@threlte/extras';
7
- import { scaleLinear } from 'd3-scale';
8
- import { onDestroy, untrack } from 'svelte';
9
- import * as THREE from 'three';
10
- import { Line2 } from 'three/examples/jsm/lines/Line2.js';
11
- import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
12
- import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
13
- import { get_series_color } from './data-transform';
14
- import ReferenceLine3D from './ReferenceLine3D.svelte';
15
- import ReferencePlane from './ReferencePlane.svelte';
16
- import { create_color_scale, create_size_scale } from './scales';
17
- import Surface3D from './Surface3D.svelte';
18
- let { series = [], series_visibility = [], x_axis = {}, y_axis = {}, z_axis = {}, display = {}, styles = {}, surfaces = [], ref_lines = [], ref_planes = [], color_scale = { type: `linear`, scheme: `interpolateViridis` }, size_scale = { type: `linear`, radius_range: [0.05, 0.2] }, camera_position = [10, 10, 10], camera_projection = `perspective`, auto_rotate = 0, rotation_damping = 0, fov = 50, min_zoom = 0.1, max_zoom = 100, rotate_speed = 2, zoom_speed = 2, pan_speed = 2, ambient_light = 0.6, directional_light = 0.8, sphere_segments = 16, gizmo = true, hovered_point = $bindable(null), on_point_click, on_point_hover, tooltip, scene = $bindable(), camera = $bindable(), orbit_controls = $bindable(), width = 0, height = 0, } = $props();
19
- const threlte = useThrelte();
20
- $effect(() => {
21
- scene = threlte.scene;
22
- camera = threlte.camera.current;
23
- });
24
- extras.interactivity();
25
- // Scene dimensions: x/y are horizontal (2:2), z is vertical (1)
26
- // Note: In Three.js, Y is vertical. We map user's Z → Three.js Y (vertical)
27
- // and user's Y Three.js Z (depth). So scene_z here refers to Three.js Y.
28
- const scene_x = 10; // user X → Three.js X (horizontal)
29
- const scene_y = 10; // user Y → Three.js Z (depth/horizontal)
30
- const scene_z = 5; // user Z → Three.js Y (vertical)
31
- const half_x = scene_x / 2;
32
- const half_y = scene_y / 2;
33
- const half_z = scene_z / 2;
34
- // Dynamic backside positions - axes/grids/planes always face away from camera
35
- // pos.x/y/z are the Three.js positions where axes attach (backside of cube)
36
- let pos = $state({ x: -half_x, y: -half_z, z: -half_y });
37
- // Update backside positions when camera crosses axis planes
38
- useTask(() => {
39
- if (!camera)
40
- return;
41
- const cam = camera.position;
4
+ >
5
+ import type { D3ColorSchemeName, D3InterpolateName } from '../colors'
6
+ import { format_num } from '../labels'
7
+ import type { Vec2, Vec3 } from '../math'
8
+ import type {
9
+ AxisConfig3D,
10
+ CameraProjection3D,
11
+ DataSeries3D,
12
+ DisplayConfig3D,
13
+ InternalPoint3D,
14
+ RefLine3D,
15
+ RefPlane,
16
+ ScaleType,
17
+ Scatter3DHandlerEvent,
18
+ StyleOverrides3D,
19
+ Surface3DConfig,
20
+ } from './types'
21
+ import { T, useTask, useThrelte } from '@threlte/core'
22
+ import * as extras from '@threlte/extras'
23
+ import { scaleLinear } from 'd3-scale'
24
+ import { type ComponentProps, onDestroy, type Snippet, untrack } from 'svelte'
25
+ import type { Camera, Scene } from 'three'
26
+ import * as THREE from 'three'
27
+ import { Line2 } from 'three/examples/jsm/lines/Line2.js'
28
+ import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
29
+ import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
30
+ import { get_series_color } from './data-transform'
31
+ import { normalize_to_scene } from './reference-line'
32
+ import ReferenceLine3D from './ReferenceLine3D.svelte'
33
+ import ReferencePlane from './ReferencePlane.svelte'
34
+ import { create_color_scale, create_size_scale } from './scales'
35
+ import Surface3D from './Surface3D.svelte'
36
+
37
+ let {
38
+ series = [],
39
+ series_visibility = [],
40
+ x_axis = {},
41
+ y_axis = {},
42
+ z_axis = {},
43
+ display = {},
44
+ styles = {},
45
+ surfaces = [],
46
+ ref_lines = [],
47
+ ref_planes = [],
48
+ color_scale = { type: `linear`, scheme: `interpolateViridis` },
49
+ size_scale = { type: `linear`, radius_range: [0.05, 0.2] },
50
+ camera_position = [10, 10, 10] as Vec3,
51
+ camera_projection = `perspective` as CameraProjection3D,
52
+ auto_rotate = 0,
53
+ rotation_damping = 0,
54
+ fov = 50,
55
+ min_zoom = 0.1,
56
+ max_zoom = 100,
57
+ rotate_speed = 2,
58
+ zoom_speed = 2,
59
+ pan_speed = 2,
60
+ ambient_light = 0.6,
61
+ directional_light = 0.8,
62
+ sphere_segments = 16,
63
+ gizmo = true,
64
+ hovered_point = $bindable(null),
65
+ on_point_click,
66
+ on_point_hover,
67
+ tooltip,
68
+ scene = $bindable(),
69
+ camera = $bindable(),
70
+ orbit_controls = $bindable(),
71
+ width = 0,
72
+ height = 0,
73
+ }: {
74
+ series?: DataSeries3D<Metadata>[]
75
+ series_visibility?: boolean[]
76
+ x_axis?: AxisConfig3D
77
+ y_axis?: AxisConfig3D
78
+ z_axis?: AxisConfig3D
79
+ display?: DisplayConfig3D
80
+ styles?: StyleOverrides3D
81
+ surfaces?: Surface3DConfig[]
82
+ ref_lines?: RefLine3D[]
83
+ ref_planes?: RefPlane[]
84
+ color_scale?: {
85
+ type?: ScaleType
86
+ scheme?: D3ColorSchemeName | D3InterpolateName
87
+ value_range?: [number, number]
88
+ }
89
+ size_scale?: {
90
+ type?: ScaleType
91
+ radius_range?: [number, number]
92
+ value_range?: [number, number]
93
+ }
94
+ camera_position?: Vec3
95
+ camera_projection?: CameraProjection3D
96
+ auto_rotate?: number
97
+ rotation_damping?: number
98
+ fov?: number
99
+ min_zoom?: number
100
+ max_zoom?: number
101
+ rotate_speed?: number
102
+ zoom_speed?: number
103
+ pan_speed?: number
104
+ ambient_light?: number
105
+ directional_light?: number
106
+ sphere_segments?: number
107
+ gizmo?: boolean | ComponentProps<typeof extras.Gizmo>
108
+ hovered_point?: InternalPoint3D<Metadata> | null
109
+ on_point_click?: (data: Scatter3DHandlerEvent<Metadata>) => void
110
+ on_point_hover?: (data: Scatter3DHandlerEvent<Metadata> | null) => void
111
+ tooltip?: Snippet<[Scatter3DHandlerEvent<Metadata>]>
112
+ scene?: Scene
113
+ camera?: Camera
114
+ orbit_controls?: ComponentProps<typeof extras.OrbitControls>[`ref`]
115
+ width?: number
116
+ height?: number
117
+ } = $props()
118
+
119
+ const threlte = useThrelte()
120
+ $effect(() => {
121
+ scene = threlte.scene
122
+ camera = threlte.camera.current
123
+ })
124
+
125
+ extras.interactivity()
126
+
127
+ // Scene dimensions: x/y are horizontal (2:2), z is vertical (1)
128
+ // Note: In Three.js, Y is vertical. We map user's Z → Three.js Y (vertical)
129
+ // and user's Y → Three.js Z (depth). So scene_z here refers to Three.js Y.
130
+ const scene_x = 10 // user X → Three.js X (horizontal)
131
+ const scene_y = 10 // user Y → Three.js Z (depth/horizontal)
132
+ const scene_z = 5 // user Z → Three.js Y (vertical)
133
+ const half_x = scene_x / 2
134
+ const half_y = scene_y / 2
135
+ const half_z = scene_z / 2
136
+
137
+ // Dynamic backside positions - axes/grids/planes always face away from camera
138
+ // pos.x/y/z are the Three.js positions where axes attach (backside of cube)
139
+ let pos = $state({ x: -half_x, y: -half_z, z: -half_y })
140
+
141
+ // Update backside positions when camera crosses axis planes
142
+ useTask(() => {
143
+ if (!camera) return
144
+ const cam = camera.position
42
145
  // Only update when sign changes to avoid triggering geometry recreation every frame
43
- const new_x = cam.x > 0 ? -half_x : half_x;
44
- const new_y = cam.y > 0 ? -half_z : half_z;
45
- const new_z = cam.z > 0 ? -half_y : half_y;
46
- if (pos.x !== new_x)
47
- pos.x = new_x;
48
- if (pos.y !== new_y)
49
- pos.y = new_y;
50
- if (pos.z !== new_z)
51
- pos.z = new_z;
52
- });
53
- // Sign helpers for tick/label offsets (point outward from cube center)
54
- const sign_x = $derived(pos.x < 0 ? -1 : 1);
55
- const sign_y = $derived(pos.y < 0 ? -1 : 1);
56
- // Flatten all points from series
57
- let all_points = $derived(series
58
- .filter(Boolean)
59
- .flatMap((srs, series_idx) => srs.x.map((x_val, point_idx) => ({
60
- x: x_val,
61
- y: srs.y[point_idx],
62
- z: srs.z[point_idx],
63
- series_idx,
64
- point_idx,
65
- color_value: srs.color_values?.[point_idx] ?? null,
66
- size_value: srs.size_values?.[point_idx] ?? null,
67
- metadata: Array.isArray(srs.metadata)
68
- ? srs.metadata[point_idx]
69
- : srs.metadata,
70
- point_style: Array.isArray(srs.point_style)
71
- ? srs.point_style[point_idx]
72
- : srs.point_style,
73
- }))));
74
- // Sample surface points for range calculation (10x10 grid)
75
- function sample_surface(surface) {
76
- const n = 10;
77
- const pts = [];
146
+ const new_x = cam.x > 0 ? -half_x : half_x
147
+ const new_y = cam.y > 0 ? -half_z : half_z
148
+ const new_z = cam.z > 0 ? -half_y : half_y
149
+ if (pos.x !== new_x) pos.x = new_x
150
+ if (pos.y !== new_y) pos.y = new_y
151
+ if (pos.z !== new_z) pos.z = new_z
152
+ })
153
+
154
+ // Sign helpers for tick/label offsets (point outward from cube center)
155
+ const sign_x = $derived(pos.x < 0 ? -1 : 1)
156
+ const sign_y = $derived(pos.y < 0 ? -1 : 1)
157
+
158
+ // Flatten all points from series
159
+ let all_points = $derived(
160
+ series
161
+ .filter(Boolean)
162
+ .flatMap((srs, series_idx) =>
163
+ srs.x.map((x_val, point_idx) => ({
164
+ x: x_val,
165
+ y: srs.y[point_idx],
166
+ z: srs.z[point_idx],
167
+ series_idx,
168
+ point_idx,
169
+ color_value: srs.color_values?.[point_idx] ?? null,
170
+ size_value: srs.size_values?.[point_idx] ?? null,
171
+ metadata: Array.isArray(srs.metadata)
172
+ ? srs.metadata[point_idx]
173
+ : srs.metadata,
174
+ point_style: Array.isArray(srs.point_style)
175
+ ? srs.point_style[point_idx]
176
+ : srs.point_style,
177
+ }))
178
+ ),
179
+ )
180
+
181
+ // Sample surface points for range calculation (10x10 grid)
182
+ function sample_surface(
183
+ surface: Surface3DConfig,
184
+ ): { x: number; y: number; z: number }[] {
185
+ const n = 10
186
+ const pts: { x: number; y: number; z: number }[] = []
78
187
  if (surface.type === `grid` && surface.z_fn) {
79
- const [x0, x1] = surface.x_range ?? [-1, 1];
80
- const [y0, y1] = surface.y_range ?? [-1, 1];
81
- for (let i = 0; i <= n; i++) {
82
- for (let j = 0; j <= n; j++) {
83
- const x = x0 + (i / n) * (x1 - x0), y = y0 + (j / n) * (y1 - y0);
84
- pts.push({ x, y, z: surface.z_fn(x, y) });
85
- }
188
+ const [x0, x1] = surface.x_range ?? [-1, 1]
189
+ const [y0, y1] = surface.y_range ?? [-1, 1]
190
+ for (let i = 0; i <= n; i++) {
191
+ for (let j = 0; j <= n; j++) {
192
+ const x = x0 + (i / n) * (x1 - x0), y = y0 + (j / n) * (y1 - y0)
193
+ pts.push({ x, y, z: surface.z_fn(x, y) })
86
194
  }
87
- }
88
- else if (surface.type === `parametric` && surface.parametric_fn) {
89
- const [u0, u1] = surface.u_range ?? [0, 1];
90
- const [v0, v1] = surface.v_range ?? [0, 1];
91
- for (let i = 0; i <= n; i++) {
92
- for (let j = 0; j <= n; j++) {
93
- pts.push(surface.parametric_fn(u0 + (i / n) * (u1 - u0), v0 + (j / n) * (v1 - v0)));
94
- }
195
+ }
196
+ } else if (surface.type === `parametric` && surface.parametric_fn) {
197
+ const [u0, u1] = surface.u_range ?? [0, 1]
198
+ const [v0, v1] = surface.v_range ?? [0, 1]
199
+ for (let i = 0; i <= n; i++) {
200
+ for (let j = 0; j <= n; j++) {
201
+ pts.push(
202
+ surface.parametric_fn(u0 + (i / n) * (u1 - u0), v0 + (j / n) * (v1 - v0)),
203
+ )
95
204
  }
205
+ }
206
+ } else if (surface.type === `triangulated` && surface.points) {
207
+ pts.push(...surface.points)
96
208
  }
97
- else if (surface.type === `triangulated` && surface.points) {
98
- pts.push(...surface.points);
99
- }
100
- return pts.filter((pt) => isFinite(pt.x) && isFinite(pt.y) && isFinite(pt.z));
101
- }
102
- // Compute axis range with D3's nice() for clean boundaries
103
- function compute_range(values, range) {
104
- if (range?.[0] != null && range?.[1] != null)
105
- return range;
106
- const valid = values.filter(isFinite);
107
- if (!valid.length)
108
- return [0, 1];
109
- let [min, max] = [Math.min(...valid), Math.max(...valid)];
209
+ return pts.filter((pt) => isFinite(pt.x) && isFinite(pt.y) && isFinite(pt.z))
210
+ }
211
+
212
+ // Compute axis range with D3's nice() for clean boundaries
213
+ function compute_range(
214
+ values: number[],
215
+ range?: [number | null, number | null],
216
+ ): Vec2 {
217
+ if (range?.[0] != null && range?.[1] != null) return range as Vec2
218
+ const valid = values.filter(isFinite)
219
+ if (!valid.length) return [0, 1]
220
+ let [min, max] = [Math.min(...valid), Math.max(...valid)]
110
221
  const pad = min === max
111
- ? (min === 0 ? 1 : Math.abs(min * 0.1))
112
- : (max - min) * 0.05;
113
- if (range?.[0] == null)
114
- min -= pad;
115
- if (range?.[1] == null)
116
- max += pad;
222
+ ? (min === 0 ? 1 : Math.abs(min * 0.1))
223
+ : (max - min) * 0.05
224
+ if (range?.[0] == null) min -= pad
225
+ if (range?.[1] == null) max += pad
117
226
  return scaleLinear().domain([range?.[0] ?? min, range?.[1] ?? max]).nice()
118
- .domain();
119
- }
120
- // Collect xyz values from points and surfaces
121
- let surface_samples = $derived(surfaces.flatMap(sample_surface));
122
- let x_range = $derived(compute_range([
123
- ...all_points.map((p) => p.x),
124
- ...surface_samples.map((p) => p.x),
125
- ], x_axis.range));
126
- let y_range = $derived(compute_range([
127
- ...all_points.map((p) => p.y),
128
- ...surface_samples.map((p) => p.y),
129
- ], y_axis.range));
130
- let z_range = $derived(compute_range([
131
- ...all_points.map((p) => p.z),
132
- ...surface_samples.map((p) => p.z),
133
- ], z_axis.range));
134
- // Normalize value to scene coordinates (centered around 0)
135
- function normalize(value, [min_val, max_val], scene_size) {
136
- return ((value - min_val) / (max_val - min_val || 1) - 0.5) * scene_size;
137
- }
138
- const normalize_x = (v) => normalize(v, x_range, scene_x);
139
- const normalize_y = (v) => normalize(v, y_range, scene_y);
140
- const normalize_z = (v) => normalize(v, z_range, scene_z);
141
- // Color/size scales
142
- let all_color_values = $derived(all_points.map((pt) => pt.color_value).filter((v) => v != null));
143
- let auto_color_range = $derived.by(() => {
144
- if (!all_color_values.length)
145
- return [0, 1];
146
- let min = all_color_values[0];
147
- let max = all_color_values[0];
227
+ .domain() as Vec2
228
+ }
229
+
230
+ // Collect xyz values from points and surfaces
231
+ let surface_samples = $derived(surfaces.flatMap(sample_surface))
232
+ let x_range = $derived(
233
+ compute_range([
234
+ ...all_points.map((point) => point.x),
235
+ ...surface_samples.map((point) => point.x),
236
+ ], x_axis.range),
237
+ )
238
+ let y_range = $derived(
239
+ compute_range([
240
+ ...all_points.map((point) => point.y),
241
+ ...surface_samples.map((point) => point.y),
242
+ ], y_axis.range),
243
+ )
244
+ let z_range = $derived(
245
+ compute_range([
246
+ ...all_points.map((point) => point.z),
247
+ ...surface_samples.map((point) => point.z),
248
+ ], z_axis.range),
249
+ )
250
+
251
+ const normalize_x = (value: number) => normalize_to_scene(value, x_range, scene_x)
252
+ const normalize_y = (value: number) => normalize_to_scene(value, y_range, scene_y)
253
+ const normalize_z = (value: number) => normalize_to_scene(value, z_range, scene_z)
254
+
255
+ // Color/size scales
256
+ let all_color_values = $derived(
257
+ all_points.map((pt) => pt.color_value).filter((val): val is number => val != null),
258
+ )
259
+ let auto_color_range: [number, number] = $derived.by(() => {
260
+ if (!all_color_values.length) return [0, 1]
261
+ let min = all_color_values[0]
262
+ let max = all_color_values[0]
148
263
  for (const val of all_color_values) {
149
- if (val < min)
150
- min = val;
151
- else if (val > max)
152
- max = val;
264
+ if (val < min) min = val
265
+ else if (val > max) max = val
153
266
  }
154
- return [min, max];
155
- });
156
- let all_size_values = $derived(all_points.map((pt) => pt.size_value).filter((val) => val != null));
157
- let color_scale_fn = $derived(create_color_scale(color_scale, auto_color_range));
158
- let size_scale_fn = $derived(create_size_scale(size_scale, all_size_values));
159
- // Process points with normalized positions
160
- // Swap Y/Z for Three.js: user Z → Three.js Y (vertical), user Y → Three.js Z (depth)
161
- let processed_points = $derived(all_points.map((pt) => ({
162
- ...pt,
163
- x: normalize_x(pt.x), // user X → Three.js X
164
- y: normalize_z(pt.z), // user Z → Three.js Y (vertical)
165
- z: normalize_y(pt.y), // user Y → Three.js Z (depth)
166
- })));
167
- let radius_groups = $derived.by(() => {
168
- const groups = {};
267
+ return [min, max]
268
+ })
269
+ let all_size_values = $derived(
270
+ all_points.map((pt) => pt.size_value).filter((val): val is number => val != null),
271
+ )
272
+ let color_scale_fn = $derived(create_color_scale(color_scale, auto_color_range))
273
+ let size_scale_fn = $derived(create_size_scale(size_scale, all_size_values))
274
+
275
+ // Process points with normalized positions
276
+ // Swap Y/Z for Three.js: user Z → Three.js Y (vertical), user Y → Three.js Z (depth)
277
+ let processed_points = $derived(
278
+ all_points.map((pt): InternalPoint3D<Metadata> => ({
279
+ ...pt,
280
+ x: normalize_x(pt.x), // user X → Three.js X
281
+ y: normalize_z(pt.z), // user Z → Three.js Y (vertical)
282
+ z: normalize_y(pt.y), // user Y → Three.js Z (depth)
283
+ })),
284
+ )
285
+
286
+ // Group points by radius, with per-instance colors
287
+ type RadiusGroup = {
288
+ radius: number
289
+ points: InternalPoint3D<Metadata>[]
290
+ colors: string[]
291
+ }
292
+
293
+ let radius_groups = $derived.by((): RadiusGroup[] => {
294
+ const groups: Record<string, RadiusGroup> = {}
169
295
  for (const pt of processed_points) {
170
- const srs = series[pt.series_idx];
171
- if (!(series_visibility[pt.series_idx] ?? srs?.visible ?? true))
172
- continue;
173
- const color = pt.color_value != null
174
- ? color_scale_fn(pt.color_value)
175
- : pt.point_style?.fill ?? get_series_color(pt.series_idx);
176
- const radius = pt.size_value != null
177
- ? size_scale_fn(pt.size_value)
178
- : (pt.point_style?.radius ?? styles.point?.size ?? 2) * 0.05;
179
- const key = radius.toFixed(4);
180
- (groups[key] ??= { radius, points: [], colors: [] }).points.push(pt);
181
- groups[key].colors.push(color);
296
+ const srs = series[pt.series_idx]
297
+ if (!(series_visibility[pt.series_idx] ?? srs?.visible ?? true)) continue
298
+ const color = pt.color_value != null
299
+ ? color_scale_fn(pt.color_value)
300
+ : pt.point_style?.fill ?? get_series_color(pt.series_idx)
301
+ const radius = pt.size_value != null
302
+ ? size_scale_fn(pt.size_value)
303
+ : (pt.point_style?.radius ?? styles.point?.size ?? 2) * 0.05
304
+ const key = radius.toFixed(4)
305
+ ;(groups[key] ??= { radius, points: [], colors: [] }).points.push(pt)
306
+ groups[key].colors.push(color)
182
307
  }
183
- return Object.values(groups);
184
- });
185
- // Projection settings - render point shadows on background planes
186
- let proj_opacity = $derived(display.projection_opacity ?? 0.3);
187
- let proj_scale = $derived(display.projection_scale ?? 0.5);
188
- let projection_configs = $derived([`xy`, `xz`, `yz`]
189
- .filter((key) => display.projections?.[key])
190
- .map((key) => ({
191
- key,
192
- get_pos: key === `xy`
193
- ? (pt) => [pt.x, pos.y, pt.z]
194
- : key === `xz`
195
- ? (pt) => [pt.x, pt.y, pos.z]
196
- : (pt) => [pos.x, pt.y, pt.z],
197
- })));
198
- // Track previous lines for cleanup
199
- let series_lines = $state([]);
200
- $effect(() => {
308
+ return Object.values(groups)
309
+ })
310
+
311
+ // Projection settings - render point shadows on background planes
312
+ let proj_opacity = $derived(display.projection_opacity ?? 0.3)
313
+ let proj_scale = $derived(display.projection_scale ?? 0.5)
314
+
315
+ // Projection plane configs: each fixes one axis to the backside position
316
+ type ProjectionConfig = {
317
+ key: `xy` | `xz` | `yz`
318
+ get_pos: (pt: InternalPoint3D<Metadata>) => Vec3
319
+ }
320
+ let projection_configs = $derived(
321
+ ([`xy`, `xz`, `yz`] as const)
322
+ .filter((key) => display.projections?.[key])
323
+ .map((key): ProjectionConfig => ({
324
+ key,
325
+ get_pos: key === `xy`
326
+ ? (pt) => [pt.x, pos.y, pt.z]
327
+ : key === `xz`
328
+ ? (pt) => [pt.x, pt.y, pos.z]
329
+ : (pt) => [pos.x, pt.y, pt.z],
330
+ })),
331
+ )
332
+
333
+ // Series line data for connecting points
334
+ type SeriesLineData = {
335
+ series_idx: number
336
+ color: string
337
+ width: number
338
+ dashed: boolean
339
+ line2: Line2
340
+ geometry: LineGeometry
341
+ material: LineMaterial
342
+ }
343
+
344
+ // Track previous lines for cleanup
345
+ let series_lines: SeriesLineData[] = $state([])
346
+
347
+ $effect(() => {
201
348
  // Dispose old lines before creating new ones
202
349
  for (const line_data of untrack(() => series_lines)) {
203
- line_data.geometry.dispose();
204
- line_data.material.dispose();
350
+ line_data.geometry.dispose()
351
+ line_data.material.dispose()
205
352
  }
206
- const lines = [];
353
+
354
+ const lines: SeriesLineData[] = []
207
355
  for (let series_idx = 0; series_idx < series.length; series_idx++) {
208
- const srs = series[series_idx];
209
- if (!srs?.line_style)
210
- continue;
211
- if (!(series_visibility[series_idx] ?? srs.visible ?? true))
212
- continue;
213
- // Get points for this series in order
214
- const series_points = processed_points
215
- .filter((pt) => pt.series_idx === series_idx)
216
- .sort((a, b) => a.point_idx - b.point_idx);
217
- if (series_points.length < 2)
218
- continue;
219
- // Create fat line geometry (LineGeometry for Line2)
220
- const positions = [];
221
- for (const pt of series_points) {
222
- positions.push(pt.x, pt.y, pt.z);
223
- }
224
- const geometry = new LineGeometry();
225
- geometry.setPositions(positions);
226
- // Determine line style
227
- const line_style = srs.line_style;
228
- const color = line_style.stroke ??
229
- (Array.isArray(srs.point_style)
230
- ? srs.point_style[0]?.fill
231
- : srs.point_style?.fill) ??
232
- get_series_color(series_idx);
233
- const line_width = line_style.stroke_width ?? 2;
234
- const dashed = Boolean(line_style.line_dash);
235
- // Create LineMaterial for fat lines (linewidth is in pixels when resolution is set)
236
- // Use placeholder resolution; the separate resolution effect updates it
237
- const material = new LineMaterial({
238
- color: new THREE.Color(color).getHex(),
239
- linewidth: line_width, // Width in pixels
240
- dashed,
241
- dashScale: dashed ? 2 : 1,
242
- dashSize: 0.1,
243
- gapSize: 0.05,
244
- resolution: new THREE.Vector2(1, 1),
245
- });
246
- const line2 = new Line2(geometry, material);
247
- line2.computeLineDistances();
248
- lines.push({
249
- series_idx,
250
- color,
251
- width: line_width,
252
- dashed,
253
- line2,
254
- geometry,
255
- material,
256
- });
356
+ const srs = series[series_idx]
357
+ if (!srs?.line_style) continue
358
+ if (!(series_visibility[series_idx] ?? srs.visible ?? true)) continue
359
+
360
+ // Get points for this series in order
361
+ const series_points = processed_points
362
+ .filter((pt) => pt.series_idx === series_idx)
363
+ .sort((a, b) => a.point_idx - b.point_idx)
364
+
365
+ if (series_points.length < 2) continue
366
+
367
+ // Create fat line geometry (LineGeometry for Line2)
368
+ const positions: number[] = []
369
+ for (const pt of series_points) {
370
+ positions.push(pt.x, pt.y, pt.z)
371
+ }
372
+ const geometry = new LineGeometry()
373
+ geometry.setPositions(positions)
374
+
375
+ // Determine line style
376
+ const line_style = srs.line_style
377
+ const color = line_style.stroke ??
378
+ (Array.isArray(srs.point_style)
379
+ ? srs.point_style[0]?.fill
380
+ : srs.point_style?.fill) ??
381
+ get_series_color(series_idx)
382
+ const line_width = line_style.stroke_width ?? 2
383
+ const dashed = Boolean(line_style.line_dash)
384
+
385
+ // Create LineMaterial for fat lines (linewidth is in pixels when resolution is set)
386
+ // Use placeholder resolution; the separate resolution effect updates it
387
+ const material = new LineMaterial({
388
+ color: new THREE.Color(color).getHex(),
389
+ linewidth: line_width, // Width in pixels
390
+ dashed,
391
+ dashScale: dashed ? 2 : 1,
392
+ dashSize: 0.1,
393
+ gapSize: 0.05,
394
+ resolution: new THREE.Vector2(1, 1),
395
+ })
396
+
397
+ const line2 = new Line2(geometry, material)
398
+ line2.computeLineDistances()
399
+
400
+ lines.push({
401
+ series_idx,
402
+ color,
403
+ width: line_width,
404
+ dashed,
405
+ line2,
406
+ geometry,
407
+ material,
408
+ })
257
409
  }
258
- series_lines = lines;
259
- });
260
- // Update LineMaterial resolution when canvas size changes
261
- $effect(() => {
262
- const canvas_width = width || 1;
263
- const canvas_height = height || 1;
410
+ series_lines = lines
411
+ })
412
+
413
+ // Update LineMaterial resolution when canvas size changes
414
+ $effect(() => {
415
+ const canvas_width = width || 1
416
+ const canvas_height = height || 1
264
417
  for (const line_data of series_lines) {
265
- line_data.material.resolution.set(canvas_width, canvas_height);
418
+ line_data.material.resolution.set(canvas_width, canvas_height)
266
419
  }
267
- });
268
- // Cleanup on component destroy
269
- onDestroy(() => {
420
+ })
421
+
422
+ // Cleanup on component destroy
423
+ onDestroy(() => {
270
424
  for (const { geometry, material } of series_lines) {
271
- geometry.dispose();
272
- material.dispose();
425
+ geometry.dispose()
426
+ material.dispose()
273
427
  }
274
- Object.values(axis_geometries).forEach((g) => g.dispose());
428
+ Object.values(axis_geometries).forEach((geom) => geom.dispose())
275
429
  for (const data of Object.values(axis_geom_data)) {
276
- data.tick_geoms.forEach((g) => g.dispose());
277
- data.grid_geoms.flat().forEach((g) => g.dispose());
430
+ data.tick_geoms.forEach((geom) => geom.dispose())
431
+ data.grid_geoms.flat().forEach((geom) => geom.dispose())
278
432
  }
279
- });
280
- // Generate axis ticks using D3's smart tick generation
281
- function gen_ticks(range, ticks) {
282
- if (Array.isArray(ticks))
283
- return ticks;
284
- const [min, max] = range;
285
- if (!isFinite(min) || !isFinite(max) || min === max)
286
- return [min];
287
- const count = typeof ticks === `number` ? ticks : 5;
288
- return scaleLinear().domain([min, max]).ticks(count);
289
- }
290
- let x_ticks = $derived(gen_ticks(x_range, x_axis.ticks));
291
- let y_ticks = $derived(gen_ticks(y_range, y_axis.ticks));
292
- let z_ticks = $derived(gen_ticks(z_range, z_axis.ticks));
293
- // Create axis line geometry - reuses a shared Float32Array for efficiency
294
- function create_line_geometry(start, end) {
295
- const geometry = new THREE.BufferGeometry();
296
- const positions = new Float32Array([...start, ...end]);
297
- geometry.setAttribute(`position`, new THREE.BufferAttribute(positions, 3));
298
- return geometry;
299
- }
300
- // Build event data for point interactions
301
- function make_event_data(point, event) {
302
- const orig = all_points.find((pt) => pt.series_idx === point.series_idx && pt.point_idx === point.point_idx);
303
- if (!orig)
304
- return null;
433
+ })
434
+
435
+ // Generate axis ticks using D3's smart tick generation
436
+ function gen_ticks(
437
+ range: [number, number],
438
+ ticks?: AxisConfig3D[`ticks`],
439
+ ): number[] {
440
+ if (Array.isArray(ticks)) return ticks
441
+ const [min, max] = range
442
+ if (!isFinite(min) || !isFinite(max) || min === max) return [min]
443
+ const count = typeof ticks === `number` ? ticks : 5
444
+ return scaleLinear().domain([min, max]).ticks(count)
445
+ }
446
+
447
+ let x_ticks = $derived(gen_ticks(x_range, x_axis.ticks))
448
+ let y_ticks = $derived(gen_ticks(y_range, y_axis.ticks))
449
+ let z_ticks = $derived(gen_ticks(z_range, z_axis.ticks))
450
+
451
+ // Create axis line geometry - reuses a shared Float32Array for efficiency
452
+ function create_line_geometry(start: Vec3, end: Vec3): THREE.BufferGeometry {
453
+ const geometry = new THREE.BufferGeometry()
454
+ const positions = new Float32Array([...start, ...end])
455
+ geometry.setAttribute(`position`, new THREE.BufferAttribute(positions, 3))
456
+ return geometry
457
+ }
458
+
459
+ // Build event data for point interactions
460
+ function make_event_data(
461
+ point: InternalPoint3D<Metadata>,
462
+ event?: MouseEvent,
463
+ ): Scatter3DHandlerEvent<Metadata> | null {
464
+ const orig = all_points.find(
465
+ (pt) => pt.series_idx === point.series_idx && pt.point_idx === point.point_idx,
466
+ )
467
+ if (!orig) return null
305
468
  return {
306
- x: orig.x,
307
- y: orig.y,
308
- z: orig.z,
309
- metadata: point.metadata ?? null,
310
- label: series[point.series_idx]?.label ?? null,
311
- series_idx: point.series_idx,
312
- x_axis,
313
- y_axis,
314
- z_axis,
315
- x_formatted: format_num(orig.x, x_axis.format || `.3~g`),
316
- y_formatted: format_num(orig.y, y_axis.format || `.3~g`),
317
- z_formatted: format_num(orig.z, z_axis.format || `.3~g`),
318
- color_value: point.color_value,
319
- fullscreen: false,
320
- event,
321
- point,
322
- };
323
- }
324
- function handle_point_enter(point) {
325
- hovered_point = point;
326
- const data = make_event_data(point);
327
- if (data)
328
- on_point_hover?.(data);
329
- }
330
- function handle_point_click(point, event) {
331
- const data = make_event_data(point, event);
332
- if (data)
333
- on_point_click?.(data);
334
- }
335
- // Gizmo props - parent (ScatterPlot3D) handles className and ColorBar offset adjustments
336
- let gizmo_props = $derived(gizmo === false
337
- ? null
338
- : gizmo === true
339
- ? { background: { enabled: false }, offset: { left: 5, bottom: 5 } }
340
- : gizmo);
341
- // Orbit controls - snappy with minimal inertia
342
- let orbit_controls_props = $derived({
469
+ x: orig.x,
470
+ y: orig.y,
471
+ z: orig.z,
472
+ metadata: point.metadata ?? null,
473
+ label: series[point.series_idx]?.label ?? null,
474
+ series_idx: point.series_idx,
475
+ x_axis,
476
+ y_axis,
477
+ z_axis,
478
+ x_formatted: format_num(orig.x, x_axis.format || `.3~g`),
479
+ y_formatted: format_num(orig.y, y_axis.format || `.3~g`),
480
+ z_formatted: format_num(orig.z, z_axis.format || `.3~g`),
481
+ color_value: point.color_value,
482
+ fullscreen: false,
483
+ event,
484
+ point,
485
+ }
486
+ }
487
+
488
+ function handle_point_enter(point: InternalPoint3D<Metadata>) {
489
+ hovered_point = point
490
+ const data = make_event_data(point)
491
+ if (data) on_point_hover?.(data)
492
+ }
493
+
494
+ function handle_point_click(point: InternalPoint3D<Metadata>, event: MouseEvent) {
495
+ const data = make_event_data(point, event)
496
+ if (data) on_point_click?.(data)
497
+ }
498
+
499
+ // Gizmo props - parent (ScatterPlot3D) handles className and ColorBar offset adjustments
500
+ let gizmo_props = $derived(
501
+ gizmo === false
502
+ ? null
503
+ : gizmo === true
504
+ ? { background: { enabled: false }, offset: { left: 5, bottom: 5 } }
505
+ : gizmo,
506
+ )
507
+
508
+ // Orbit controls - snappy with minimal inertia
509
+ let orbit_controls_props = $derived({
343
510
  enableRotate: rotate_speed > 0,
344
511
  rotateSpeed: rotate_speed,
345
512
  enableZoom: zoom_speed > 0,
346
513
  zoomSpeed: zoom_speed,
347
514
  enablePan: pan_speed > 0,
348
515
  panSpeed: pan_speed,
349
- target: [0, 0, 0],
516
+ target: [0, 0, 0] as Vec3,
350
517
  maxZoom: max_zoom,
351
518
  minZoom: min_zoom,
352
519
  autoRotate: Boolean(auto_rotate),
353
520
  autoRotateSpeed: auto_rotate,
354
521
  enableDamping: rotation_damping > 0,
355
522
  dampingFactor: rotation_damping,
356
- });
357
- // Axis configuration for rendering
358
- const tick_length = 0.15;
359
- const AXIS_KEYS = [`x`, `y`, `z`];
360
- // Main axis line geometries - updated when backside positions change
361
- let axis_geometries = $state({
523
+ })
524
+
525
+ // Axis configuration for rendering
526
+ const tick_length = 0.15
527
+ type AxisKey = `x` | `y` | `z`
528
+ const AXIS_KEYS: readonly AxisKey[] = [`x`, `y`, `z`]
529
+
530
+ // Main axis line geometries - updated when backside positions change
531
+ let axis_geometries: Record<AxisKey, THREE.BufferGeometry> = $state({
362
532
  x: create_line_geometry([-half_x, -half_z, -half_y], [half_x, -half_z, -half_y]),
363
533
  y: create_line_geometry([-half_x, -half_z, -half_y], [-half_x, -half_z, half_y]),
364
534
  z: create_line_geometry([-half_x, -half_z, -half_y], [-half_x, half_z, -half_y]),
365
- });
366
- $effect(() => {
535
+ })
536
+
537
+ $effect(() => {
367
538
  // Capture pos values for dependency tracking
368
- const { x: px, y: py, z: pz } = pos;
539
+ const { x: px, y: py, z: pz } = pos
369
540
  untrack(() => {
370
- for (const key of AXIS_KEYS)
371
- axis_geometries[key].dispose();
372
- });
541
+ for (const key of AXIS_KEYS) axis_geometries[key].dispose()
542
+ })
373
543
  // X-axis: spans full X, positioned at backside Y and Z
374
- axis_geometries.x = create_line_geometry([-half_x, py, pz], [half_x, py, pz]);
544
+ axis_geometries.x = create_line_geometry([-half_x, py, pz], [half_x, py, pz])
375
545
  // Y-axis (user Y → Three.js Z): spans full Z, positioned at backside X and Y
376
- axis_geometries.y = create_line_geometry([px, py, -half_y], [px, py, half_y]);
546
+ axis_geometries.y = create_line_geometry([px, py, -half_y], [px, py, half_y])
377
547
  // Z-axis (user Z → Three.js Y): spans full Y, positioned at backside X and Z
378
- axis_geometries.z = create_line_geometry([px, -half_z, pz], [px, half_z, pz]);
379
- });
380
- // Axis rendering config - all positions use backside `pos` values
381
- let axes_config = $derived([
548
+ axis_geometries.z = create_line_geometry([px, -half_z, pz], [px, half_z, pz])
549
+ })
550
+
551
+ // Axis rendering config - all positions use backside `pos` values
552
+ let axes_config = $derived([
382
553
  {
383
- key: `x`,
384
- color: `#ef4444`,
385
- axis: x_axis,
386
- ticks: x_ticks,
387
- range: x_range,
388
- get_tick_pos: (val) => [normalize_x(val), pos.y, pos.z],
389
- get_tick_end: (val) => [normalize_x(val), pos.y + sign_y * tick_length, pos.z],
390
- get_grid_lines: (val) => {
391
- const px = normalize_x(val);
392
- return [
393
- [[px, -half_z, pos.z], [px, half_z, pos.z]],
394
- [[px, pos.y, -half_y], [px, pos.y, half_y]],
395
- ];
396
- },
397
- tick_label_pos: (val) => [normalize_x(val), pos.y + sign_y * 0.4, pos.z],
398
- axis_label_pos: [0, pos.y + sign_y * 0.9, pos.z],
554
+ key: `x` as AxisKey,
555
+ color: `#ef4444`,
556
+ axis: x_axis,
557
+ ticks: x_ticks,
558
+ range: x_range,
559
+ get_tick_pos: (val: number): Vec3 => [normalize_x(val), pos.y, pos.z],
560
+ get_tick_end: (
561
+ val: number,
562
+ ): Vec3 => [normalize_x(val), pos.y + sign_y * tick_length, pos.z],
563
+ get_grid_lines: (val: number): [Vec3, Vec3][] => {
564
+ const px = normalize_x(val)
565
+ return [
566
+ [[px, -half_z, pos.z], [px, half_z, pos.z]],
567
+ [[px, pos.y, -half_y], [px, pos.y, half_y]],
568
+ ]
569
+ },
570
+ tick_label_pos: (
571
+ val: number,
572
+ ): Vec3 => [normalize_x(val), pos.y + sign_y * 0.4, pos.z],
573
+ axis_label_pos: [0, pos.y + sign_y * 0.9, pos.z] as Vec3,
399
574
  },
400
575
  {
401
- key: `y`,
402
- color: `#22c55e`,
403
- axis: y_axis,
404
- ticks: y_ticks,
405
- range: y_range,
406
- get_tick_pos: (val) => [pos.x, pos.y, normalize_y(val)],
407
- get_tick_end: (val) => [pos.x, pos.y + sign_y * tick_length, normalize_y(val)],
408
- get_grid_lines: (val) => {
409
- const py = normalize_y(val);
410
- return [
411
- [[-half_x, pos.y, py], [half_x, pos.y, py]],
412
- [[pos.x, -half_z, py], [pos.x, half_z, py]],
413
- ];
414
- },
415
- tick_label_pos: (val) => [pos.x + sign_x * 0.5, pos.y + sign_y * 0.4, normalize_y(val)],
416
- axis_label_pos: [
417
- pos.x,
418
- pos.y + sign_y * 0.9,
419
- pos.z < 0 ? half_y + 0.5 : -half_y - 0.5,
420
- ],
576
+ key: `y` as AxisKey,
577
+ color: `#22c55e`,
578
+ axis: y_axis,
579
+ ticks: y_ticks,
580
+ range: y_range,
581
+ get_tick_pos: (val: number): Vec3 => [pos.x, pos.y, normalize_y(val)],
582
+ get_tick_end: (
583
+ val: number,
584
+ ): Vec3 => [pos.x, pos.y + sign_y * tick_length, normalize_y(val)],
585
+ get_grid_lines: (val: number): [Vec3, Vec3][] => {
586
+ const py = normalize_y(val)
587
+ return [
588
+ [[-half_x, pos.y, py], [half_x, pos.y, py]],
589
+ [[pos.x, -half_z, py], [pos.x, half_z, py]],
590
+ ]
591
+ },
592
+ tick_label_pos: (
593
+ val: number,
594
+ ): Vec3 => [pos.x + sign_x * 0.5, pos.y + sign_y * 0.4, normalize_y(val)],
595
+ axis_label_pos: [
596
+ pos.x,
597
+ pos.y + sign_y * 0.9,
598
+ pos.z < 0 ? half_y + 0.5 : -half_y - 0.5,
599
+ ] as Vec3,
421
600
  },
422
601
  {
423
- key: `z`,
424
- color: `#3b82f6`,
425
- axis: z_axis,
426
- ticks: z_ticks,
427
- range: z_range,
428
- get_tick_pos: (val) => [pos.x, normalize_z(val), pos.z],
429
- get_tick_end: (val) => [pos.x + sign_x * tick_length, normalize_z(val), pos.z],
430
- get_grid_lines: (val) => {
431
- const pz = normalize_z(val);
432
- return [
433
- [[-half_x, pz, pos.z], [half_x, pz, pos.z]],
434
- [[pos.x, pz, -half_y], [pos.x, pz, half_y]],
435
- ];
436
- },
437
- tick_label_pos: (val) => [pos.x + sign_x * 0.5, normalize_z(val), pos.z],
438
- axis_label_pos: [pos.x + sign_x, 0, pos.z],
602
+ key: `z` as AxisKey,
603
+ color: `#3b82f6`,
604
+ axis: z_axis,
605
+ ticks: z_ticks,
606
+ range: z_range,
607
+ get_tick_pos: (val: number): Vec3 => [pos.x, normalize_z(val), pos.z],
608
+ get_tick_end: (
609
+ val: number,
610
+ ): Vec3 => [pos.x + sign_x * tick_length, normalize_z(val), pos.z],
611
+ get_grid_lines: (val: number): [Vec3, Vec3][] => {
612
+ const pz = normalize_z(val)
613
+ return [
614
+ [[-half_x, pz, pos.z], [half_x, pz, pos.z]],
615
+ [[pos.x, pz, -half_y], [pos.x, pz, half_y]],
616
+ ]
617
+ },
618
+ tick_label_pos: (
619
+ val: number,
620
+ ): Vec3 => [pos.x + sign_x * 0.5, normalize_z(val), pos.z],
621
+ axis_label_pos: [pos.x + sign_x, 0, pos.z] as Vec3,
439
622
  },
440
- ]);
441
- const empty_geom_data = () => ({ tick_geoms: [], grid_geoms: [] });
442
- let axis_geom_data = $state({
623
+ ])
624
+
625
+ // Pre-computed geometries for tick marks and grid lines, indexed by axis and tick position
626
+ type AxisGeomData = {
627
+ tick_geoms: THREE.BufferGeometry[]
628
+ grid_geoms: THREE.BufferGeometry[][]
629
+ }
630
+ const empty_geom_data = (): AxisGeomData => ({ tick_geoms: [], grid_geoms: [] })
631
+ let axis_geom_data: Record<AxisKey, AxisGeomData> = $state({
443
632
  x: empty_geom_data(),
444
633
  y: empty_geom_data(),
445
634
  z: empty_geom_data(),
446
- });
447
- // Recreate tick/grid geometries when axes config changes
448
- $effect(() => {
449
- const config = axes_config;
635
+ })
636
+
637
+ // Recreate tick/grid geometries when axes config changes
638
+ $effect(() => {
639
+ const config = axes_config
450
640
  // Dispose old geometries (untracked to avoid dependency cycle)
451
641
  untrack(() => {
452
- for (const key of AXIS_KEYS) {
453
- axis_geom_data[key].tick_geoms.forEach((geom) => geom.dispose());
454
- axis_geom_data[key].grid_geoms.flat().forEach((geom) => geom.dispose());
455
- }
456
- });
642
+ for (const key of AXIS_KEYS) {
643
+ axis_geom_data[key].tick_geoms.forEach((geom) => geom.dispose())
644
+ axis_geom_data[key].grid_geoms.flat().forEach((geom) => geom.dispose())
645
+ }
646
+ })
457
647
  for (const { key, ticks, get_tick_pos, get_tick_end, get_grid_lines } of config) {
458
- axis_geom_data[key] = {
459
- tick_geoms: ticks.map((v) => create_line_geometry(get_tick_pos(v), get_tick_end(v))),
460
- grid_geoms: ticks.map((v) => get_grid_lines(v).map(([s, e]) => create_line_geometry(s, e))),
461
- };
648
+ axis_geom_data[key] = {
649
+ tick_geoms: ticks.map((val) =>
650
+ create_line_geometry(get_tick_pos(val), get_tick_end(val))
651
+ ),
652
+ grid_geoms: ticks.map((val) =>
653
+ get_grid_lines(val).map(([start, end]) => create_line_geometry(start, end))
654
+ ),
655
+ }
462
656
  }
463
- });
657
+ })
464
658
  </script>
465
659
 
466
660
  {#if camera_projection === `perspective`}
@@ -599,7 +793,11 @@ $effect(() => {
599
793
 
600
794
  <!-- Instanced scatter points with per-instance colors and event handling -->
601
795
  {#each radius_groups as group (group.radius)}
602
- <extras.InstancedMesh range={group.points.length} frustumCulled={false}>
796
+ <extras.InstancedMesh
797
+ limit={group.points.length}
798
+ range={group.points.length}
799
+ frustumCulled={false}
800
+ >
603
801
  <T.SphereGeometry args={[1, sphere_segments, sphere_segments]} />
604
802
  <T.MeshStandardMaterial vertexColors={false} />
605
803
  {#each group.points as point, idx (`${point.series_idx}-${point.point_idx}`)}
@@ -621,7 +819,11 @@ $effect(() => {
621
819
  <!-- Plane Projections - render point shadows on enabled background planes -->
622
820
  {#each projection_configs as { key, get_pos } (key)}
623
821
  {#each radius_groups as group (group.radius)}
624
- <extras.InstancedMesh range={group.points.length} frustumCulled={false}>
822
+ <extras.InstancedMesh
823
+ limit={group.points.length}
824
+ range={group.points.length}
825
+ frustumCulled={false}
826
+ >
625
827
  <T.SphereGeometry args={[1, 8, 8]} />
626
828
  <T.MeshBasicMaterial transparent opacity={proj_opacity} depthWrite={false} />
627
829
  {#each group.points as point, idx (`${key}-${point.series_idx}-${point.point_idx}`)}