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
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { OverlayTier } from '../../overlay/core/overlayTiers.ts';
|
|
4
|
+
import type { OverlayChipSetting } from '../../overlay/ui/OverlayChip.tsx';
|
|
5
|
+
import { OverlayChip } from '../../overlay/ui/OverlayChip.tsx';
|
|
6
|
+
import { OverlaySegmented } from '../../overlay/ui/OverlaySegmented.tsx';
|
|
7
|
+
import { OverlaySelect } from '../../overlay/ui/OverlaySelect.tsx';
|
|
8
|
+
import { OverlayToggle } from '../../overlay/ui/OverlayToggle.tsx';
|
|
9
|
+
import { OverlayValueMenu } from '../../overlay/ui/OverlayValueMenu.tsx';
|
|
10
|
+
import type { CloudGesture } from '../../scatter3d/core/cloudGesture.ts';
|
|
11
|
+
import type { ProjectionCopy } from '../core/projectionCopy.ts';
|
|
12
|
+
import type {
|
|
13
|
+
ProjectionColorBy,
|
|
14
|
+
ProjectionOptions,
|
|
15
|
+
} from '../core/projectionOptions.ts';
|
|
16
|
+
|
|
17
|
+
import type { ProjectionReading } from './projectionBarReadings.ts';
|
|
18
|
+
import { CLOUD_GESTURE_CHOICES } from './projectionCloudChoices.ts';
|
|
19
|
+
import { ellipseKey, ellipseSize } from './projectionEllipse.ts';
|
|
20
|
+
import { ellipsoidChoices } from './projectionEllipsoid.ts';
|
|
21
|
+
import { projectionColourChoices } from './projectionMapChoices.ts';
|
|
22
|
+
|
|
23
|
+
/** What the cloud's controls are drawn from, wherever in the bar they sit. */
|
|
24
|
+
export interface ProjectionSpaceControlsProps {
|
|
25
|
+
/** What the cloud is showing, already resolved against the result. */
|
|
26
|
+
options: ProjectionOptions;
|
|
27
|
+
/** Called with only the options that changed, for the viewer to merge. */
|
|
28
|
+
onChange: (patch: Partial<ProjectionOptions>) => void;
|
|
29
|
+
/** The words the bar writes, already merged over the defaults. */
|
|
30
|
+
copy: ProjectionCopy;
|
|
31
|
+
/** What the set of groups is called. */
|
|
32
|
+
groupLabel: string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the samples carry groups at all. Without them the shells stand for
|
|
35
|
+
* nothing, so their control is greyed rather than removed.
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
hasGroups?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* What the settings currently read, built once for the bar and handed down.
|
|
41
|
+
* @default [] — nothing is written, which is what the cog's panel wants
|
|
42
|
+
*/
|
|
43
|
+
readings?: readonly ProjectionReading[];
|
|
44
|
+
/**
|
|
45
|
+
* How much of itself the bar is writing.
|
|
46
|
+
* @default 'full'
|
|
47
|
+
*/
|
|
48
|
+
tier?: OverlayTier;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The cloud settings a reader changes while looking at the picture.
|
|
53
|
+
*
|
|
54
|
+
* One of them is not on the map's bar at all, and it is the first: what a
|
|
55
|
+
* plain drag does. Turning the box and drawing a lasso both want that drag,
|
|
56
|
+
* and neither can be given a modifier without taking one that already means
|
|
57
|
+
* "add" or "take away" inside a lasso — so the choice is written on the bar
|
|
58
|
+
* where the reader can see it, rather than left as a shortcut they would have
|
|
59
|
+
* to be told about. It leads the row because it is the only setting on the bar
|
|
60
|
+
* that changes what the reader's own hand does.
|
|
61
|
+
*
|
|
62
|
+
* The rest are the map's, unchanged: how large the shells are, and the two
|
|
63
|
+
* name switches. The axes and the dot size wait behind the cog exactly as they
|
|
64
|
+
* do on the map.
|
|
65
|
+
* @param props - See {@link ProjectionSpaceControlsProps}.
|
|
66
|
+
* @returns The controls.
|
|
67
|
+
*/
|
|
68
|
+
export function ProjectionSpaceControls(
|
|
69
|
+
props: ProjectionSpaceControlsProps,
|
|
70
|
+
): ReactElement {
|
|
71
|
+
const { options, onChange, copy, groupLabel, hasGroups = false } = props;
|
|
72
|
+
const { readings = NO_READINGS, tier = 'full' } = props;
|
|
73
|
+
const { bar, help } = copy;
|
|
74
|
+
|
|
75
|
+
const uncoloured = !hasGroups || options.colorBy === 'none';
|
|
76
|
+
const names = (
|
|
77
|
+
<>
|
|
78
|
+
<OverlayToggle
|
|
79
|
+
label={bar.switches.showGroupLabels}
|
|
80
|
+
help={help.showGroupLabels}
|
|
81
|
+
icon={GROUP_LABELS_GLYPH}
|
|
82
|
+
hideLabel
|
|
83
|
+
checked={options.showGroupLabels}
|
|
84
|
+
disabled={uncoloured}
|
|
85
|
+
onChange={(showGroupLabels) => onChange({ showGroupLabels })}
|
|
86
|
+
/>
|
|
87
|
+
<OverlayToggle
|
|
88
|
+
label={bar.switches.showIds}
|
|
89
|
+
help={help.showIds}
|
|
90
|
+
icon={IDS_GLYPH}
|
|
91
|
+
hideLabel
|
|
92
|
+
checked={options.showIds}
|
|
93
|
+
onChange={(showIds) => onChange({ showIds })}
|
|
94
|
+
/>
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (tier === 'chip' || tier === 'tiny') {
|
|
99
|
+
return (
|
|
100
|
+
<>
|
|
101
|
+
<OverlayChip label={bar.settings} settings={chipSettings(readings)}>
|
|
102
|
+
<SpaceSettings
|
|
103
|
+
options={options}
|
|
104
|
+
onChange={onChange}
|
|
105
|
+
copy={copy}
|
|
106
|
+
groupLabel={groupLabel}
|
|
107
|
+
hasGroups={hasGroups}
|
|
108
|
+
/>
|
|
109
|
+
</OverlayChip>
|
|
110
|
+
{names}
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<>
|
|
117
|
+
<OverlayValueMenu<CloudGesture>
|
|
118
|
+
label={help.cloudGesture.title}
|
|
119
|
+
keyWord={bar.key.cloudGesture}
|
|
120
|
+
showKey={tier === 'full'}
|
|
121
|
+
value={options.cloudGesture}
|
|
122
|
+
options={CLOUD_GESTURE_CHOICES}
|
|
123
|
+
onChange={(cloudGesture) => onChange({ cloudGesture })}
|
|
124
|
+
/>
|
|
125
|
+
<OverlayValueMenu
|
|
126
|
+
label={help.ellipse.title}
|
|
127
|
+
keyWord={bar.key.ellipse}
|
|
128
|
+
showKey={tier === 'full'}
|
|
129
|
+
value={ellipseKey(options.ellipse)}
|
|
130
|
+
disabled={uncoloured}
|
|
131
|
+
disabledReason={UNCOLOURED_REASON}
|
|
132
|
+
options={ellipsoidChoices(options.ellipse)}
|
|
133
|
+
onChange={(value) => onChange({ ellipse: ellipseSize(value) })}
|
|
134
|
+
/>
|
|
135
|
+
{names}
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* What the chip reads out, which is every setting spelled in full.
|
|
142
|
+
* @param readings - What the settings currently read.
|
|
143
|
+
* @returns The chip's own settings.
|
|
144
|
+
*/
|
|
145
|
+
function chipSettings(
|
|
146
|
+
readings: readonly ProjectionReading[],
|
|
147
|
+
): readonly OverlayChipSetting[] {
|
|
148
|
+
const settings: OverlayChipSetting[] = [];
|
|
149
|
+
for (const reading of readings) {
|
|
150
|
+
settings.push({
|
|
151
|
+
label: reading.label,
|
|
152
|
+
value: reading.value,
|
|
153
|
+
swatches: reading.swatches,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return settings;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** What the captioned pair behind the chip is drawn from. */
|
|
160
|
+
interface SpaceSettingsProps {
|
|
161
|
+
/** What the cloud is showing, already resolved against the result. */
|
|
162
|
+
options: ProjectionOptions;
|
|
163
|
+
/** Called with only the options that changed. */
|
|
164
|
+
onChange: (patch: Partial<ProjectionOptions>) => void;
|
|
165
|
+
/** The words the panel writes. */
|
|
166
|
+
copy: ProjectionCopy;
|
|
167
|
+
/** What the set of groups is called, which is what `Colour by` offers. */
|
|
168
|
+
groupLabel: string;
|
|
169
|
+
/** Whether the samples carry groups at all. */
|
|
170
|
+
hasGroups: boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The same settings with their captions, for the panel the chip opens.
|
|
175
|
+
* @param props - See {@link SpaceSettingsProps}.
|
|
176
|
+
* @returns The captioned controls.
|
|
177
|
+
*/
|
|
178
|
+
function SpaceSettings(props: SpaceSettingsProps): ReactElement {
|
|
179
|
+
const { options, onChange, copy, groupLabel, hasGroups } = props;
|
|
180
|
+
const { bar, help } = copy;
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<>
|
|
184
|
+
<OverlaySegmented<CloudGesture>
|
|
185
|
+
label={help.cloudGesture.title}
|
|
186
|
+
help={help.cloudGesture}
|
|
187
|
+
value={options.cloudGesture}
|
|
188
|
+
options={CLOUD_GESTURE_CHOICES}
|
|
189
|
+
onChange={(cloudGesture) => onChange({ cloudGesture })}
|
|
190
|
+
/>
|
|
191
|
+
<OverlaySegmented<ProjectionColorBy>
|
|
192
|
+
label={help.colorBy.title}
|
|
193
|
+
help={help.colorBy}
|
|
194
|
+
value={options.colorBy}
|
|
195
|
+
disabled={!hasGroups}
|
|
196
|
+
options={projectionColourChoices(groupLabel, bar.uncoloured)}
|
|
197
|
+
onChange={(colorBy) => onChange({ colorBy })}
|
|
198
|
+
/>
|
|
199
|
+
<OverlaySelect
|
|
200
|
+
label={help.ellipse.title}
|
|
201
|
+
help={help.ellipse}
|
|
202
|
+
value={ellipseKey(options.ellipse)}
|
|
203
|
+
disabled={!hasGroups || options.colorBy === 'none'}
|
|
204
|
+
options={ellipsoidChoices(options.ellipse)}
|
|
205
|
+
onChange={(value) => onChange({ ellipse: ellipseSize(value) })}
|
|
206
|
+
/>
|
|
207
|
+
</>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The two glyphs the name switches ride as, the same pair the map uses. */
|
|
212
|
+
const GROUP_LABELS_GLYPH = 'tag';
|
|
213
|
+
const IDS_GLYPH = 'id-number';
|
|
214
|
+
|
|
215
|
+
const UNCOLOURED_REASON =
|
|
216
|
+
'Shells follow the groups, so colour the cloud by them first.';
|
|
217
|
+
|
|
218
|
+
/** No readings at all, which is what the panel behind the cog is handed. */
|
|
219
|
+
const NO_READINGS: readonly ProjectionReading[] = [];
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import { chartAxisTitle } from '../../chart/core/chartLabels.ts';
|
|
4
|
+
import { OverlayAction } from '../../overlay/ui/OverlayAction.tsx';
|
|
5
|
+
import { OverlayGroup } from '../../overlay/ui/OverlayGroup.tsx';
|
|
6
|
+
import { OverlayNumber } from '../../overlay/ui/OverlayNumber.tsx';
|
|
7
|
+
import { OverlayPanel } from '../../overlay/ui/OverlayPanel.tsx';
|
|
8
|
+
import type { OverlayOption } from '../../overlay/ui/OverlayRow.tsx';
|
|
9
|
+
import { OverlaySegmented } from '../../overlay/ui/OverlaySegmented.tsx';
|
|
10
|
+
import { OverlaySelect } from '../../overlay/ui/OverlaySelect.tsx';
|
|
11
|
+
import type { ScatterSelectionMode } from '../../scatter/core/scatterSelection.ts';
|
|
12
|
+
import type { CloudGesture } from '../../scatter3d/core/cloudGesture.ts';
|
|
13
|
+
import type { ProjectionResult } from '../core/projectionResult.ts';
|
|
14
|
+
import {
|
|
15
|
+
PROJECTION_PANEL_NAME,
|
|
16
|
+
PROJECTION_PANEL_SECTION,
|
|
17
|
+
} from '../core/projectionStrings.ts';
|
|
18
|
+
import { PROJECTION_TAB_DEFAULTS } from '../core/projectionTabDefaults.ts';
|
|
19
|
+
|
|
20
|
+
import type { ProjectionSpaceControlsProps } from './ProjectionSpaceControls.tsx';
|
|
21
|
+
import { CLOUD_GESTURE_CHOICES } from './projectionCloudChoices.ts';
|
|
22
|
+
|
|
23
|
+
/** What {@link ProjectionSpaceMore} needs beyond the cloud's own controls. */
|
|
24
|
+
export interface ProjectionSpaceMoreProps extends ProjectionSpaceControlsProps {
|
|
25
|
+
/** The reduced space the controls apply to; its axes fill the three pickers. */
|
|
26
|
+
result: ProjectionResult;
|
|
27
|
+
/**
|
|
28
|
+
* How many samples are selected, which is what the clearing action needs
|
|
29
|
+
* before it can do anything.
|
|
30
|
+
* @default 0
|
|
31
|
+
*/
|
|
32
|
+
selectedCount?: number;
|
|
33
|
+
/** Puts the box back where it was first seen. */
|
|
34
|
+
onResetView: () => void;
|
|
35
|
+
/** Empties the selection. */
|
|
36
|
+
onClearSelection: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* What the cloud keeps behind the cog: its three axes, the dot size, and what
|
|
41
|
+
* the two drags do.
|
|
42
|
+
*
|
|
43
|
+
* It is the map's panel with one picker more and one action fewer. There is no
|
|
44
|
+
* "zoom to selection" here, because a box has no frame to draw round a crowd:
|
|
45
|
+
* the reader turns it until the crowd is facing them, which is the gesture the
|
|
46
|
+
* whole tab exists for, and a button that guessed an angle for them would
|
|
47
|
+
* usually guess wrong.
|
|
48
|
+
* @param props - See {@link ProjectionSpaceMoreProps}.
|
|
49
|
+
* @returns The panel behind the cog.
|
|
50
|
+
*/
|
|
51
|
+
export function ProjectionSpaceMore(
|
|
52
|
+
props: ProjectionSpaceMoreProps,
|
|
53
|
+
): ReactElement {
|
|
54
|
+
const { result, options, onChange, copy } = props;
|
|
55
|
+
const { selectedCount = 0, onResetView, onClearSelection } = props;
|
|
56
|
+
const { action, help, tab } = copy;
|
|
57
|
+
|
|
58
|
+
const nothingPicked = selectedCount === 0;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<OverlayPanel
|
|
62
|
+
title={tab.space}
|
|
63
|
+
onReset={() => onChange(PROJECTION_TAB_DEFAULTS.space)}
|
|
64
|
+
actions={
|
|
65
|
+
<>
|
|
66
|
+
<OverlayAction
|
|
67
|
+
text={action.resetView}
|
|
68
|
+
icon="reset"
|
|
69
|
+
onClick={onResetView}
|
|
70
|
+
/>
|
|
71
|
+
<OverlayAction
|
|
72
|
+
text={action.clearSelection}
|
|
73
|
+
icon="cross"
|
|
74
|
+
disabled={nothingPicked}
|
|
75
|
+
onClick={onClearSelection}
|
|
76
|
+
/>
|
|
77
|
+
</>
|
|
78
|
+
}
|
|
79
|
+
>
|
|
80
|
+
<OverlayGroup label={PROJECTION_PANEL_SECTION.axes} divider={false}>
|
|
81
|
+
<OverlaySelect
|
|
82
|
+
label={PROJECTION_PANEL_NAME.xAxis}
|
|
83
|
+
help={help.xAxis}
|
|
84
|
+
value={String(options.xAxis)}
|
|
85
|
+
options={axisChoices(result, [options.yAxis, options.zAxis])}
|
|
86
|
+
onChange={(value) => onChange({ xAxis: Number(value) })}
|
|
87
|
+
/>
|
|
88
|
+
<OverlaySelect
|
|
89
|
+
label={PROJECTION_PANEL_NAME.yAxis}
|
|
90
|
+
help={help.yAxis}
|
|
91
|
+
value={String(options.yAxis)}
|
|
92
|
+
options={axisChoices(result, [options.xAxis, options.zAxis])}
|
|
93
|
+
onChange={(value) => onChange({ yAxis: Number(value) })}
|
|
94
|
+
/>
|
|
95
|
+
<OverlaySelect
|
|
96
|
+
label={PROJECTION_PANEL_NAME.zAxis}
|
|
97
|
+
help={help.zAxis}
|
|
98
|
+
value={String(options.zAxis)}
|
|
99
|
+
options={axisChoices(result, [options.xAxis, options.yAxis])}
|
|
100
|
+
onChange={(value) => onChange({ zAxis: Number(value) })}
|
|
101
|
+
/>
|
|
102
|
+
</OverlayGroup>
|
|
103
|
+
<OverlayGroup label={PROJECTION_PANEL_SECTION.drawing}>
|
|
104
|
+
<OverlayNumber
|
|
105
|
+
label={PROJECTION_PANEL_NAME.pointRadius}
|
|
106
|
+
help={help.pointRadius}
|
|
107
|
+
value={options.pointRadius}
|
|
108
|
+
min={1}
|
|
109
|
+
max={12}
|
|
110
|
+
step={0.5}
|
|
111
|
+
digits={1}
|
|
112
|
+
unit="px"
|
|
113
|
+
onChange={(pointRadius) => onChange({ pointRadius })}
|
|
114
|
+
/>
|
|
115
|
+
</OverlayGroup>
|
|
116
|
+
<OverlayGroup label={PROJECTION_PANEL_SECTION.handling}>
|
|
117
|
+
<OverlaySegmented<CloudGesture>
|
|
118
|
+
label={PROJECTION_PANEL_NAME.cloudGesture}
|
|
119
|
+
help={help.cloudGesture}
|
|
120
|
+
value={options.cloudGesture}
|
|
121
|
+
options={CLOUD_GESTURE_CHOICES}
|
|
122
|
+
onChange={(cloudGesture) => onChange({ cloudGesture })}
|
|
123
|
+
/>
|
|
124
|
+
</OverlayGroup>
|
|
125
|
+
<OverlayGroup label={PROJECTION_PANEL_SECTION.selecting}>
|
|
126
|
+
<OverlaySegmented<ScatterSelectionMode>
|
|
127
|
+
label={PROJECTION_PANEL_NAME.selectMode}
|
|
128
|
+
help={help.selectMode}
|
|
129
|
+
value={options.selectMode}
|
|
130
|
+
options={SELECT_MODE_CHOICES}
|
|
131
|
+
onChange={(selectMode) => onChange({ selectMode })}
|
|
132
|
+
/>
|
|
133
|
+
</OverlayGroup>
|
|
134
|
+
</OverlayPanel>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const AXIS_TAKEN_NOTE = 'Already drawn on another axis.';
|
|
139
|
+
|
|
140
|
+
/** The same three words the map's panel offers, for the same reason. */
|
|
141
|
+
const SELECT_MODE_CHOICES: ReadonlyArray<OverlayOption<ScatterSelectionMode>> =
|
|
142
|
+
[
|
|
143
|
+
{ value: 'replace', label: 'Replace' },
|
|
144
|
+
{ value: 'add', label: 'Add' },
|
|
145
|
+
{ value: 'remove', label: 'Remove' },
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The axes one picker offers, with the ones already drawn kept but unreachable.
|
|
150
|
+
* @param result - The reduced space being drawn.
|
|
151
|
+
* @param taken - The axes the other two pickers are showing.
|
|
152
|
+
* @returns The choices, in the order the run produced them.
|
|
153
|
+
*/
|
|
154
|
+
function axisChoices(
|
|
155
|
+
result: ProjectionResult,
|
|
156
|
+
taken: readonly number[],
|
|
157
|
+
): OverlayOption[] {
|
|
158
|
+
const choices: OverlayOption[] = [];
|
|
159
|
+
for (const [index, axis] of result.axes.entries()) {
|
|
160
|
+
const drawn = taken.includes(index);
|
|
161
|
+
choices.push({
|
|
162
|
+
value: String(index),
|
|
163
|
+
label: chartAxisTitle(axis.name, { share: axis.share }),
|
|
164
|
+
disabled: drawn,
|
|
165
|
+
title: drawn ? AXIS_TAKEN_NOTE : undefined,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return choices;
|
|
169
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
import type { ScatterPointOpen } from '../../scatter/ui/scatterPlotProps.ts';
|
|
5
|
+
import type { SelectionChange } from '../../scatter/ui/useScatterSelection.ts';
|
|
6
|
+
import type { OrbitCamera } from '../../scatter3d/core/orbitCamera.ts';
|
|
7
|
+
import { ScatterCloud } from '../../scatter3d/ui/ScatterCloud.tsx';
|
|
8
|
+
import type { ProjectionCopy } from '../core/projectionCopy.ts';
|
|
9
|
+
import type { ProjectionOptions } from '../core/projectionOptions.ts';
|
|
10
|
+
import type { ProjectionResult } from '../core/projectionResult.ts';
|
|
11
|
+
import type { ResolvedProjectionGroups } from '../core/projectionSamples.ts';
|
|
12
|
+
|
|
13
|
+
import type { SelectionReport } from './ProjectionMapCaption.tsx';
|
|
14
|
+
import { ProjectionMapCaption } from './ProjectionMapCaption.tsx';
|
|
15
|
+
import { ProjectionMapLegend } from './ProjectionMapLegend.tsx';
|
|
16
|
+
import { ProjectionSurface } from './ProjectionSurface.tsx';
|
|
17
|
+
import type { ProjectionMapChrome } from './projectionMapChrome.ts';
|
|
18
|
+
import {
|
|
19
|
+
projectionAxisTitle,
|
|
20
|
+
projectionMapCloud,
|
|
21
|
+
projectionScatterGroups,
|
|
22
|
+
} from './projectionMapModel.ts';
|
|
23
|
+
import { MINIMUM_SHELL_POINTS } from './projectionSpaceModel.ts';
|
|
24
|
+
|
|
25
|
+
/** What {@link ProjectionSpaceTab} needs. */
|
|
26
|
+
export interface ProjectionSpaceTabProps {
|
|
27
|
+
/** What the run produced, whatever produced it. */
|
|
28
|
+
result: ProjectionResult;
|
|
29
|
+
/** The groups, resolved once by the viewer so every tab colours alike. */
|
|
30
|
+
groups: ResolvedProjectionGroups;
|
|
31
|
+
/** What the cloud is showing, already resolved against `result`. */
|
|
32
|
+
options: ProjectionOptions;
|
|
33
|
+
/** The words the cloud writes, already merged over the defaults. */
|
|
34
|
+
copy: ProjectionCopy;
|
|
35
|
+
/** The key's title and entries, built once with the bar's explanation. */
|
|
36
|
+
chrome: ProjectionMapChrome;
|
|
37
|
+
/**
|
|
38
|
+
* What each sample is called, in the score matrix's row order.
|
|
39
|
+
* @default undefined — the setting has nothing to write and draws nothing
|
|
40
|
+
*/
|
|
41
|
+
ids?: readonly string[];
|
|
42
|
+
/** Width of the figure, in pixels. */
|
|
43
|
+
width: number;
|
|
44
|
+
/** Height of the figure, in pixels. */
|
|
45
|
+
height: number;
|
|
46
|
+
/** The selected rows, as indices into the score matrix. */
|
|
47
|
+
selected: readonly number[];
|
|
48
|
+
/** Where the reader is standing, which the viewer keeps between visits. */
|
|
49
|
+
camera: OrbitCamera;
|
|
50
|
+
/** Called with the camera every frame of a turn leaves behind. */
|
|
51
|
+
onCameraChange: (camera: OrbitCamera) => void;
|
|
52
|
+
/** How far in the reader has zoomed. */
|
|
53
|
+
zoom: number;
|
|
54
|
+
/** Called with the zoom the wheel asks for. */
|
|
55
|
+
onZoomChange: (zoom: number) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the wheel zooms the box.
|
|
58
|
+
* @default true
|
|
59
|
+
*/
|
|
60
|
+
wheelZoom?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* What the last gesture picked out, for the line over the picture.
|
|
63
|
+
* @default null — nothing has happened yet
|
|
64
|
+
*/
|
|
65
|
+
report?: SelectionReport | null;
|
|
66
|
+
/**
|
|
67
|
+
* Called when a lasso is released or a dot is tapped.
|
|
68
|
+
* @default undefined
|
|
69
|
+
*/
|
|
70
|
+
onSelectionChange?: (change: SelectionChange) => void;
|
|
71
|
+
/**
|
|
72
|
+
* Called with the row under the pointer, or `-1`.
|
|
73
|
+
* @default undefined
|
|
74
|
+
*/
|
|
75
|
+
onHoverChange?: (index: number) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Called when the reader double-clicks a sample.
|
|
78
|
+
* @default undefined
|
|
79
|
+
*/
|
|
80
|
+
onPointDoubleClick?: (point: ScatterPointOpen) => void;
|
|
81
|
+
/**
|
|
82
|
+
* The card that follows the pointer, built by the viewer so that it says the
|
|
83
|
+
* same thing on every tab.
|
|
84
|
+
* @default undefined — no card is drawn
|
|
85
|
+
*/
|
|
86
|
+
hoverCard?: ReactNode;
|
|
87
|
+
/**
|
|
88
|
+
* Value of the `data-testid` attribute of the figure.
|
|
89
|
+
* @default undefined
|
|
90
|
+
*/
|
|
91
|
+
testId?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The cloud: the same samples as the map, in a box the reader can turn.
|
|
96
|
+
*
|
|
97
|
+
* It earns a tab of its own rather than a switch on the map because it answers
|
|
98
|
+
* a different question. The map shows the two strongest patterns and is the
|
|
99
|
+
* right picture nine times in ten; the cloud is what a reader reaches for when
|
|
100
|
+
* two groups sit on top of each other there, because a third component often
|
|
101
|
+
* pulls them apart and no flat picture can show that.
|
|
102
|
+
*
|
|
103
|
+
* Everything it can share with the map it does share — the colours, the
|
|
104
|
+
* selection, the lasso, the key, the card and the names — so moving between
|
|
105
|
+
* the two tabs changes the picture and nothing else. What it cannot share is
|
|
106
|
+
* the scale: all three axes are stretched to one box, because a solid seen
|
|
107
|
+
* from an angle has nowhere to put three sets of tick labels, and a box drawn
|
|
108
|
+
* at three scales would tilt a group in a direction the data never did.
|
|
109
|
+
* @param props - See {@link ProjectionSpaceTabProps}.
|
|
110
|
+
* @returns The figure.
|
|
111
|
+
*/
|
|
112
|
+
export function ProjectionSpaceTab(
|
|
113
|
+
props: ProjectionSpaceTabProps,
|
|
114
|
+
): ReactElement {
|
|
115
|
+
const { result, groups, options, copy, chrome, width, height } = props;
|
|
116
|
+
const { ids, selected, report = null, camera, zoom } = props;
|
|
117
|
+
const { onCameraChange, onZoomChange, wheelZoom } = props;
|
|
118
|
+
const { onSelectionChange, onHoverChange, hoverCard, testId } = props;
|
|
119
|
+
const { onPointDoubleClick } = props;
|
|
120
|
+
const { scores, fittedCount } = result;
|
|
121
|
+
const { xAxis, yAxis, zAxis, colorBy, pointRadius } = options;
|
|
122
|
+
const { showGroupLabels, showIds, selectMode, cloudGesture } = options;
|
|
123
|
+
const { ellipse: shells } = options;
|
|
124
|
+
|
|
125
|
+
const flat = useMemo(
|
|
126
|
+
() => projectionMapCloud(scores, xAxis, yAxis),
|
|
127
|
+
[scores, xAxis, yAxis],
|
|
128
|
+
);
|
|
129
|
+
const depth = useMemo(
|
|
130
|
+
() => projectionMapCloud(scores, zAxis, zAxis).x,
|
|
131
|
+
[scores, zAxis],
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const colored = colorBy === 'group' && groups.entries.length > 0;
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div style={{ position: 'relative' }} data-testid={testId}>
|
|
138
|
+
<ScatterCloud
|
|
139
|
+
x={flat.x}
|
|
140
|
+
y={flat.y}
|
|
141
|
+
z={depth}
|
|
142
|
+
width={width}
|
|
143
|
+
height={height}
|
|
144
|
+
xLabel={axisName(result, xAxis)}
|
|
145
|
+
yLabel={axisName(result, yAxis)}
|
|
146
|
+
zLabel={axisName(result, zAxis)}
|
|
147
|
+
groupOf={colored ? groups.groupOf : undefined}
|
|
148
|
+
groups={colored ? projectionScatterGroups(groups) : undefined}
|
|
149
|
+
ellipsoid={colored ? shells : null}
|
|
150
|
+
ellipsoidMinimumPoints={MINIMUM_SHELL_POINTS}
|
|
151
|
+
showGroupLabels={colored && showGroupLabels}
|
|
152
|
+
pointLabels={showIds ? ids : undefined}
|
|
153
|
+
pointRadius={pointRadius}
|
|
154
|
+
outlinedFrom={fittedCount}
|
|
155
|
+
selected={selected}
|
|
156
|
+
onSelectionChange={onSelectionChange}
|
|
157
|
+
selectMode={selectMode}
|
|
158
|
+
onHoverChange={onHoverChange}
|
|
159
|
+
onPointDoubleClick={onPointDoubleClick}
|
|
160
|
+
gesture={cloudGesture}
|
|
161
|
+
camera={camera}
|
|
162
|
+
onCameraChange={onCameraChange}
|
|
163
|
+
zoom={zoom}
|
|
164
|
+
onZoomChange={onZoomChange}
|
|
165
|
+
wheelZoom={wheelZoom}
|
|
166
|
+
overlay={
|
|
167
|
+
<ProjectionSurface>
|
|
168
|
+
{chrome.entries.length === 0 ? null : (
|
|
169
|
+
<ProjectionMapLegend
|
|
170
|
+
title={chrome.title}
|
|
171
|
+
entries={chrome.entries}
|
|
172
|
+
cloud={flat}
|
|
173
|
+
xDomain={CUBE_DOMAIN}
|
|
174
|
+
yDomain={CUBE_DOMAIN}
|
|
175
|
+
width={width}
|
|
176
|
+
height={height}
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
<ProjectionMapCaption
|
|
180
|
+
report={report}
|
|
181
|
+
copy={copy}
|
|
182
|
+
mode={selectMode}
|
|
183
|
+
drawing={false}
|
|
184
|
+
/>
|
|
185
|
+
{hoverCard}
|
|
186
|
+
</ProjectionSurface>
|
|
187
|
+
}
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* What an arm of the frame is called.
|
|
195
|
+
*
|
|
196
|
+
* The frame is the cloud's only record of which axis is which, so an axis the
|
|
197
|
+
* result cannot name is given its number rather than left blank: an unlabelled
|
|
198
|
+
* arm reads as a drawing error, and the reader cannot tell which of the three
|
|
199
|
+
* it was.
|
|
200
|
+
* @param result - The reduced space being drawn.
|
|
201
|
+
* @param index - Which axis.
|
|
202
|
+
* @returns The name to write on that arm.
|
|
203
|
+
*/
|
|
204
|
+
function axisName(result: ProjectionResult, index: number): string {
|
|
205
|
+
return projectionAxisTitle(result, index) ?? `Axis ${index + 1}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The range the key measures emptiness against.
|
|
210
|
+
*
|
|
211
|
+
* The key floats in whichever corner of the picture holds fewest dots, and it
|
|
212
|
+
* works that out from the cloud's own coordinates against a range. A turning
|
|
213
|
+
* box has no fixed range in data units — the same sample is at a different
|
|
214
|
+
* corner a second later — so the cube's own is used, which is stable and is
|
|
215
|
+
* what the corners of the picture actually are.
|
|
216
|
+
*/
|
|
217
|
+
const CUBE_DOMAIN: readonly [number, number] = [-1, 1];
|
|
@@ -15,6 +15,7 @@ import { useProjectionState } from './useProjectionState.ts';
|
|
|
15
15
|
|
|
16
16
|
export type { ProjectionVariableTrack } from './ProjectionVariablesTab.tsx';
|
|
17
17
|
export type { ProjectionSelection } from './projectionSelection.ts';
|
|
18
|
+
export type { ProjectionSampleOpen } from './projectionViewerProps.ts';
|
|
18
19
|
export type { ProjectionViewerProps } from './projectionViewerProps.ts';
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -45,6 +46,7 @@ export type { ProjectionViewerProps } from './projectionViewerProps.ts';
|
|
|
45
46
|
export function ProjectionViewer(props: ProjectionViewerProps): ReactElement {
|
|
46
47
|
const { result, samples, className, testId, onTrackVariable } = props;
|
|
47
48
|
const { onHoverSample, touchLasso, height = PROJECTION_TAB_HEIGHT } = props;
|
|
49
|
+
const { onSampleDoubleClick } = props;
|
|
48
50
|
const { wheelZoom = true } = props;
|
|
49
51
|
const { formatValue = formatProjectionValue, fileName = 'projection' } =
|
|
50
52
|
props;
|
|
@@ -110,6 +112,7 @@ export function ProjectionViewer(props: ProjectionViewerProps): ReactElement {
|
|
|
110
112
|
panelId={panelId}
|
|
111
113
|
strip={strip}
|
|
112
114
|
onHoverSample={onHoverSample}
|
|
115
|
+
onSampleDoubleClick={onSampleDoubleClick}
|
|
113
116
|
onTrackVariable={onTrackVariable}
|
|
114
117
|
touchLasso={touchLasso}
|
|
115
118
|
wheelZoom={wheelZoom}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
export type { PcaViewerProps } from './PcaViewer.tsx';
|
|
15
15
|
export { PcaViewer } from './PcaViewer.tsx';
|
|
16
|
+
export type { ProjectionSampleOpen } from './projectionViewerProps.ts';
|
|
16
17
|
export type { ProjectionSelection } from './projectionSelection.ts';
|
|
17
18
|
export type { ProjectionVariableTrack } from './projectionVariablesModel.ts';
|
|
18
19
|
export type { ProjectionViewerProps } from './projectionViewerProps.ts';
|
|
@@ -71,7 +71,8 @@ export interface ProjectionBarRoom {
|
|
|
71
71
|
*/
|
|
72
72
|
export function projectionBarGlyphs(tab: ProjectionTab): number {
|
|
73
73
|
if (tab === 'shares') return SAVE_GLYPHS + 1;
|
|
74
|
-
|
|
74
|
+
const named = tab === 'map' || tab === 'space';
|
|
75
|
+
return SAVE_GLYPHS + (named ? MAP_BAR_GLYPHS : BAR_GLYPHS);
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
/** The one that takes the figure off the page, on every tab. */
|
|
@@ -80,7 +81,7 @@ const SAVE_GLYPHS = 1;
|
|
|
80
81
|
/** The question mark and the cog, which almost every tab carries. */
|
|
81
82
|
const BAR_GLYPHS = 2;
|
|
82
83
|
|
|
83
|
-
/** Those two, and the map
|
|
84
|
+
/** Those two, and the pair of name switches the map and the cloud both carry. */
|
|
84
85
|
const MAP_BAR_GLYPHS = 4;
|
|
85
86
|
|
|
86
87
|
/**
|
|
@@ -15,6 +15,7 @@ import type { ResolvedProjectionGroups } from '../core/projectionSamples.ts';
|
|
|
15
15
|
import type { ProjectionTab } from '../core/projectionTabs.ts';
|
|
16
16
|
|
|
17
17
|
import { ellipseCoverageText } from './projectionEllipse.ts';
|
|
18
|
+
import { ellipsoidCoverageText } from './projectionEllipsoid.ts';
|
|
18
19
|
import { WHOLE_SHARE } from './projectionTabStyles.ts';
|
|
19
20
|
|
|
20
21
|
/** One setting on the bar, as the reader sees it right now. */
|
|
@@ -81,6 +82,23 @@ export function projectionBarReadings(
|
|
|
81
82
|
},
|
|
82
83
|
];
|
|
83
84
|
}
|
|
85
|
+
if (tab === 'space') {
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
key: bar.key.cloudGesture,
|
|
89
|
+
label: help.cloudGesture.title,
|
|
90
|
+
value: options.cloudGesture === 'turn' ? 'Turn' : 'Select',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: bar.key.ellipse,
|
|
94
|
+
label: help.ellipse.title,
|
|
95
|
+
value:
|
|
96
|
+
options.colorBy === 'group' && groups.entries.length > 0
|
|
97
|
+
? ellipsoidCoverageText(options.ellipse)
|
|
98
|
+
: bar.noOutlines,
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
}
|
|
84
102
|
if (tab === 'shares') {
|
|
85
103
|
return [
|
|
86
104
|
{
|