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.
- package/lib/chrome/ui/NavLink.d.ts.map +1 -1
- package/lib/chrome/ui/NavLink.js +4 -3
- package/lib/chrome/ui/NavLink.js.map +1 -1
- package/lib/chrome/ui/SiteFooter.d.ts +8 -0
- package/lib/chrome/ui/SiteFooter.d.ts.map +1 -1
- package/lib/chrome/ui/SiteFooter.js +7 -2
- package/lib/chrome/ui/SiteFooter.js.map +1 -1
- package/lib/chrome/ui/SiteHeader.d.ts +11 -0
- package/lib/chrome/ui/SiteHeader.d.ts.map +1 -1
- package/lib/chrome/ui/SiteHeader.js +7 -2
- package/lib/chrome/ui/SiteHeader.js.map +1 -1
- package/lib/chrome/ui/navItem.d.ts +12 -0
- package/lib/chrome/ui/navItem.d.ts.map +1 -1
- package/lib/chrome/ui/navItem.js.map +1 -1
- package/lib/core.d.ts +1 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +1 -0
- package/lib/core.js.map +1 -1
- package/lib/download/core/figureLegend.d.ts +90 -0
- package/lib/download/core/figureLegend.d.ts.map +1 -0
- package/lib/download/core/figureLegend.js +77 -0
- package/lib/download/core/figureLegend.js.map +1 -0
- package/lib/download/core/figureSvg.d.ts.map +1 -1
- package/lib/download/core/figureSvg.js +71 -1
- package/lib/download/core/figureSvg.js.map +1 -1
- package/lib/download/core/figureSvgDocument.d.ts +9 -2
- package/lib/download/core/figureSvgDocument.d.ts.map +1 -1
- package/lib/download/core/figureSvgDocument.js +4 -1
- package/lib/download/core/figureSvgDocument.js.map +1 -1
- package/lib/download/core/figureTarget.d.ts +11 -0
- package/lib/download/core/figureTarget.d.ts.map +1 -1
- package/lib/download/core/figureTarget.js +19 -0
- package/lib/download/core/figureTarget.js.map +1 -1
- package/lib/download/core/index.d.ts +3 -1
- package/lib/download/core/index.d.ts.map +1 -1
- package/lib/download/core/index.js +2 -1
- package/lib/download/core/index.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +13 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +5 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/overlay/ui/OverlayLegend.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayLegend.js +1 -1
- package/lib/overlay/ui/OverlayLegend.js.map +1 -1
- package/lib/projection/core/projectionCopy.d.ts +4 -0
- package/lib/projection/core/projectionCopy.d.ts.map +1 -1
- package/lib/projection/core/projectionCopy.js +3 -0
- package/lib/projection/core/projectionCopy.js.map +1 -1
- package/lib/projection/core/projectionOptions.d.ts +12 -0
- package/lib/projection/core/projectionOptions.d.ts.map +1 -1
- package/lib/projection/core/projectionOptions.js +27 -1
- package/lib/projection/core/projectionOptions.js.map +1 -1
- package/lib/projection/core/projectionStrings.d.ts +4 -0
- package/lib/projection/core/projectionStrings.d.ts.map +1 -1
- package/lib/projection/core/projectionStrings.js +14 -0
- package/lib/projection/core/projectionStrings.js.map +1 -1
- package/lib/projection/core/projectionTabDefaults.d.ts.map +1 -1
- package/lib/projection/core/projectionTabDefaults.js +12 -0
- package/lib/projection/core/projectionTabDefaults.js.map +1 -1
- package/lib/projection/core/projectionTabs.d.ts +1 -1
- package/lib/projection/core/projectionTabs.d.ts.map +1 -1
- package/lib/projection/core/projectionTabs.js +9 -0
- package/lib/projection/core/projectionTabs.js.map +1 -1
- package/lib/projection/ui/ProjectionMapTab.d.ts +6 -0
- package/lib/projection/ui/ProjectionMapTab.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionMapTab.js +2 -1
- package/lib/projection/ui/ProjectionMapTab.js.map +1 -1
- package/lib/projection/ui/ProjectionPanel.d.ts +6 -0
- package/lib/projection/ui/ProjectionPanel.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionPanel.js +15 -1
- package/lib/projection/ui/ProjectionPanel.js.map +1 -1
- package/lib/projection/ui/ProjectionReadout.d.ts +7 -0
- package/lib/projection/ui/ProjectionReadout.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionReadout.js +5 -4
- package/lib/projection/ui/ProjectionReadout.js.map +1 -1
- package/lib/projection/ui/ProjectionSpaceControls.d.ts +51 -0
- package/lib/projection/ui/ProjectionSpaceControls.d.ts.map +1 -0
- package/lib/projection/ui/ProjectionSpaceControls.js +71 -0
- package/lib/projection/ui/ProjectionSpaceControls.js.map +1 -0
- package/lib/projection/ui/ProjectionSpaceMore.d.ts +32 -0
- package/lib/projection/ui/ProjectionSpaceMore.d.ts.map +1 -0
- package/lib/projection/ui/ProjectionSpaceMore.js +57 -0
- package/lib/projection/ui/ProjectionSpaceMore.js.map +1 -0
- package/lib/projection/ui/ProjectionSpaceTab.d.ts +98 -0
- package/lib/projection/ui/ProjectionSpaceTab.d.ts.map +1 -0
- package/lib/projection/ui/ProjectionSpaceTab.js +66 -0
- package/lib/projection/ui/ProjectionSpaceTab.js.map +1 -0
- package/lib/projection/ui/ProjectionViewer.d.ts +1 -0
- package/lib/projection/ui/ProjectionViewer.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionViewer.js +2 -1
- package/lib/projection/ui/ProjectionViewer.js.map +1 -1
- package/lib/projection/ui/index.d.ts +1 -0
- package/lib/projection/ui/index.d.ts.map +1 -1
- package/lib/projection/ui/index.js.map +1 -1
- package/lib/projection/ui/projectionBarModel.d.ts.map +1 -1
- package/lib/projection/ui/projectionBarModel.js +3 -2
- package/lib/projection/ui/projectionBarModel.js.map +1 -1
- package/lib/projection/ui/projectionBarReadings.d.ts.map +1 -1
- package/lib/projection/ui/projectionBarReadings.js +17 -0
- package/lib/projection/ui/projectionBarReadings.js.map +1 -1
- package/lib/projection/ui/projectionBarSlots.d.ts.map +1 -1
- package/lib/projection/ui/projectionBarSlots.js +17 -0
- package/lib/projection/ui/projectionBarSlots.js.map +1 -1
- package/lib/projection/ui/projectionCloudChoices.d.ts +18 -0
- package/lib/projection/ui/projectionCloudChoices.d.ts.map +1 -0
- package/lib/projection/ui/projectionCloudChoices.js +19 -0
- package/lib/projection/ui/projectionCloudChoices.js.map +1 -0
- package/lib/projection/ui/projectionEllipsoid.d.ts +38 -0
- package/lib/projection/ui/projectionEllipsoid.d.ts.map +1 -0
- package/lib/projection/ui/projectionEllipsoid.js +71 -0
- package/lib/projection/ui/projectionEllipsoid.js.map +1 -0
- package/lib/projection/ui/projectionMapChrome.d.ts +8 -0
- package/lib/projection/ui/projectionMapChrome.d.ts.map +1 -1
- package/lib/projection/ui/projectionMapChrome.js +10 -6
- package/lib/projection/ui/projectionMapChrome.js.map +1 -1
- package/lib/projection/ui/projectionMapView.d.ts +13 -0
- package/lib/projection/ui/projectionMapView.d.ts.map +1 -1
- package/lib/projection/ui/projectionMapView.js +26 -0
- package/lib/projection/ui/projectionMapView.js.map +1 -1
- package/lib/projection/ui/projectionSpaceModel.d.ts +14 -0
- package/lib/projection/ui/projectionSpaceModel.d.ts.map +1 -0
- package/lib/projection/ui/projectionSpaceModel.js +14 -0
- package/lib/projection/ui/projectionSpaceModel.js.map +1 -0
- package/lib/projection/ui/projectionViewerProps.d.ts +34 -0
- package/lib/projection/ui/projectionViewerProps.d.ts.map +1 -1
- package/lib/scatter/ui/ScatterPlot.d.ts +1 -1
- package/lib/scatter/ui/ScatterPlot.d.ts.map +1 -1
- package/lib/scatter/ui/ScatterPlot.js +18 -2
- package/lib/scatter/ui/ScatterPlot.js.map +1 -1
- package/lib/scatter/ui/lassoGesture.d.ts +14 -2
- package/lib/scatter/ui/lassoGesture.d.ts.map +1 -1
- package/lib/scatter/ui/lassoGesture.js +4 -1
- package/lib/scatter/ui/lassoGesture.js.map +1 -1
- package/lib/scatter/ui/scatterPlotProps.d.ts +34 -0
- package/lib/scatter/ui/scatterPlotProps.d.ts.map +1 -1
- package/lib/scatter/ui/useScatterInteraction.d.ts +9 -0
- package/lib/scatter/ui/useScatterInteraction.d.ts.map +1 -1
- package/lib/scatter/ui/useScatterInteraction.js +9 -1
- package/lib/scatter/ui/useScatterInteraction.js.map +1 -1
- package/lib/scatter3d/core/cloudGesture.d.ts +17 -0
- package/lib/scatter3d/core/cloudGesture.d.ts.map +1 -0
- package/lib/scatter3d/core/cloudGesture.js +2 -0
- package/lib/scatter3d/core/cloudGesture.js.map +1 -0
- package/lib/scatter3d/core/confidenceEllipsoid.d.ts +63 -0
- package/lib/scatter3d/core/confidenceEllipsoid.d.ts.map +1 -0
- package/lib/scatter3d/core/confidenceEllipsoid.js +128 -0
- package/lib/scatter3d/core/confidenceEllipsoid.js.map +1 -0
- package/lib/scatter3d/core/cubeFrame.d.ts +40 -0
- package/lib/scatter3d/core/cubeFrame.d.ts.map +1 -0
- package/lib/scatter3d/core/cubeFrame.js +58 -0
- package/lib/scatter3d/core/cubeFrame.js.map +1 -0
- package/lib/scatter3d/core/cubePoints.d.ts +35 -0
- package/lib/scatter3d/core/cubePoints.d.ts.map +1 -0
- package/lib/scatter3d/core/cubePoints.js +50 -0
- package/lib/scatter3d/core/cubePoints.js.map +1 -0
- package/lib/scatter3d/core/ellipsoidCoverage.d.ts +32 -0
- package/lib/scatter3d/core/ellipsoidCoverage.d.ts.map +1 -0
- package/lib/scatter3d/core/ellipsoidCoverage.js +83 -0
- package/lib/scatter3d/core/ellipsoidCoverage.js.map +1 -0
- package/lib/scatter3d/core/ellipsoidSilhouette.d.ts +42 -0
- package/lib/scatter3d/core/ellipsoidSilhouette.d.ts.map +1 -0
- package/lib/scatter3d/core/ellipsoidSilhouette.js +52 -0
- package/lib/scatter3d/core/ellipsoidSilhouette.js.map +1 -0
- package/lib/scatter3d/core/index.d.ts +29 -0
- package/lib/scatter3d/core/index.d.ts.map +1 -0
- package/lib/scatter3d/core/index.js +22 -0
- package/lib/scatter3d/core/index.js.map +1 -0
- package/lib/scatter3d/core/orbitCamera.d.ts +113 -0
- package/lib/scatter3d/core/orbitCamera.d.ts.map +1 -0
- package/lib/scatter3d/core/orbitCamera.js +104 -0
- package/lib/scatter3d/core/orbitCamera.js.map +1 -0
- package/lib/scatter3d/core/symmetricEigen.d.ts +47 -0
- package/lib/scatter3d/core/symmetricEigen.d.ts.map +1 -0
- package/lib/scatter3d/core/symmetricEigen.js +219 -0
- package/lib/scatter3d/core/symmetricEigen.js.map +1 -0
- package/lib/scatter3d/ui/CloudFrameLayer.d.ts +33 -0
- package/lib/scatter3d/ui/CloudFrameLayer.d.ts.map +1 -0
- package/lib/scatter3d/ui/CloudFrameLayer.js +37 -0
- package/lib/scatter3d/ui/CloudFrameLayer.js.map +1 -0
- package/lib/scatter3d/ui/CloudPointLayer.d.ts +62 -0
- package/lib/scatter3d/ui/CloudPointLayer.d.ts.map +1 -0
- package/lib/scatter3d/ui/CloudPointLayer.js +67 -0
- package/lib/scatter3d/ui/CloudPointLayer.js.map +1 -0
- package/lib/scatter3d/ui/CloudShellLayer.d.ts +52 -0
- package/lib/scatter3d/ui/CloudShellLayer.d.ts.map +1 -0
- package/lib/scatter3d/ui/CloudShellLayer.js +40 -0
- package/lib/scatter3d/ui/CloudShellLayer.js.map +1 -0
- package/lib/scatter3d/ui/ScatterCloud.d.ts +24 -0
- package/lib/scatter3d/ui/ScatterCloud.d.ts.map +1 -0
- package/lib/scatter3d/ui/ScatterCloud.js +189 -0
- package/lib/scatter3d/ui/ScatterCloud.js.map +1 -0
- package/lib/scatter3d/ui/cloudGeometry.d.ts +108 -0
- package/lib/scatter3d/ui/cloudGeometry.d.ts.map +1 -0
- package/lib/scatter3d/ui/cloudGeometry.js +122 -0
- package/lib/scatter3d/ui/cloudGeometry.js.map +1 -0
- package/lib/scatter3d/ui/cloudShellStyles.d.ts +36 -0
- package/lib/scatter3d/ui/cloudShellStyles.d.ts.map +1 -0
- package/lib/scatter3d/ui/cloudShellStyles.js +62 -0
- package/lib/scatter3d/ui/cloudShellStyles.js.map +1 -0
- package/lib/scatter3d/ui/cloudZoom.d.ts +21 -0
- package/lib/scatter3d/ui/cloudZoom.d.ts.map +1 -0
- package/lib/scatter3d/ui/cloudZoom.js +22 -0
- package/lib/scatter3d/ui/cloudZoom.js.map +1 -0
- package/lib/scatter3d/ui/index.d.ts +29 -0
- package/lib/scatter3d/ui/index.d.ts.map +1 -0
- package/lib/scatter3d/ui/index.js +21 -0
- package/lib/scatter3d/ui/index.js.map +1 -0
- package/lib/scatter3d/ui/scatterCloudModel.d.ts +60 -0
- package/lib/scatter3d/ui/scatterCloudModel.d.ts.map +1 -0
- package/lib/scatter3d/ui/scatterCloudModel.js +66 -0
- package/lib/scatter3d/ui/scatterCloudModel.js.map +1 -0
- package/lib/scatter3d/ui/scatterCloudProps.d.ts +185 -0
- package/lib/scatter3d/ui/scatterCloudProps.d.ts.map +1 -0
- package/lib/scatter3d/ui/scatterCloudProps.js +9 -0
- package/lib/scatter3d/ui/scatterCloudProps.js.map +1 -0
- package/lib/scatter3d/ui/useOrbitDrag.d.ts +60 -0
- package/lib/scatter3d/ui/useOrbitDrag.d.ts.map +1 -0
- package/lib/scatter3d/ui/useOrbitDrag.js +129 -0
- package/lib/scatter3d/ui/useOrbitDrag.js.map +1 -0
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/chrome/ui/NavLink.tsx +5 -3
- package/src/chrome/ui/SiteFooter.tsx +16 -1
- package/src/chrome/ui/SiteHeader.tsx +19 -1
- package/src/chrome/ui/navItem.ts +12 -0
- package/src/core.ts +1 -0
- package/src/download/core/figureLegend.ts +167 -0
- package/src/download/core/figureSvg.ts +81 -1
- package/src/download/core/figureSvgDocument.ts +13 -3
- package/src/download/core/figureTarget.ts +21 -0
- package/src/download/core/index.ts +8 -0
- package/src/ecosystem/core/sites.ts +14 -0
- package/src/ecosystem/ui/glyphs.tsx +26 -0
- package/src/overlay/ui/OverlayLegend.tsx +7 -1
- package/src/projection/core/projectionCopy.ts +9 -0
- package/src/projection/core/projectionOptions.ts +48 -3
- package/src/projection/core/projectionStrings.ts +16 -0
- package/src/projection/core/projectionTabDefaults.ts +12 -0
- package/src/projection/core/projectionTabs.ts +10 -1
- package/src/projection/ui/ProjectionMapTab.tsx +8 -0
- package/src/projection/ui/ProjectionPanel.tsx +63 -0
- package/src/projection/ui/ProjectionReadout.tsx +16 -6
- package/src/projection/ui/ProjectionSpaceControls.tsx +219 -0
- package/src/projection/ui/ProjectionSpaceMore.tsx +169 -0
- package/src/projection/ui/ProjectionSpaceTab.tsx +217 -0
- package/src/projection/ui/ProjectionViewer.tsx +3 -0
- package/src/projection/ui/index.ts +1 -0
- package/src/projection/ui/projectionBarModel.ts +3 -2
- package/src/projection/ui/projectionBarReadings.ts +18 -0
- package/src/projection/ui/projectionBarSlots.tsx +19 -0
- package/src/projection/ui/projectionCloudChoices.ts +23 -0
- package/src/projection/ui/projectionEllipsoid.ts +78 -0
- package/src/projection/ui/projectionMapChrome.ts +26 -5
- package/src/projection/ui/projectionMapView.ts +45 -0
- package/src/projection/ui/projectionSpaceModel.ts +14 -0
- package/src/projection/ui/projectionViewerProps.ts +35 -0
- package/src/scatter/ui/ScatterPlot.tsx +21 -2
- package/src/scatter/ui/lassoGesture.ts +15 -2
- package/src/scatter/ui/scatterPlotProps.ts +35 -0
- package/src/scatter/ui/useScatterInteraction.ts +22 -1
- package/src/scatter3d/core/cloudGesture.ts +16 -0
- package/src/scatter3d/core/confidenceEllipsoid.ts +177 -0
- package/src/scatter3d/core/cubeFrame.ts +72 -0
- package/src/scatter3d/core/cubePoints.ts +75 -0
- package/src/scatter3d/core/ellipsoidCoverage.ts +86 -0
- package/src/scatter3d/core/ellipsoidSilhouette.ts +76 -0
- package/src/scatter3d/core/index.ts +52 -0
- package/src/scatter3d/core/orbitCamera.ts +159 -0
- package/src/scatter3d/core/symmetricEigen.ts +273 -0
- package/src/scatter3d/ui/CloudFrameLayer.tsx +97 -0
- package/src/scatter3d/ui/CloudPointLayer.tsx +132 -0
- package/src/scatter3d/ui/CloudShellLayer.tsx +107 -0
- package/src/scatter3d/ui/ScatterCloud.tsx +312 -0
- package/src/scatter3d/ui/cloudGeometry.ts +200 -0
- package/src/scatter3d/ui/cloudShellStyles.ts +74 -0
- package/src/scatter3d/ui/cloudZoom.ts +22 -0
- package/src/scatter3d/ui/index.ts +41 -0
- package/src/scatter3d/ui/scatterCloudModel.ts +98 -0
- package/src/scatter3d/ui/scatterCloudProps.ts +191 -0
- package/src/scatter3d/ui/useOrbitDrag.ts +220 -0
- package/src/ui.ts +1 -0
- 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(
|
|
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
|
-
|
|
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:
|
|
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={
|
|
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
|
-
*
|
|
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:
|
|
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
|
-
|
|
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';
|