react-cheminfo 0.10.0 → 0.11.0

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 (288) hide show
  1. package/lib/chrome/ui/NavLink.d.ts.map +1 -1
  2. package/lib/chrome/ui/NavLink.js +4 -3
  3. package/lib/chrome/ui/NavLink.js.map +1 -1
  4. package/lib/chrome/ui/SiteFooter.d.ts +8 -0
  5. package/lib/chrome/ui/SiteFooter.d.ts.map +1 -1
  6. package/lib/chrome/ui/SiteFooter.js +7 -2
  7. package/lib/chrome/ui/SiteFooter.js.map +1 -1
  8. package/lib/chrome/ui/SiteHeader.d.ts +11 -0
  9. package/lib/chrome/ui/SiteHeader.d.ts.map +1 -1
  10. package/lib/chrome/ui/SiteHeader.js +7 -2
  11. package/lib/chrome/ui/SiteHeader.js.map +1 -1
  12. package/lib/chrome/ui/navItem.d.ts +12 -0
  13. package/lib/chrome/ui/navItem.d.ts.map +1 -1
  14. package/lib/chrome/ui/navItem.js.map +1 -1
  15. package/lib/core.d.ts +1 -0
  16. package/lib/core.d.ts.map +1 -1
  17. package/lib/core.js +1 -0
  18. package/lib/core.js.map +1 -1
  19. package/lib/download/core/figureLegend.d.ts +90 -0
  20. package/lib/download/core/figureLegend.d.ts.map +1 -0
  21. package/lib/download/core/figureLegend.js +77 -0
  22. package/lib/download/core/figureLegend.js.map +1 -0
  23. package/lib/download/core/figureSvg.d.ts.map +1 -1
  24. package/lib/download/core/figureSvg.js +71 -1
  25. package/lib/download/core/figureSvg.js.map +1 -1
  26. package/lib/download/core/figureSvgDocument.d.ts +9 -2
  27. package/lib/download/core/figureSvgDocument.d.ts.map +1 -1
  28. package/lib/download/core/figureSvgDocument.js +4 -1
  29. package/lib/download/core/figureSvgDocument.js.map +1 -1
  30. package/lib/download/core/figureTarget.d.ts +11 -0
  31. package/lib/download/core/figureTarget.d.ts.map +1 -1
  32. package/lib/download/core/figureTarget.js +19 -0
  33. package/lib/download/core/figureTarget.js.map +1 -1
  34. package/lib/download/core/index.d.ts +3 -1
  35. package/lib/download/core/index.d.ts.map +1 -1
  36. package/lib/download/core/index.js +2 -1
  37. package/lib/download/core/index.js.map +1 -1
  38. package/lib/ecosystem/core/sites.d.ts +1 -1
  39. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  40. package/lib/ecosystem/core/sites.js +13 -0
  41. package/lib/ecosystem/core/sites.js.map +1 -1
  42. package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
  43. package/lib/ecosystem/ui/glyphs.js +5 -0
  44. package/lib/ecosystem/ui/glyphs.js.map +1 -1
  45. package/lib/overlay/ui/OverlayLegend.d.ts.map +1 -1
  46. package/lib/overlay/ui/OverlayLegend.js +1 -1
  47. package/lib/overlay/ui/OverlayLegend.js.map +1 -1
  48. package/lib/projection/core/projectionCopy.d.ts +4 -0
  49. package/lib/projection/core/projectionCopy.d.ts.map +1 -1
  50. package/lib/projection/core/projectionCopy.js +3 -0
  51. package/lib/projection/core/projectionCopy.js.map +1 -1
  52. package/lib/projection/core/projectionOptions.d.ts +12 -0
  53. package/lib/projection/core/projectionOptions.d.ts.map +1 -1
  54. package/lib/projection/core/projectionOptions.js +27 -1
  55. package/lib/projection/core/projectionOptions.js.map +1 -1
  56. package/lib/projection/core/projectionStrings.d.ts +4 -0
  57. package/lib/projection/core/projectionStrings.d.ts.map +1 -1
  58. package/lib/projection/core/projectionStrings.js +14 -0
  59. package/lib/projection/core/projectionStrings.js.map +1 -1
  60. package/lib/projection/core/projectionTabDefaults.d.ts.map +1 -1
  61. package/lib/projection/core/projectionTabDefaults.js +12 -0
  62. package/lib/projection/core/projectionTabDefaults.js.map +1 -1
  63. package/lib/projection/core/projectionTabs.d.ts +1 -1
  64. package/lib/projection/core/projectionTabs.d.ts.map +1 -1
  65. package/lib/projection/core/projectionTabs.js +9 -0
  66. package/lib/projection/core/projectionTabs.js.map +1 -1
  67. package/lib/projection/ui/ProjectionMapTab.d.ts +6 -0
  68. package/lib/projection/ui/ProjectionMapTab.d.ts.map +1 -1
  69. package/lib/projection/ui/ProjectionMapTab.js +2 -1
  70. package/lib/projection/ui/ProjectionMapTab.js.map +1 -1
  71. package/lib/projection/ui/ProjectionPanel.d.ts +6 -0
  72. package/lib/projection/ui/ProjectionPanel.d.ts.map +1 -1
  73. package/lib/projection/ui/ProjectionPanel.js +15 -1
  74. package/lib/projection/ui/ProjectionPanel.js.map +1 -1
  75. package/lib/projection/ui/ProjectionReadout.d.ts +7 -0
  76. package/lib/projection/ui/ProjectionReadout.d.ts.map +1 -1
  77. package/lib/projection/ui/ProjectionReadout.js +5 -4
  78. package/lib/projection/ui/ProjectionReadout.js.map +1 -1
  79. package/lib/projection/ui/ProjectionSpaceControls.d.ts +51 -0
  80. package/lib/projection/ui/ProjectionSpaceControls.d.ts.map +1 -0
  81. package/lib/projection/ui/ProjectionSpaceControls.js +71 -0
  82. package/lib/projection/ui/ProjectionSpaceControls.js.map +1 -0
  83. package/lib/projection/ui/ProjectionSpaceMore.d.ts +32 -0
  84. package/lib/projection/ui/ProjectionSpaceMore.d.ts.map +1 -0
  85. package/lib/projection/ui/ProjectionSpaceMore.js +57 -0
  86. package/lib/projection/ui/ProjectionSpaceMore.js.map +1 -0
  87. package/lib/projection/ui/ProjectionSpaceTab.d.ts +98 -0
  88. package/lib/projection/ui/ProjectionSpaceTab.d.ts.map +1 -0
  89. package/lib/projection/ui/ProjectionSpaceTab.js +66 -0
  90. package/lib/projection/ui/ProjectionSpaceTab.js.map +1 -0
  91. package/lib/projection/ui/ProjectionViewer.d.ts +1 -0
  92. package/lib/projection/ui/ProjectionViewer.d.ts.map +1 -1
  93. package/lib/projection/ui/ProjectionViewer.js +2 -1
  94. package/lib/projection/ui/ProjectionViewer.js.map +1 -1
  95. package/lib/projection/ui/index.d.ts +1 -0
  96. package/lib/projection/ui/index.d.ts.map +1 -1
  97. package/lib/projection/ui/index.js.map +1 -1
  98. package/lib/projection/ui/projectionBarModel.d.ts.map +1 -1
  99. package/lib/projection/ui/projectionBarModel.js +3 -2
  100. package/lib/projection/ui/projectionBarModel.js.map +1 -1
  101. package/lib/projection/ui/projectionBarReadings.d.ts.map +1 -1
  102. package/lib/projection/ui/projectionBarReadings.js +17 -0
  103. package/lib/projection/ui/projectionBarReadings.js.map +1 -1
  104. package/lib/projection/ui/projectionBarSlots.d.ts.map +1 -1
  105. package/lib/projection/ui/projectionBarSlots.js +17 -0
  106. package/lib/projection/ui/projectionBarSlots.js.map +1 -1
  107. package/lib/projection/ui/projectionCloudChoices.d.ts +18 -0
  108. package/lib/projection/ui/projectionCloudChoices.d.ts.map +1 -0
  109. package/lib/projection/ui/projectionCloudChoices.js +19 -0
  110. package/lib/projection/ui/projectionCloudChoices.js.map +1 -0
  111. package/lib/projection/ui/projectionEllipsoid.d.ts +38 -0
  112. package/lib/projection/ui/projectionEllipsoid.d.ts.map +1 -0
  113. package/lib/projection/ui/projectionEllipsoid.js +71 -0
  114. package/lib/projection/ui/projectionEllipsoid.js.map +1 -0
  115. package/lib/projection/ui/projectionMapChrome.d.ts +8 -0
  116. package/lib/projection/ui/projectionMapChrome.d.ts.map +1 -1
  117. package/lib/projection/ui/projectionMapChrome.js +10 -6
  118. package/lib/projection/ui/projectionMapChrome.js.map +1 -1
  119. package/lib/projection/ui/projectionMapView.d.ts +13 -0
  120. package/lib/projection/ui/projectionMapView.d.ts.map +1 -1
  121. package/lib/projection/ui/projectionMapView.js +26 -0
  122. package/lib/projection/ui/projectionMapView.js.map +1 -1
  123. package/lib/projection/ui/projectionSpaceModel.d.ts +14 -0
  124. package/lib/projection/ui/projectionSpaceModel.d.ts.map +1 -0
  125. package/lib/projection/ui/projectionSpaceModel.js +14 -0
  126. package/lib/projection/ui/projectionSpaceModel.js.map +1 -0
  127. package/lib/projection/ui/projectionViewerProps.d.ts +34 -0
  128. package/lib/projection/ui/projectionViewerProps.d.ts.map +1 -1
  129. package/lib/scatter/ui/ScatterPlot.d.ts +1 -1
  130. package/lib/scatter/ui/ScatterPlot.d.ts.map +1 -1
  131. package/lib/scatter/ui/ScatterPlot.js +18 -2
  132. package/lib/scatter/ui/ScatterPlot.js.map +1 -1
  133. package/lib/scatter/ui/lassoGesture.d.ts +14 -2
  134. package/lib/scatter/ui/lassoGesture.d.ts.map +1 -1
  135. package/lib/scatter/ui/lassoGesture.js +4 -1
  136. package/lib/scatter/ui/lassoGesture.js.map +1 -1
  137. package/lib/scatter/ui/scatterPlotProps.d.ts +34 -0
  138. package/lib/scatter/ui/scatterPlotProps.d.ts.map +1 -1
  139. package/lib/scatter/ui/useScatterInteraction.d.ts +9 -0
  140. package/lib/scatter/ui/useScatterInteraction.d.ts.map +1 -1
  141. package/lib/scatter/ui/useScatterInteraction.js +9 -1
  142. package/lib/scatter/ui/useScatterInteraction.js.map +1 -1
  143. package/lib/scatter3d/core/cloudGesture.d.ts +17 -0
  144. package/lib/scatter3d/core/cloudGesture.d.ts.map +1 -0
  145. package/lib/scatter3d/core/cloudGesture.js +2 -0
  146. package/lib/scatter3d/core/cloudGesture.js.map +1 -0
  147. package/lib/scatter3d/core/confidenceEllipsoid.d.ts +63 -0
  148. package/lib/scatter3d/core/confidenceEllipsoid.d.ts.map +1 -0
  149. package/lib/scatter3d/core/confidenceEllipsoid.js +128 -0
  150. package/lib/scatter3d/core/confidenceEllipsoid.js.map +1 -0
  151. package/lib/scatter3d/core/cubeFrame.d.ts +40 -0
  152. package/lib/scatter3d/core/cubeFrame.d.ts.map +1 -0
  153. package/lib/scatter3d/core/cubeFrame.js +58 -0
  154. package/lib/scatter3d/core/cubeFrame.js.map +1 -0
  155. package/lib/scatter3d/core/cubePoints.d.ts +35 -0
  156. package/lib/scatter3d/core/cubePoints.d.ts.map +1 -0
  157. package/lib/scatter3d/core/cubePoints.js +50 -0
  158. package/lib/scatter3d/core/cubePoints.js.map +1 -0
  159. package/lib/scatter3d/core/ellipsoidCoverage.d.ts +32 -0
  160. package/lib/scatter3d/core/ellipsoidCoverage.d.ts.map +1 -0
  161. package/lib/scatter3d/core/ellipsoidCoverage.js +83 -0
  162. package/lib/scatter3d/core/ellipsoidCoverage.js.map +1 -0
  163. package/lib/scatter3d/core/ellipsoidSilhouette.d.ts +42 -0
  164. package/lib/scatter3d/core/ellipsoidSilhouette.d.ts.map +1 -0
  165. package/lib/scatter3d/core/ellipsoidSilhouette.js +52 -0
  166. package/lib/scatter3d/core/ellipsoidSilhouette.js.map +1 -0
  167. package/lib/scatter3d/core/index.d.ts +29 -0
  168. package/lib/scatter3d/core/index.d.ts.map +1 -0
  169. package/lib/scatter3d/core/index.js +22 -0
  170. package/lib/scatter3d/core/index.js.map +1 -0
  171. package/lib/scatter3d/core/orbitCamera.d.ts +113 -0
  172. package/lib/scatter3d/core/orbitCamera.d.ts.map +1 -0
  173. package/lib/scatter3d/core/orbitCamera.js +104 -0
  174. package/lib/scatter3d/core/orbitCamera.js.map +1 -0
  175. package/lib/scatter3d/core/symmetricEigen.d.ts +47 -0
  176. package/lib/scatter3d/core/symmetricEigen.d.ts.map +1 -0
  177. package/lib/scatter3d/core/symmetricEigen.js +219 -0
  178. package/lib/scatter3d/core/symmetricEigen.js.map +1 -0
  179. package/lib/scatter3d/ui/CloudFrameLayer.d.ts +33 -0
  180. package/lib/scatter3d/ui/CloudFrameLayer.d.ts.map +1 -0
  181. package/lib/scatter3d/ui/CloudFrameLayer.js +37 -0
  182. package/lib/scatter3d/ui/CloudFrameLayer.js.map +1 -0
  183. package/lib/scatter3d/ui/CloudPointLayer.d.ts +62 -0
  184. package/lib/scatter3d/ui/CloudPointLayer.d.ts.map +1 -0
  185. package/lib/scatter3d/ui/CloudPointLayer.js +67 -0
  186. package/lib/scatter3d/ui/CloudPointLayer.js.map +1 -0
  187. package/lib/scatter3d/ui/CloudShellLayer.d.ts +52 -0
  188. package/lib/scatter3d/ui/CloudShellLayer.d.ts.map +1 -0
  189. package/lib/scatter3d/ui/CloudShellLayer.js +40 -0
  190. package/lib/scatter3d/ui/CloudShellLayer.js.map +1 -0
  191. package/lib/scatter3d/ui/ScatterCloud.d.ts +24 -0
  192. package/lib/scatter3d/ui/ScatterCloud.d.ts.map +1 -0
  193. package/lib/scatter3d/ui/ScatterCloud.js +189 -0
  194. package/lib/scatter3d/ui/ScatterCloud.js.map +1 -0
  195. package/lib/scatter3d/ui/cloudGeometry.d.ts +108 -0
  196. package/lib/scatter3d/ui/cloudGeometry.d.ts.map +1 -0
  197. package/lib/scatter3d/ui/cloudGeometry.js +122 -0
  198. package/lib/scatter3d/ui/cloudGeometry.js.map +1 -0
  199. package/lib/scatter3d/ui/cloudShellStyles.d.ts +36 -0
  200. package/lib/scatter3d/ui/cloudShellStyles.d.ts.map +1 -0
  201. package/lib/scatter3d/ui/cloudShellStyles.js +62 -0
  202. package/lib/scatter3d/ui/cloudShellStyles.js.map +1 -0
  203. package/lib/scatter3d/ui/cloudZoom.d.ts +21 -0
  204. package/lib/scatter3d/ui/cloudZoom.d.ts.map +1 -0
  205. package/lib/scatter3d/ui/cloudZoom.js +22 -0
  206. package/lib/scatter3d/ui/cloudZoom.js.map +1 -0
  207. package/lib/scatter3d/ui/index.d.ts +29 -0
  208. package/lib/scatter3d/ui/index.d.ts.map +1 -0
  209. package/lib/scatter3d/ui/index.js +21 -0
  210. package/lib/scatter3d/ui/index.js.map +1 -0
  211. package/lib/scatter3d/ui/scatterCloudModel.d.ts +60 -0
  212. package/lib/scatter3d/ui/scatterCloudModel.d.ts.map +1 -0
  213. package/lib/scatter3d/ui/scatterCloudModel.js +66 -0
  214. package/lib/scatter3d/ui/scatterCloudModel.js.map +1 -0
  215. package/lib/scatter3d/ui/scatterCloudProps.d.ts +185 -0
  216. package/lib/scatter3d/ui/scatterCloudProps.d.ts.map +1 -0
  217. package/lib/scatter3d/ui/scatterCloudProps.js +9 -0
  218. package/lib/scatter3d/ui/scatterCloudProps.js.map +1 -0
  219. package/lib/scatter3d/ui/useOrbitDrag.d.ts +60 -0
  220. package/lib/scatter3d/ui/useOrbitDrag.d.ts.map +1 -0
  221. package/lib/scatter3d/ui/useOrbitDrag.js +129 -0
  222. package/lib/scatter3d/ui/useOrbitDrag.js.map +1 -0
  223. package/lib/ui.d.ts +1 -0
  224. package/lib/ui.d.ts.map +1 -1
  225. package/lib/ui.js +1 -0
  226. package/lib/ui.js.map +1 -1
  227. package/package.json +1 -1
  228. package/src/chrome/ui/NavLink.tsx +5 -3
  229. package/src/chrome/ui/SiteFooter.tsx +16 -1
  230. package/src/chrome/ui/SiteHeader.tsx +19 -1
  231. package/src/chrome/ui/navItem.ts +12 -0
  232. package/src/core.ts +1 -0
  233. package/src/download/core/figureLegend.ts +167 -0
  234. package/src/download/core/figureSvg.ts +81 -1
  235. package/src/download/core/figureSvgDocument.ts +13 -3
  236. package/src/download/core/figureTarget.ts +21 -0
  237. package/src/download/core/index.ts +8 -0
  238. package/src/ecosystem/core/sites.ts +14 -0
  239. package/src/ecosystem/ui/glyphs.tsx +26 -0
  240. package/src/overlay/ui/OverlayLegend.tsx +7 -1
  241. package/src/projection/core/projectionCopy.ts +9 -0
  242. package/src/projection/core/projectionOptions.ts +48 -3
  243. package/src/projection/core/projectionStrings.ts +16 -0
  244. package/src/projection/core/projectionTabDefaults.ts +12 -0
  245. package/src/projection/core/projectionTabs.ts +10 -1
  246. package/src/projection/ui/ProjectionMapTab.tsx +8 -0
  247. package/src/projection/ui/ProjectionPanel.tsx +63 -0
  248. package/src/projection/ui/ProjectionReadout.tsx +16 -6
  249. package/src/projection/ui/ProjectionSpaceControls.tsx +219 -0
  250. package/src/projection/ui/ProjectionSpaceMore.tsx +169 -0
  251. package/src/projection/ui/ProjectionSpaceTab.tsx +217 -0
  252. package/src/projection/ui/ProjectionViewer.tsx +3 -0
  253. package/src/projection/ui/index.ts +1 -0
  254. package/src/projection/ui/projectionBarModel.ts +3 -2
  255. package/src/projection/ui/projectionBarReadings.ts +18 -0
  256. package/src/projection/ui/projectionBarSlots.tsx +19 -0
  257. package/src/projection/ui/projectionCloudChoices.ts +23 -0
  258. package/src/projection/ui/projectionEllipsoid.ts +78 -0
  259. package/src/projection/ui/projectionMapChrome.ts +26 -5
  260. package/src/projection/ui/projectionMapView.ts +45 -0
  261. package/src/projection/ui/projectionSpaceModel.ts +14 -0
  262. package/src/projection/ui/projectionViewerProps.ts +35 -0
  263. package/src/scatter/ui/ScatterPlot.tsx +21 -2
  264. package/src/scatter/ui/lassoGesture.ts +15 -2
  265. package/src/scatter/ui/scatterPlotProps.ts +35 -0
  266. package/src/scatter/ui/useScatterInteraction.ts +22 -1
  267. package/src/scatter3d/core/cloudGesture.ts +16 -0
  268. package/src/scatter3d/core/confidenceEllipsoid.ts +177 -0
  269. package/src/scatter3d/core/cubeFrame.ts +72 -0
  270. package/src/scatter3d/core/cubePoints.ts +75 -0
  271. package/src/scatter3d/core/ellipsoidCoverage.ts +86 -0
  272. package/src/scatter3d/core/ellipsoidSilhouette.ts +76 -0
  273. package/src/scatter3d/core/index.ts +52 -0
  274. package/src/scatter3d/core/orbitCamera.ts +159 -0
  275. package/src/scatter3d/core/symmetricEigen.ts +273 -0
  276. package/src/scatter3d/ui/CloudFrameLayer.tsx +97 -0
  277. package/src/scatter3d/ui/CloudPointLayer.tsx +132 -0
  278. package/src/scatter3d/ui/CloudShellLayer.tsx +107 -0
  279. package/src/scatter3d/ui/ScatterCloud.tsx +312 -0
  280. package/src/scatter3d/ui/cloudGeometry.ts +200 -0
  281. package/src/scatter3d/ui/cloudShellStyles.ts +74 -0
  282. package/src/scatter3d/ui/cloudZoom.ts +22 -0
  283. package/src/scatter3d/ui/index.ts +41 -0
  284. package/src/scatter3d/ui/scatterCloudModel.ts +98 -0
  285. package/src/scatter3d/ui/scatterCloudProps.ts +191 -0
  286. package/src/scatter3d/ui/useOrbitDrag.ts +220 -0
  287. package/src/ui.ts +1 -0
  288. package/styles/chrome.css +29 -4
