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
@@ -13,6 +13,8 @@ import { ProjectionPairsControls } from './ProjectionPairsControls.tsx';
13
13
  import { ProjectionPairsMore } from './ProjectionPairsMore.tsx';
14
14
  import { ProjectionSharesControls } from './ProjectionSharesControls.tsx';
15
15
  import { ProjectionSharesMore } from './ProjectionSharesMore.tsx';
16
+ import { ProjectionSpaceControls } from './ProjectionSpaceControls.tsx';
17
+ import { ProjectionSpaceMore } from './ProjectionSpaceMore.tsx';
16
18
  import { ProjectionVariablesControls } from './ProjectionVariablesControls.tsx';
17
19
  import { ProjectionVariablesMore } from './ProjectionVariablesMore.tsx';
18
20
  import type { ProjectionReading } from './projectionBarReadings.ts';
@@ -96,6 +98,23 @@ export function projectionBarSlots(
96
98
  info: projectionPairsInfo(copy, groups.label, colored),
97
99
  };
98
100
  }
101
+ if (tab === 'space') {
102
+ const space = {
103
+ ...shared,
104
+ result,
105
+ hasGroups,
106
+ selectedCount: selected.length,
107
+ onResetView: map.resetSpaceView,
108
+ onClearSelection: map.clearSelection,
109
+ };
110
+ return {
111
+ end: (
112
+ <ProjectionSpaceControls {...space} readings={readings} tier={tier} />
113
+ ),
114
+ more: <ProjectionSpaceMore {...space} />,
115
+ info: map.spaceChrome.caption,
116
+ };
117
+ }
99
118
  if (tab === 'variables') {
100
119
  const variables = {
101
120
  options,
@@ -0,0 +1,23 @@
1
+ /**
2
+ * What the cloud's own settings offer.
3
+ *
4
+ * One list, read by the value menu on the bar and by the segmented control in
5
+ * the panel behind the cog, so the two can never offer different words for the
6
+ * same thing.
7
+ */
8
+
9
+ import type { OverlayOption } from '../../overlay/ui/OverlayRow.tsx';
10
+ import type { CloudGesture } from '../../scatter3d/core/cloudGesture.ts';
11
+
12
+ /**
13
+ * What a plain drag does.
14
+ *
15
+ * Written as the action rather than as the name of a mode: `Turn` and `Select`
16
+ * are what the reader's hand is about to do, where `Orbit` would be a state
17
+ * they have to translate first.
18
+ */
19
+ export const CLOUD_GESTURE_CHOICES: ReadonlyArray<OverlayOption<CloudGesture>> =
20
+ [
21
+ { value: 'turn', label: 'Turn' },
22
+ { value: 'select', label: 'Select' },
23
+ ];
@@ -0,0 +1,78 @@
1
+ /**
2
+ * How large a group's shell is, in the words the cloud writes.
3
+ *
4
+ * It is the flat map's `projectionEllipse` with one dimension more, and the
5
+ * two cannot share an implementation for the one reason worth having a second
6
+ * module for: the same share is a different distance in three dimensions than
7
+ * in two. A shell drawn where the map's 95 % outline is drawn holds about 74 %
8
+ * of its group, so a cloud quoting the map's number promises a fifth of the
9
+ * samples it does not contain.
10
+ */
11
+
12
+ import type { OverlayOption } from '../../overlay/ui/OverlayRow.tsx';
13
+ import type { EllipseSize } from '../../scatter/core/confidenceEllipse.ts';
14
+ import { coverageForStandardDeviations3 } from '../../scatter3d/core/ellipsoidCoverage.ts';
15
+
16
+ import { ellipseKey } from './projectionEllipse.ts';
17
+
18
+ const WHOLE_SHARE = 100;
19
+
20
+ /**
21
+ * How much of a group its shell holds, written the way a sentence wants it.
22
+ * @param size - How large the shells are, or `null` for none.
23
+ * @returns The share, e.g. `95%`; the empty string when nothing is shelled.
24
+ */
25
+ export function ellipsoidCoverageText(size: EllipseSize | null): string {
26
+ if (size === null) return '';
27
+ const share =
28
+ size.kind === 'coverage'
29
+ ? size.probability
30
+ : coverageForStandardDeviations3(size.standardDeviations);
31
+ return `${Math.round(share * WHOLE_SHARE)}%`;
32
+ }
33
+
34
+ /**
35
+ * What one choice of the cloud's shell picker reads.
36
+ * @param size - The size that choice stands for, or `null` for no shells.
37
+ * @returns The label, e.g. `95% of samples`, or `2 SD (about 74%)`.
38
+ */
39
+ export function ellipsoidSizeLabel(size: EllipseSize | null): string {
40
+ if (size === null) return NO_SHELLS_LABEL;
41
+ const share = ellipsoidCoverageText(size);
42
+ if (size.kind === 'coverage') return `${share} of samples`;
43
+ return `${size.standardDeviations} SD (about ${share})`;
44
+ }
45
+
46
+ /**
47
+ * The sizes the cloud's picker offers, with the current one kept in the list.
48
+ *
49
+ * The same shares the map offers, because a share means the same thing in both
50
+ * pictures — it is the distance it comes out at that differs, and that is the
51
+ * geometry's business rather than the reader's. A size given in standard
52
+ * deviations is the one place the difference shows, and it is written here
53
+ * with the share a shell at that distance actually holds: two of them cover
54
+ * 86 % of a flat group and 74 % of a solid one.
55
+ * @param current - The size the cloud is drawn at, or `null` for no shells.
56
+ * @returns The choices, in the order they are offered.
57
+ */
58
+ export function ellipsoidChoices(current: EllipseSize | null): OverlayOption[] {
59
+ const choices: OverlayOption[] = [
60
+ { value: ellipseKey(null), label: ellipsoidSizeLabel(null) },
61
+ ];
62
+ for (const probability of SHELL_SHARES) {
63
+ const size: EllipseSize = { kind: 'coverage', probability };
64
+ choices.push({ value: ellipseKey(size), label: ellipsoidSizeLabel(size) });
65
+ }
66
+
67
+ const key = ellipseKey(current);
68
+ for (const choice of choices) {
69
+ if (choice.value === key) return choices;
70
+ }
71
+ choices.splice(1, 0, { value: key, label: ellipsoidSizeLabel(current) });
72
+ return choices;
73
+ }
74
+
75
+ /* The same four the map offers, so the two pictures are set the same way. */
76
+ const SHELL_SHARES: readonly number[] = [0.5, 0.9, 0.95, 0.99];
77
+
78
+ const NO_SHELLS_LABEL = 'No shells';
@@ -16,6 +16,7 @@ import type { ProjectionMarker } from '../core/projectionResult.ts';
16
16
  import type { ResolvedProjectionGroups } from '../core/projectionSamples.ts';
17
17
 
18
18
  import { ellipseCoverageText } from './projectionEllipse.ts';
19
+ import { ellipsoidCoverageText } from './projectionEllipsoid.ts';
19
20
  import { UNGROUPED_INK } from './projectionMapModel.ts';
20
21
 
21
22
  /** What the map's chrome is built from. */
@@ -38,6 +39,14 @@ export interface ProjectionMapChromeInput {
38
39
  fittedCount: number;
39
40
  /** How many rows there are in all. */
40
41
  total: number;
42
+ /**
43
+ * Which figure the chrome is for. The cloud says the same things about
44
+ * itself, in its own words and about its own geometry: a share is a wider
45
+ * shape in three dimensions than in two, so the sentence promising one has
46
+ * to be built from the dimension it will be read in.
47
+ * @default 'map'
48
+ */
49
+ figure?: 'map' | 'space';
41
50
  }
42
51
 
43
52
  /** The legend and the standing caption of one map. */
@@ -73,7 +82,7 @@ export function projectionMapChrome(
73
82
  input: ProjectionMapChromeInput,
74
83
  ): ProjectionMapChrome {
75
84
  const { copy, groups, colored, ellipse, showGroupMeans, markers } = input;
76
- const { minimumPoints, fittedCount, total } = input;
85
+ const { minimumPoints, fittedCount, total, figure = 'map' } = input;
77
86
  const entries: OverlayLegendEntry[] = [];
78
87
  const skipped: string[] = [];
79
88
 
@@ -114,7 +123,15 @@ export function projectionMapChrome(
114
123
  return {
115
124
  title: legendTitle(copy, groups, colored),
116
125
  entries,
117
- caption: captionFor(copy, groups, colored, ellipse, skipped, hollow),
126
+ caption: captionFor(
127
+ copy,
128
+ groups,
129
+ colored,
130
+ ellipse,
131
+ skipped,
132
+ hollow,
133
+ figure,
134
+ ),
118
135
  };
119
136
  }
120
137
 
@@ -169,15 +186,19 @@ function captionFor(
169
186
  ellipse: EllipseSize | null,
170
187
  skipped: readonly string[],
171
188
  hollow: boolean,
189
+ figure: 'map' | 'space',
172
190
  ): string {
173
- let sentence = copy.intro.map;
191
+ const solid = figure === 'space';
192
+ let sentence = solid ? copy.intro.space : copy.intro.map;
174
193
  // What an outline actually holds is the one claim the figure makes that the
175
194
  // reader cannot check by looking, so it is said in full here rather than
176
195
  // squeezed onto the key floating over the dots.
177
196
  if (colored && ellipse !== null) {
178
- sentence += ` ${fillCopy(copy.legend.map, {
197
+ sentence += ` ${fillCopy(solid ? copy.legend.space : copy.legend.map, {
179
198
  groups: groups.label.toLowerCase(),
180
- coverage: ellipseCoverageText(ellipse),
199
+ coverage: solid
200
+ ? ellipsoidCoverageText(ellipse)
201
+ : ellipseCoverageText(ellipse),
181
202
  })}`;
182
203
  }
183
204
  if (skipped.length > 0) {
@@ -14,6 +14,8 @@ import { useCallback, useMemo, useState } from 'react';
14
14
  import type { ChartViewport } from '../../chart/core/chartViewport.ts';
15
15
  import { chartClampViewport } from '../../chart/core/chartViewport.ts';
16
16
  import type { SelectionChange } from '../../scatter/ui/useScatterSelection.ts';
17
+ import type { OrbitCamera } from '../../scatter3d/core/orbitCamera.ts';
18
+ import { DEFAULT_ORBIT_CAMERA } from '../../scatter3d/core/orbitCamera.ts';
17
19
  import type { ProjectionCopy } from '../core/projectionCopy.ts';
18
20
  import type { ProjectionOptions } from '../core/projectionOptions.ts';
19
21
  import type {
@@ -33,6 +35,7 @@ import {
33
35
  projectionMapDomain,
34
36
  projectionMapViewport,
35
37
  } from './projectionMapModel.ts';
38
+ import { MINIMUM_SHELL_POINTS } from './projectionSpaceModel.ts';
36
39
 
37
40
  /** What {@link useProjectionMapView} needs. */
38
41
  export interface ProjectionMapViewInput {
@@ -54,6 +57,18 @@ export interface ProjectionMapViewInput {
54
57
  export interface ProjectionMapView {
55
58
  /** The key's title, the key's entries, and the paragraph behind the `?`. */
56
59
  chrome: ProjectionMapChrome;
60
+ /** The same three for the cloud, which promises a different share. */
61
+ spaceChrome: ProjectionMapChrome;
62
+ /** Where the reader is standing in the cloud. */
63
+ camera: OrbitCamera;
64
+ /** Turn the box. */
65
+ setCamera: (camera: OrbitCamera) => void;
66
+ /** How far in the cloud is zoomed. */
67
+ zoom: number;
68
+ /** Zoom the box. */
69
+ setZoom: (zoom: number) => void;
70
+ /** Put the box back where it was first seen, at the size it was first drawn. */
71
+ resetSpaceView: () => void;
57
72
  /** The frame the map is drawn in, or `null` when it is drawn around every sample. */
58
73
  viewport: ChartViewport | null;
59
74
  /** Draw the map in another frame, which is what the wheel does. */
@@ -84,6 +99,8 @@ export function useProjectionMapView(
84
99
 
85
100
  const [zoom, setZoom] = useState<AxisFrame | null>(null);
86
101
  const [report, setReport] = useState<SelectionReport | null>(null);
102
+ const [camera, setCamera] = useState<OrbitCamera>(DEFAULT_ORBIT_CAMERA);
103
+ const [cloudZoom, setCloudZoom] = useState(1);
87
104
 
88
105
  // A frame belongs to the pair of axes it was drawn on. Carrying it onto
89
106
  // another pair would show the reader a corner of a picture they never
@@ -117,6 +134,11 @@ export function useProjectionMapView(
117
134
  setViewport(null);
118
135
  }, [setViewport]);
119
136
 
137
+ const resetSpaceView = useCallback(() => {
138
+ setCamera(DEFAULT_ORBIT_CAMERA);
139
+ setCloudZoom(1);
140
+ }, []);
141
+
120
142
  const colored = colorBy === 'group' && groups.entries.length > 0;
121
143
  const chrome = useMemo(
122
144
  () =>
@@ -143,6 +165,23 @@ export function useProjectionMapView(
143
165
  ],
144
166
  );
145
167
 
168
+ const spaceChrome = useMemo(
169
+ () =>
170
+ projectionMapChrome({
171
+ copy,
172
+ groups,
173
+ colored,
174
+ ellipse: colored ? ellipse : null,
175
+ minimumPoints: MINIMUM_SHELL_POINTS,
176
+ showGroupMeans: false,
177
+ markers: NO_MARKERS,
178
+ fittedCount: fittedCount ?? scores.rows,
179
+ total: scores.rows,
180
+ figure: 'space',
181
+ }),
182
+ [copy, groups, colored, ellipse, fittedCount, scores],
183
+ );
184
+
146
185
  const settle = useCallback(
147
186
  (change: SelectionChange) => {
148
187
  setReport((previous) => ({
@@ -164,8 +203,14 @@ export function useProjectionMapView(
164
203
 
165
204
  return {
166
205
  chrome,
206
+ spaceChrome,
167
207
  viewport,
168
208
  setViewport,
209
+ camera,
210
+ setCamera,
211
+ zoom: cloudZoom,
212
+ setZoom: setCloudZoom,
213
+ resetSpaceView,
169
214
  report,
170
215
  settle,
171
216
  zoomToSelection,
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The one number the cloud sets differently from the map.
3
+ */
4
+
5
+ /**
6
+ * How many samples a group needs before it is given a shell.
7
+ *
8
+ * One more than the map asks for, and for the same reason the map asks for
9
+ * three: a shape fitted to the fewest points that can define it describes those
10
+ * points and not the group they came from. Three samples lie on a plane
11
+ * however they are arranged, so three is the number at which a shell is
12
+ * arithmetic rather than a finding.
13
+ */
14
+ export const MINIMUM_SHELL_POINTS = 4;
@@ -16,6 +16,25 @@ import type { ProjectionTab } from '../core/projectionTabs.ts';
16
16
  import type { ProjectionVariableTrack } from './ProjectionVariablesTab.tsx';
17
17
  import type { ProjectionSelection } from './projectionSelection.ts';
18
18
 
19
+ /** A sample the reader opened, and where they opened it. */
20
+ export interface ProjectionSampleOpen {
21
+ /** Its name, from `samples.ids` — the currency every other callback uses. */
22
+ id: string;
23
+ /** Its row in the score matrix, for a caller that would rather index. */
24
+ index: number;
25
+ /** Where the reader clicked, in pixels from the figure's left edge. */
26
+ x: number;
27
+ /** Where they clicked, in pixels from its top edge. */
28
+ y: number;
29
+ /**
30
+ * Where they clicked in the window, which is what an editor floating over
31
+ * the page is anchored to.
32
+ */
33
+ clientX: number;
34
+ /** Where they clicked in the window. */
35
+ clientY: number;
36
+ }
37
+
19
38
  /** What {@link ProjectionViewer} needs. */
20
39
  export interface ProjectionViewerProps {
21
40
  /** What the run produced, whatever produced it. */
@@ -88,6 +107,22 @@ export interface ProjectionViewerProps {
88
107
  * @default undefined
89
108
  */
90
109
  onHoverSample?: (id: string | null) => void;
110
+ /**
111
+ * Called when the reader double-clicks a sample, on the map or in the cloud.
112
+ *
113
+ * The viewer says what a sample *is*; this is the door to what the page
114
+ * around it can do with one — open its record, edit the class it was given,
115
+ * show the spectrum behind the dot. The viewer does nothing itself, so a
116
+ * page that does not pass it has a double click that only ever resets the
117
+ * frame.
118
+ *
119
+ * The sample is selected first, because a double click is also two clicks.
120
+ * Holding every single click for the double-click interval to avoid that
121
+ * would make the commonest gesture in the figure feel slow, which is a worse
122
+ * trade than a redundant selection on the rarest one.
123
+ * @default undefined
124
+ */
125
+ onSampleDoubleClick?: (sample: ProjectionSampleOpen) => void;
91
126
  /**
92
127
  * What the figure is showing. Present, the caller owns every option, which
93
128
  * is how a site persists them or writes them into a share link.
@@ -1,4 +1,4 @@
1
- import type { ReactElement } from 'react';
1
+ import type { MouseEvent as ReactMouseEvent, ReactElement } from 'react';
2
2
  import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
3
3
 
4
4
  import { ChartFrame } from '../../chart/ui/ChartFrame.tsx';
@@ -7,6 +7,7 @@ import { ScatterEllipseLayer } from './ScatterEllipseLayer.tsx';
7
7
  import { ScatterLabelLayer } from './ScatterLabelLayer.tsx';
8
8
  import { ScatterMarkLayer } from './ScatterMarkLayer.tsx';
9
9
  import { ScatterPointLayer } from './ScatterPointLayer.tsx';
10
+ import { surfacePosition } from './lassoGesture.ts';
10
11
  import {
11
12
  SCATTER_GROUP_LABEL_SIZE,
12
13
  SCATTER_LABEL_SIZE,
@@ -28,6 +29,7 @@ export type {
28
29
  ScatterGroup,
29
30
  ScatterMarker,
30
31
  ScatterPlotProps,
32
+ ScatterPointOpen,
31
33
  } from './scatterPlotProps.ts';
32
34
 
33
35
  /**
@@ -68,6 +70,7 @@ export function ScatterPlot(props: ScatterPlotProps): ReactElement {
68
70
  selectMode,
69
71
  onHoverChange,
70
72
  onPinChange,
73
+ onPointDoubleClick,
71
74
  onLassoChange,
72
75
  touchLasso,
73
76
  viewport,
@@ -151,6 +154,22 @@ export function ScatterPlot(props: ScatterPlotProps): ReactElement {
151
154
  onPinChange,
152
155
  });
153
156
 
157
+ function handleDoubleClick(event: ReactMouseEvent<SVGRectElement>): void {
158
+ const at = surfacePosition(event, rect.x, rect.y);
159
+ const index = interaction.pointAt(at.x, at.y);
160
+ if (index === -1 || onPointDoubleClick === undefined) {
161
+ reset();
162
+ return;
163
+ }
164
+ onPointDoubleClick({
165
+ index,
166
+ x: at.x,
167
+ y: at.y,
168
+ clientX: event.clientX,
169
+ clientY: event.clientY,
170
+ });
171
+ }
172
+
154
173
  // Held in a ref so an inline arrow, which is how every caller writes it, does
155
174
  // not put the callback in the effect's dependencies and report a drag that
156
175
  // never changed on every render.
@@ -224,7 +243,7 @@ export function ScatterPlot(props: ScatterPlotProps): ReactElement {
224
243
  fill="transparent"
225
244
  tabIndex={0}
226
245
  onKeyDown={interaction.keyboard.onKeyDown}
227
- onDoubleClick={reset}
246
+ onDoubleClick={handleDoubleClick}
228
247
  {...interaction.surface}
229
248
  />
230
249
  </>
@@ -163,6 +163,16 @@ export function modifierMode(
163
163
  return resting;
164
164
  }
165
165
 
166
+ /** The little of an event that says where on the surface it happened. */
167
+ export interface SurfaceEvent {
168
+ /** The element the handler is on, which is the rectangle being measured. */
169
+ currentTarget: Element;
170
+ /** Where the event happened, in the window. */
171
+ clientX: number;
172
+ /** Where the event happened, in the window. */
173
+ clientY: number;
174
+ }
175
+
166
176
  /**
167
177
  * Where the pointer is, in the space the points were measured in.
168
178
  *
@@ -170,13 +180,16 @@ export function modifierMode(
170
180
  * the plot's own corner, so the answer can be compared with a point coordinate
171
181
  * without the caller mapping anything. An element that cannot be measured —
172
182
  * a server render — reports the corner itself rather than throwing.
173
- * @param event - The pointer event.
183
+ *
184
+ * It takes the three fields it actually reads rather than a pointer event,
185
+ * because a double click arrives as a mouse event and asks the same question.
186
+ * @param event - The event, or anything carrying those three fields.
174
187
  * @param originX - The rectangle's left edge in the points' space.
175
188
  * @param originY - Its top edge.
176
189
  * @returns The position, in pixels.
177
190
  */
178
191
  export function surfacePosition(
179
- event: ReactPointerEvent<Element>,
192
+ event: SurfaceEvent,
180
193
  originX: number,
181
194
  originY: number,
182
195
  ): { x: number; y: number } {
@@ -45,6 +45,25 @@ export interface ScatterMarker {
45
45
  shape?: OverlayMarkShape;
46
46
  }
47
47
 
48
+ /** A point the reader opened, and where they opened it. */
49
+ export interface ScatterPointOpen {
50
+ /** Its row, as an index into the arrays the caller passed. */
51
+ index: number;
52
+ /** Where the reader clicked, in pixels from the figure's left edge. */
53
+ x: number;
54
+ /** Where they clicked, in pixels from its top edge. */
55
+ y: number;
56
+ /**
57
+ * Where they clicked in the window, which is what an editor floating over
58
+ * the page — a menu, a popover, a dialog anchored on the point — is placed
59
+ * from. The pair above is the figure's own space, for a caller drawing
60
+ * inside it.
61
+ */
62
+ clientX: number;
63
+ /** Where they clicked in the window. */
64
+ clientY: number;
65
+ }
66
+
48
67
  /** What {@link ScatterPlot} needs. */
49
68
  export interface ScatterPlotProps {
50
69
  /** Horizontal coordinate of every point, in data units. */
@@ -174,6 +193,22 @@ export interface ScatterPlotProps {
174
193
  * @default undefined
175
194
  */
176
195
  onPinChange?: (index: number) => void;
196
+ /**
197
+ * Called when the reader double-clicks a point: the gesture for "tell me
198
+ * more about this one", or "let me change it".
199
+ *
200
+ * A double click is also two clicks, so the point is selected first and its
201
+ * card pinned. That is deliberate: the alternative is to hold every single
202
+ * click for the length of the double-click interval before acting on it, and
203
+ * clicking a dot is the commonest gesture in the figure — making it feel
204
+ * slow to save a redundant selection on the rarest one is the wrong trade.
205
+ *
206
+ * A double click on empty ground is not this. It puts the frame back around
207
+ * every point, which is the only way out of a zoom, and it keeps doing that
208
+ * whether or not this is given.
209
+ * @default undefined — a double click only ever resets the frame
210
+ */
211
+ onPointDoubleClick?: (point: ScatterPointOpen) => void;
177
212
  /**
178
213
  * Called when a lasso starts being drawn and again when it ends, and never
179
214
  * in between.
@@ -97,6 +97,15 @@ export interface ScatterInteractionApi {
97
97
  keyboard: ScatterKeyboardApi;
98
98
  /** The outline being drawn, for the plot to stroke. */
99
99
  lasso: LassoGesture;
100
+ /**
101
+ * Which point is near a position, or `-1`.
102
+ *
103
+ * The same search a click runs, offered on its own so that a gesture the
104
+ * hook does not own — a double click, which the plot answers because only
105
+ * the plot knows what a double click on empty ground means — asks it in the
106
+ * same words and with the same radius.
107
+ */
108
+ pointAt: (x: number, y: number) => number;
100
109
  }
101
110
 
102
111
  /**
@@ -233,5 +242,17 @@ export function useScatterInteraction(
233
242
 
234
243
  const keyboard = useScatterKeyboard({ count, onCommit, onCancel });
235
244
 
236
- return { surface: lasso.surface, selection, hover, keyboard, lasso };
245
+ const pointAt = useCallback(
246
+ (x: number, y: number) => nearestPointIndex(points, x, y, radius, included),
247
+ [included, points, radius],
248
+ );
249
+
250
+ return {
251
+ surface: lasso.surface,
252
+ selection,
253
+ hover,
254
+ keyboard,
255
+ lasso,
256
+ pointAt,
257
+ };
237
258
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * What a drag over a cloud does.
3
+ *
4
+ * Two gestures want the same drag. Turning the box is the one a reader reaches
5
+ * for first and must stay a plain drag; drawing a lasso is the one the flat map
6
+ * has already taught them. Neither can be given a modifier without taking one
7
+ * that already means "add to the selection" or "take out of it", so the choice
8
+ * is a control the reader can see rather than a shortcut somebody has to tell
9
+ * them about — and inside a lasso the modifiers keep exactly the meaning they
10
+ * have on the map.
11
+ */
12
+ export type CloudGesture =
13
+ /** Turn the box. A tap still picks the sample under it. */
14
+ | 'turn'
15
+ /** Draw a lasso, exactly as on the map. */
16
+ | 'select';