@@ -0,0 +1,177 @@
1
+ /**
2
+ * The shell that summarises where a group of samples sits in the cloud.
3
+ *
4
+ * It is the outline of `scatter/core` with one dimension more, and it is
5
+ * described the same way — a centre and the axes of the group's own spread,
6
+ * in the data's units — so that turning it into something an SVG can draw
7
+ * stays a separate step. Here that step matters more than it does on a map:
8
+ * a shell has no `<ellipse>` to fall back on and has to be tessellated,
9
+ * projected and painted back to front.
10
+ */
11
+
12
+ import type { EllipseSize } from '../../scatter/core/confidenceEllipse.ts';
13
+
14
+ import { standardDeviationsForCoverage3 } from './ellipsoidCoverage.ts';
15
+ import type { Vector3 } from './orbitCamera.ts';
16
+ import type { SymmetricMatrix3 } from './symmetricEigen.ts';
17
+ import { symmetricEigen3 } from './symmetricEigen.ts';
18
+
19
+ /** A group's shell, in the cloud's own units. */
20
+ export interface ConfidenceEllipsoid {
21
+ /** Centre of the shell, which is the group's average. */
22
+ center: Vector3;
23
+ /**
24
+ * Three orthogonal semi-axes, longest first, each vector as long as its own
25
+ * radius. A direction the group has no spread in is the zero vector, which
26
+ * is what flattens the shell rather than giving it a false thickness.
27
+ */
28
+ axes: readonly [Vector3, Vector3, Vector3];
29
+ /** How many finite points went into it. */
30
+ count: number;
31
+ }
32
+
33
+ /** How {@link confidenceEllipsoid} measures a group. */
34
+ export interface ConfidenceEllipsoidOptions {
35
+ /**
36
+ * How large to draw the shell. A share is read in three dimensions, so the
37
+ * same share gives a wider shell here than the same call gives on the map.
38
+ * @default { kind: 'coverage', probability: 0.95 }
39
+ */
40
+ size?: EllipseSize;
41
+ /**
42
+ * How many finite points a group needs before it is given a shell at all.
43
+ * Below four a group has no volume to measure — three points lie on a plane
44
+ * however they are arranged — so fewer than four is read as four.
45
+ * @default 4
46
+ */
47
+ minimumPoints?: number;
48
+ }
49
+
50
+ /**
51
+ * The shell that summarises a group of samples.
52
+ *
53
+ * Points that are not finite are skipped rather than poisoning the average,
54
+ * and the spread is measured with the `n - 1` divisor, so the shell estimates
55
+ * the group rather than describing the handful of samples that happen to have
56
+ * been measured — both exactly as the two-dimensional outline does.
57
+ * @param points - The group's samples, in the cloud's own units.
58
+ * @param options - See {@link ConfidenceEllipsoidOptions}.
59
+ * @returns The shell, or `null` when the group is too small to have a shape, when its coordinates are so large that squaring them overflows, or when the requested size is unbounded.
60
+ */
61
+ export function confidenceEllipsoid(
62
+ points: readonly Vector3[],
63
+ options: ConfidenceEllipsoidOptions = {},
64
+ ): ConfidenceEllipsoid | null {
65
+ const { size = DEFAULT_SIZE, minimumPoints = 4 } = options;
66
+ const floor = Math.max(4, minimumPoints);
67
+
68
+ let count = 0;
69
+ let sumX = 0;
70
+ let sumY = 0;
71
+ let sumZ = 0;
72
+ for (const point of points) {
73
+ if (!isFinitePoint(point)) continue;
74
+ count++;
75
+ sumX += point[0];
76
+ sumY += point[1];
77
+ sumZ += point[2];
78
+ }
79
+ if (count < floor) return null;
80
+
81
+ const center: Vector3 = [sumX / count, sumY / count, sumZ / count];
82
+ const covariance = spreadAbout(points, center, count);
83
+ if (covariance === null) return null;
84
+
85
+ const radius = ellipsoidStandardDeviations(size);
86
+ if (!Number.isFinite(radius)) return null;
87
+
88
+ const { values, vectors } = symmetricEigen3(covariance);
89
+ const axes: Vector3[] = [];
90
+ for (let i = 0; i < 3; i++) {
91
+ const length = radius * Math.sqrt(Math.max(0, values[i] as number));
92
+ if (!Number.isFinite(length)) return null;
93
+ const direction = vectors[i] as Vector3;
94
+ axes.push([
95
+ direction[0] * length,
96
+ direction[1] * length,
97
+ direction[2] * length,
98
+ ]);
99
+ }
100
+
101
+ return {
102
+ center,
103
+ axes: [axes[0] as Vector3, axes[1] as Vector3, axes[2] as Vector3],
104
+ count,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * How far out a size asks the shell to be drawn.
110
+ *
111
+ * Public because a legend has to be able to say what a share came to without
112
+ * building a shell to ask it.
113
+ * @param size - How large the shell was asked to be.
114
+ * @returns The number of standard deviations.
115
+ */
116
+ export function ellipsoidStandardDeviations(size: EllipseSize): number {
117
+ if (size.kind === 'standardDeviations') return size.standardDeviations;
118
+ return standardDeviationsForCoverage3(size.probability);
119
+ }
120
+
121
+ function spreadAbout(
122
+ points: readonly Vector3[],
123
+ center: Vector3,
124
+ count: number,
125
+ ): SymmetricMatrix3 | null {
126
+ let xx = 0;
127
+ let xy = 0;
128
+ let xz = 0;
129
+ let yy = 0;
130
+ let yz = 0;
131
+ let zz = 0;
132
+ for (const point of points) {
133
+ if (!isFinitePoint(point)) continue;
134
+ const dx = point[0] - center[0];
135
+ const dy = point[1] - center[1];
136
+ const dz = point[2] - center[2];
137
+ xx += dx * dx;
138
+ xy += dx * dy;
139
+ xz += dx * dz;
140
+ yy += dy * dy;
141
+ yz += dy * dz;
142
+ zz += dz * dz;
143
+ }
144
+ const divisor = count - 1;
145
+ const covariance: SymmetricMatrix3 = {
146
+ xx: xx / divisor,
147
+ xy: xy / divisor,
148
+ xz: xz / divisor,
149
+ yy: yy / divisor,
150
+ yz: yz / divisor,
151
+ zz: zz / divisor,
152
+ };
153
+ if (
154
+ !Number.isFinite(center[0]) ||
155
+ !Number.isFinite(center[1]) ||
156
+ !Number.isFinite(center[2]) ||
157
+ !Number.isFinite(covariance.xx) ||
158
+ !Number.isFinite(covariance.yy) ||
159
+ !Number.isFinite(covariance.zz) ||
160
+ !Number.isFinite(covariance.xy) ||
161
+ !Number.isFinite(covariance.xz) ||
162
+ !Number.isFinite(covariance.yz)
163
+ ) {
164
+ return null;
165
+ }
166
+ return covariance;
167
+ }
168
+
169
+ function isFinitePoint(point: Vector3): boolean {
170
+ return (
171
+ Number.isFinite(point[0]) &&
172
+ Number.isFinite(point[1]) &&
173
+ Number.isFinite(point[2])
174
+ );
175
+ }
176
+
177
+ const DEFAULT_SIZE: EllipseSize = { kind: 'coverage', probability: 0.95 };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The box the cloud sits in.
3
+ *
4
+ * A cloud of dots on an empty background has no orientation at all: turn it
5
+ * and nothing tells the reader it turned, and a dot near the top could be near
6
+ * or far. Twelve faint edges and three labelled arms fix that for the cost of
7
+ * fifteen lines, and they are the reason the projection can stay orthographic
8
+ * — the frame carries the depth that perspective would otherwise have to, and
9
+ * unlike perspective it distorts no distance.
10
+ */
11
+
12
+ import type { Vector3 } from './orbitCamera.ts';
13
+
14
+ /** The corner the three labelled arms are drawn from. */
15
+ export const CUBE_ORIGIN: Vector3 = [-1, -1, -1];
16
+
17
+ /**
18
+ * The twelve edges of the cube, each as its two corners.
19
+ *
20
+ * Every edge, not just the three at the back: which edges are hidden changes
21
+ * as the box turns, and drawing all twelve faintly reads as a wireframe, while
22
+ * drawing three reads as an axis system that keeps rearranging itself.
23
+ * @returns The edges. The order is stable but carries no meaning.
24
+ */
25
+ export function cubeEdges(): Array<[Vector3, Vector3]> {
26
+ const corners: Vector3[] = [];
27
+ for (const x of [-1, 1]) {
28
+ for (const y of [-1, 1]) {
29
+ for (const z of [-1, 1]) corners.push([x, y, z]);
30
+ }
31
+ }
32
+
33
+ const edges: Array<[Vector3, Vector3]> = [];
34
+ for (let i = 0; i < corners.length; i++) {
35
+ for (let j = i + 1; j < corners.length; j++) {
36
+ const a = corners[i] as Vector3;
37
+ const b = corners[j] as Vector3;
38
+ // Two corners are joined by an edge exactly when they differ in one
39
+ // coordinate; differing in two is a face diagonal and in three is the
40
+ // body diagonal, and drawing either would turn the frame into a mesh.
41
+ const differing =
42
+ (a[0] === b[0] ? 0 : 1) +
43
+ (a[1] === b[1] ? 0 : 1) +
44
+ (a[2] === b[2] ? 0 : 1);
45
+ if (differing === 1) edges.push([a, b]);
46
+ }
47
+ }
48
+ return edges;
49
+ }
50
+
51
+ /** One labelled arm of the frame. */
52
+ export interface CubeArm {
53
+ /** Which of the three axes it stands for. */
54
+ axis: 'x' | 'y' | 'z';
55
+ /** The far end of the arm. */
56
+ end: Vector3;
57
+ /** Where its name is written, just beyond the end. */
58
+ label: Vector3;
59
+ }
60
+
61
+ /**
62
+ * The three arms, from the origin corner along each axis.
63
+ *
64
+ * The label sits a little past the end of its arm so that it is outside the
65
+ * cloud whatever the box is turned to; at the end itself it would fall on the
66
+ * frame from about a third of the angles.
67
+ */
68
+ export const CUBE_ARMS: readonly CubeArm[] = [
69
+ { axis: 'x', end: [1, -1, -1], label: [1.18, -1, -1] },
70
+ { axis: 'y', end: [-1, 1, -1], label: [-1, 1.18, -1] },
71
+ { axis: 'z', end: [-1, -1, 1], label: [-1, -1, 1.18] },
72
+ ];
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Three runs of numbers, stretched into the cube the cloud is drawn in.
3
+ *
4
+ * A map draws its two axes at whatever scale each one needs, and the tick
5
+ * labels are the reader's record of that. A cloud has no such record: it is
6
+ * one solid seen from an angle, so three axes drawn at three scales would make
7
+ * a group lean in a direction that is an artefact of the scaling and not
8
+ * anything in the data. Every axis is therefore stretched to the same cube,
9
+ * the frame is labelled with which axis is which, and the numbers stay in the
10
+ * card the pointer raises.
11
+ *
12
+ * The shells are fitted in this cube for the same reason the map fits its
13
+ * outlines in pixels: a shell has to sit on the cloud the reader is looking
14
+ * at, not on a cloud in units nothing draws.
15
+ */
16
+
17
+ import {
18
+ chartPadExtent,
19
+ chartValuesExtent,
20
+ } from '../../chart/core/chartExtent.ts';
21
+
22
+ import type { Vector3 } from './orbitCamera.ts';
23
+
24
+ /** How {@link cubePoints} stretches the runs it is given. */
25
+ export interface CubePointsOptions {
26
+ /**
27
+ * Share of each axis' span left empty at both ends, so the cloud does not
28
+ * touch the frame.
29
+ * @default 0.05
30
+ */
31
+ padding?: number;
32
+ }
33
+
34
+ /**
35
+ * Three runs of numbers, each stretched to `[-1, 1]`.
36
+ * @param x - Where every sample sits along the axis that runs left to right.
37
+ * @param y - Along the axis that runs bottom to top, in the same order.
38
+ * @param z - Along the axis that runs away from the reader.
39
+ * @param options - See {@link CubePointsOptions}.
40
+ * @returns One point per sample, as many as the shortest run holds. An axis with no spread at all puts every sample at the middle of it rather than dividing by zero, and a value that is not finite stays that way so nothing draws it.
41
+ */
42
+ export function cubePoints(
43
+ x: ArrayLike<number>,
44
+ y: ArrayLike<number>,
45
+ z: ArrayLike<number>,
46
+ options: CubePointsOptions = {},
47
+ ): Vector3[] {
48
+ const { padding = 0.05 } = options;
49
+ const toCube = [x, y, z].map((values) => axisMapper(values, padding));
50
+ const across = toCube[0] as Mapper;
51
+ const up = toCube[1] as Mapper;
52
+ const away = toCube[2] as Mapper;
53
+
54
+ const count = Math.min(x.length, y.length, z.length);
55
+ const points = new Array<Vector3>(count);
56
+ for (let index = 0; index < count; index++) {
57
+ points[index] = [
58
+ across(x[index] as number),
59
+ up(y[index] as number),
60
+ away(z[index] as number),
61
+ ];
62
+ }
63
+ return points;
64
+ }
65
+
66
+ type Mapper = (value: number) => number;
67
+
68
+ function axisMapper(values: ArrayLike<number>, padding: number): Mapper {
69
+ const { min, max } = chartPadExtent(chartValuesExtent(values), padding);
70
+ const span = max - min;
71
+ if (!Number.isFinite(span) || span <= 0) {
72
+ return (value: number) => (Number.isFinite(value) ? 0 : Number.NaN);
73
+ }
74
+ return (value: number) => (2 * (value - min)) / span - 1;
75
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * How far out a group's shell is drawn, and how much of the group it then
3
+ * holds.
4
+ *
5
+ * The two-dimensional pair in `scatter/core` cannot answer this, and the
6
+ * difference is not a rounding: a shell has to hold a sample in three
7
+ * directions at once, so the same two standard deviations that keep 86 per
8
+ * cent of a flat cloud keep only 74 per cent of a solid one. Quoting the flat
9
+ * figure beside a cloud is the usual way a legend comes to promise more than
10
+ * its shells contain, which is why the map and the cloud each ask their own
11
+ * dimension.
12
+ */
13
+
14
+ /**
15
+ * The share of a three-dimensional normal cloud inside the shell drawn at a
16
+ * number of standard deviations.
17
+ * @param standardDeviations - How far out the shell is drawn.
18
+ * @returns The share, from 0 to 1. Zero at or below zero, since a shell of no size holds nothing.
19
+ */
20
+ export function coverageForStandardDeviations3(
21
+ standardDeviations: number,
22
+ ): number {
23
+ if (!Number.isFinite(standardDeviations)) {
24
+ return standardDeviations > 0 ? 1 : 0;
25
+ }
26
+ if (standardDeviations <= 0) return 0;
27
+ const radius = standardDeviations;
28
+ const inside = erf(radius / Math.SQRT2);
29
+ const shell =
30
+ radius * Math.sqrt(2 / Math.PI) * Math.exp(-(radius * radius) / 2);
31
+ return Math.min(1, Math.max(0, inside - shell));
32
+ }
33
+
34
+ /**
35
+ * How far out to draw the shell that holds a share of a three-dimensional
36
+ * normal cloud.
37
+ *
38
+ * The inverse of {@link coverageForStandardDeviations3}. There is no closed
39
+ * form for it, so it is bisected — the share grows strictly with the radius,
40
+ * so fifty halvings settle it far below the width of the line the shell is
41
+ * drawn with, and the search runs once per group rather than once per face.
42
+ * @param coverage - The share to enclose, from 0 to 1.
43
+ * @returns The number of standard deviations. Zero at or below zero, and infinite at or above one, because no finite shell holds every point of a cloud with unbounded tails.
44
+ */
45
+ export function standardDeviationsForCoverage3(coverage: number): number {
46
+ if (!Number.isFinite(coverage) || coverage <= 0) return 0;
47
+ if (coverage >= 1) return Number.POSITIVE_INFINITY;
48
+
49
+ let low = 0;
50
+ // Nine standard deviations hold every share a control can ask for: the
51
+ // remainder there is under 1e-17, which no `number` can tell from zero.
52
+ let high = 9;
53
+ for (let step = 0; step < 50; step++) {
54
+ const middle = (low + high) / 2;
55
+ if (coverageForStandardDeviations3(middle) < coverage) {
56
+ low = middle;
57
+ } else {
58
+ high = middle;
59
+ }
60
+ }
61
+ return (low + high) / 2;
62
+ }
63
+
64
+ /**
65
+ * The error function, to about a part in ten million.
66
+ *
67
+ * Abramowitz and Stegun 7.1.26. That is coarser than the library's usual
68
+ * standard and is deliberately so: it decides the radius of a shell drawn a
69
+ * few hundred pixels across, where a part in ten million is a part in ten
70
+ * thousand of one pixel, and the alternative is a series that costs more to
71
+ * read than the whole module around it.
72
+ * @param x - The argument.
73
+ * @returns The error function at `x`.
74
+ */
75
+ function erf(x: number): number {
76
+ const sign = x < 0 ? -1 : 1;
77
+ const absolute = Math.abs(x);
78
+ const t = 1 / (1 + 0.327_591_1 * absolute);
79
+ const series =
80
+ t *
81
+ (0.254_829_592 +
82
+ t *
83
+ (-0.284_496_736 +
84
+ t * (1.421_413_741 + t * (-1.453_152_027 + t * 1.061_405_429))));
85
+ return sign * (1 - series * Math.exp(-absolute * absolute));
86
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The outline a shell has on screen, which is an ellipse and nothing else.
3
+ *
4
+ * An orthographic camera sends the unit sphere to a filled ellipse, so a shell
5
+ * — a sphere carried by three semi-axes — has an exact elliptical silhouette
6
+ * whatever it is turned to. Taking it as such is what keeps the glass smooth:
7
+ * a tessellated shell is only as round as the number of faces it was cut into,
8
+ * and every seam between two of those faces is a line the reader can see.
9
+ */
10
+
11
+ import type { ConfidenceEllipsoid } from './confidenceEllipsoid.ts';
12
+ import type { OrbitCamera } from './orbitCamera.ts';
13
+ import { rotatePoint } from './orbitCamera.ts';
14
+
15
+ /** Where a shell's outline sits, in cube units, for one camera. */
16
+ export interface EllipsoidSilhouette {
17
+ /** Half the outline's longest width, along the angle below. */
18
+ rx: number;
19
+ /** Half its width across that, never longer than `rx`. */
20
+ ry: number;
21
+ /**
22
+ * How far the long axis is turned from the horizontal, in radians, measured
23
+ * the way the screen measures — down from the right — since the projection
24
+ * flips the vertical axis on its way to pixels.
25
+ */
26
+ angle: number;
27
+ }
28
+
29
+ /**
30
+ * The outline of a shell, for the camera it is seen from.
31
+ *
32
+ * The three semi-axes are turned with the camera and their depth dropped,
33
+ * which leaves a `2 x 3` map from the unit sphere to the screen. The image of
34
+ * that sphere is the ellipse whose shape matrix is the map times its own
35
+ * transpose, so the two radii are the square roots of that matrix's
36
+ * eigenvalues and the tilt is half the angle of its off-diagonal term.
37
+ *
38
+ * A group flat in one direction gives a matrix with a zero eigenvalue and
39
+ * comes back as a segment, which is the honest picture of a group that has no
40
+ * spread to show.
41
+ * @param ellipsoid - The shell, in cube units.
42
+ * @param camera - Where the reader is standing.
43
+ * @returns See {@link EllipsoidSilhouette}.
44
+ */
45
+ export function ellipsoidSilhouette(
46
+ ellipsoid: ConfidenceEllipsoid,
47
+ camera: OrbitCamera,
48
+ ): EllipsoidSilhouette {
49
+ const [first, second, third] = ellipsoid.axes;
50
+ const a = rotatePoint(first, camera);
51
+ const b = rotatePoint(second, camera);
52
+ const c = rotatePoint(third, camera);
53
+
54
+ // The screen's own frame: across, and down — hence the sign on the second.
55
+ const acrossX = a[0];
56
+ const acrossY = b[0];
57
+ const acrossZ = c[0];
58
+ const downX = -a[1];
59
+ const downY = -b[1];
60
+ const downZ = -c[1];
61
+
62
+ const xx = acrossX * acrossX + acrossY * acrossY + acrossZ * acrossZ;
63
+ const yy = downX * downX + downY * downY + downZ * downZ;
64
+ const xy = acrossX * downX + acrossY * downY + acrossZ * downZ;
65
+
66
+ const middle = (xx + yy) / 2;
67
+ const spread = Math.hypot((xx - yy) / 2, xy);
68
+ const major = Math.max(0, middle + spread);
69
+ const minor = Math.max(0, middle - spread);
70
+
71
+ return {
72
+ rx: Math.sqrt(major),
73
+ ry: Math.sqrt(minor),
74
+ angle: 0.5 * Math.atan2(2 * xy, xx - yy),
75
+ };
76
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * A cloud of samples in three dimensions: where the reader is standing, where
3
+ * a sample lands on screen from there, and the shell that holds a group.
4
+ *
5
+ * It is the two-dimensional `scatter` domain with one axis more, and the extra
6
+ * axis changes more than it looks. Nothing can be handed to an SVG shape any
7
+ * more — a shell has to be cut into faces, projected, and painted back to
8
+ * front — and the three axes have to share one scale, because a solid seen
9
+ * from an angle has no tick labels to record three different ones with.
10
+ *
11
+ * Nothing here knows what produced the coordinates, and nothing here draws:
12
+ * every function is arithmetic over plain numbers, so the geometry can be
13
+ * checked without a DOM.
14
+ */
15
+
16
+ export type {
17
+ ConfidenceEllipsoid,
18
+ ConfidenceEllipsoidOptions,
19
+ } from './confidenceEllipsoid.ts';
20
+ export {
21
+ confidenceEllipsoid,
22
+ ellipsoidStandardDeviations,
23
+ } from './confidenceEllipsoid.ts';
24
+ export type { CubeArm } from './cubeFrame.ts';
25
+ export { CUBE_ARMS, CUBE_ORIGIN, cubeEdges } from './cubeFrame.ts';
26
+ export {
27
+ coverageForStandardDeviations3,
28
+ standardDeviationsForCoverage3,
29
+ } from './ellipsoidCoverage.ts';
30
+ export type { EllipsoidSilhouette } from './ellipsoidSilhouette.ts';
31
+ export { ellipsoidSilhouette } from './ellipsoidSilhouette.ts';
32
+ export type {
33
+ OrbitCamera,
34
+ OrbitViewport,
35
+ ProjectedPoint,
36
+ Vector3,
37
+ } from './orbitCamera.ts';
38
+ export {
39
+ CUBE_HALF_DIAGONAL,
40
+ DEFAULT_ORBIT_CAMERA,
41
+ ORBIT_PITCH_LIMIT,
42
+ ORBIT_RADIANS_PER_PIXEL,
43
+ depthFraction,
44
+ orbitByDrag,
45
+ projectPoint,
46
+ rotatePoint,
47
+ } from './orbitCamera.ts';
48
+ export type { CloudGesture } from './cloudGesture.ts';
49
+ export type { CubePointsOptions } from './cubePoints.ts';
50
+ export { cubePoints } from './cubePoints.ts';
51
+ export type { SymmetricEigen3, SymmetricMatrix3 } from './symmetricEigen.ts';
52
+ export { symmetricEigen3 } from './symmetricEigen.ts';