react-cheminfo 0.21.0 → 0.23.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 (253) hide show
  1. package/README.md +14 -4
  2. package/lib/build/vite/gitHead.d.ts.map +1 -1
  3. package/lib/build/vite/gitHead.js +17 -2
  4. package/lib/build/vite/gitHead.js.map +1 -1
  5. package/lib/clipboard/ui/ClickToCopy.d.ts +69 -0
  6. package/lib/clipboard/ui/ClickToCopy.d.ts.map +1 -0
  7. package/lib/clipboard/ui/ClickToCopy.js +66 -0
  8. package/lib/clipboard/ui/ClickToCopy.js.map +1 -0
  9. package/lib/clipboard/ui/CopyButton.d.ts +1 -1
  10. package/lib/clipboard/ui/CopyButton.js +1 -1
  11. package/lib/clipboard/ui/CopyableValue.d.ts +3 -3
  12. package/lib/clipboard/ui/CopyableValue.d.ts.map +1 -1
  13. package/lib/clipboard/ui/CopyableValue.js +5 -5
  14. package/lib/clipboard/ui/CopyableValue.js.map +1 -1
  15. package/lib/clipboard/ui/index.d.ts +2 -0
  16. package/lib/clipboard/ui/index.d.ts.map +1 -1
  17. package/lib/clipboard/ui/index.js +1 -0
  18. package/lib/clipboard/ui/index.js.map +1 -1
  19. package/lib/ecosystem/core/groups.d.ts +4 -3
  20. package/lib/ecosystem/core/groups.d.ts.map +1 -1
  21. package/lib/ecosystem/core/groups.js +8 -2
  22. package/lib/ecosystem/core/groups.js.map +1 -1
  23. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  24. package/lib/ecosystem/core/sites.js +39 -0
  25. package/lib/ecosystem/core/sites.js.map +1 -1
  26. package/lib/ecosystem/core/types.d.ts +1 -1
  27. package/lib/ecosystem/core/types.d.ts.map +1 -1
  28. package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
  29. package/lib/ecosystem/ui/glyphs.js +9 -0
  30. package/lib/ecosystem/ui/glyphs.js.map +1 -1
  31. package/lib/error/ui/ErrorFallback.d.ts.map +1 -1
  32. package/lib/error/ui/ErrorFallback.js +1 -1
  33. package/lib/error/ui/ErrorFallback.js.map +1 -1
  34. package/lib/overlay/core/index.d.ts +2 -2
  35. package/lib/overlay/core/index.d.ts.map +1 -1
  36. package/lib/overlay/core/index.js +1 -1
  37. package/lib/overlay/core/index.js.map +1 -1
  38. package/lib/overlay/core/overlayMarks.d.ts +20 -5
  39. package/lib/overlay/core/overlayMarks.d.ts.map +1 -1
  40. package/lib/overlay/core/overlayMarks.js +31 -5
  41. package/lib/overlay/core/overlayMarks.js.map +1 -1
  42. package/lib/overlay/ui/OverlayLegendMark.js +5 -0
  43. package/lib/overlay/ui/OverlayLegendMark.js.map +1 -1
  44. package/lib/pedagogy/ui/ReferenceSectionBlock.d.ts.map +1 -1
  45. package/lib/pedagogy/ui/ReferenceSectionBlock.js +2 -1
  46. package/lib/pedagogy/ui/ReferenceSectionBlock.js.map +1 -1
  47. package/lib/projection/core/index.d.ts +4 -4
  48. package/lib/projection/core/index.d.ts.map +1 -1
  49. package/lib/projection/core/index.js +2 -2
  50. package/lib/projection/core/index.js.map +1 -1
  51. package/lib/projection/core/projectionCopy.d.ts +2 -0
  52. package/lib/projection/core/projectionCopy.d.ts.map +1 -1
  53. package/lib/projection/core/projectionCopy.js +1 -0
  54. package/lib/projection/core/projectionCopy.js.map +1 -1
  55. package/lib/projection/core/projectionGroupings.d.ts +44 -0
  56. package/lib/projection/core/projectionGroupings.d.ts.map +1 -0
  57. package/lib/projection/core/projectionGroupings.js +132 -0
  58. package/lib/projection/core/projectionGroupings.js.map +1 -0
  59. package/lib/projection/core/projectionOptions.d.ts +16 -6
  60. package/lib/projection/core/projectionOptions.d.ts.map +1 -1
  61. package/lib/projection/core/projectionOptions.js +7 -1
  62. package/lib/projection/core/projectionOptions.js.map +1 -1
  63. package/lib/projection/core/projectionPanelWords.d.ts +6 -0
  64. package/lib/projection/core/projectionPanelWords.d.ts.map +1 -1
  65. package/lib/projection/core/projectionPanelWords.js +4 -0
  66. package/lib/projection/core/projectionPanelWords.js.map +1 -1
  67. package/lib/projection/core/projectionSamples.d.ts +63 -32
  68. package/lib/projection/core/projectionSamples.d.ts.map +1 -1
  69. package/lib/projection/core/projectionSamples.js +1 -80
  70. package/lib/projection/core/projectionSamples.js.map +1 -1
  71. package/lib/projection/core/projectionStrings.d.ts +2 -0
  72. package/lib/projection/core/projectionStrings.d.ts.map +1 -1
  73. package/lib/projection/core/projectionStrings.js +10 -1
  74. package/lib/projection/core/projectionStrings.js.map +1 -1
  75. package/lib/projection/core/resolveProjectionOptions.d.ts +8 -3
  76. package/lib/projection/core/resolveProjectionOptions.d.ts.map +1 -1
  77. package/lib/projection/core/resolveProjectionOptions.js +58 -5
  78. package/lib/projection/core/resolveProjectionOptions.js.map +1 -1
  79. package/lib/projection/ui/ProjectionBar.d.ts +1 -1
  80. package/lib/projection/ui/ProjectionBar.d.ts.map +1 -1
  81. package/lib/projection/ui/ProjectionBar.js +3 -1
  82. package/lib/projection/ui/ProjectionBar.js.map +1 -1
  83. package/lib/projection/ui/ProjectionGroupingPickers.d.ts +38 -0
  84. package/lib/projection/ui/ProjectionGroupingPickers.d.ts.map +1 -0
  85. package/lib/projection/ui/ProjectionGroupingPickers.js +33 -0
  86. package/lib/projection/ui/ProjectionGroupingPickers.js.map +1 -0
  87. package/lib/projection/ui/ProjectionMapControls.d.ts +6 -7
  88. package/lib/projection/ui/ProjectionMapControls.d.ts.map +1 -1
  89. package/lib/projection/ui/ProjectionMapControls.js +11 -9
  90. package/lib/projection/ui/ProjectionMapControls.js.map +1 -1
  91. package/lib/projection/ui/ProjectionMapLegend.d.ts.map +1 -1
  92. package/lib/projection/ui/ProjectionMapLegend.js +1 -1
  93. package/lib/projection/ui/ProjectionMapLegend.js.map +1 -1
  94. package/lib/projection/ui/ProjectionMapMore.d.ts +2 -2
  95. package/lib/projection/ui/ProjectionMapMore.d.ts.map +1 -1
  96. package/lib/projection/ui/ProjectionMapMore.js +7 -5
  97. package/lib/projection/ui/ProjectionMapMore.js.map +1 -1
  98. package/lib/projection/ui/ProjectionMapTab.d.ts +15 -6
  99. package/lib/projection/ui/ProjectionMapTab.d.ts.map +1 -1
  100. package/lib/projection/ui/ProjectionMapTab.js +11 -7
  101. package/lib/projection/ui/ProjectionMapTab.js.map +1 -1
  102. package/lib/projection/ui/ProjectionPairsMore.d.ts +3 -7
  103. package/lib/projection/ui/ProjectionPairsMore.d.ts.map +1 -1
  104. package/lib/projection/ui/ProjectionPairsMore.js +4 -5
  105. package/lib/projection/ui/ProjectionPairsMore.js.map +1 -1
  106. package/lib/projection/ui/ProjectionPairsTab.d.ts.map +1 -1
  107. package/lib/projection/ui/ProjectionPairsTab.js +2 -2
  108. package/lib/projection/ui/ProjectionPairsTab.js.map +1 -1
  109. package/lib/projection/ui/ProjectionPanel.d.ts +1 -1
  110. package/lib/projection/ui/ProjectionPanel.d.ts.map +1 -1
  111. package/lib/projection/ui/ProjectionPanel.js +1 -1
  112. package/lib/projection/ui/ProjectionPanel.js.map +1 -1
  113. package/lib/projection/ui/ProjectionReadout.d.ts +7 -1
  114. package/lib/projection/ui/ProjectionReadout.d.ts.map +1 -1
  115. package/lib/projection/ui/ProjectionReadout.js +19 -7
  116. package/lib/projection/ui/ProjectionReadout.js.map +1 -1
  117. package/lib/projection/ui/ProjectionSpaceControls.d.ts +3 -5
  118. package/lib/projection/ui/ProjectionSpaceControls.d.ts.map +1 -1
  119. package/lib/projection/ui/ProjectionSpaceControls.js +9 -7
  120. package/lib/projection/ui/ProjectionSpaceControls.js.map +1 -1
  121. package/lib/projection/ui/ProjectionSpaceMore.d.ts +2 -2
  122. package/lib/projection/ui/ProjectionSpaceMore.d.ts.map +1 -1
  123. package/lib/projection/ui/ProjectionSpaceMore.js +5 -4
  124. package/lib/projection/ui/ProjectionSpaceMore.js.map +1 -1
  125. package/lib/projection/ui/ProjectionSpaceTab.d.ts +11 -3
  126. package/lib/projection/ui/ProjectionSpaceTab.d.ts.map +1 -1
  127. package/lib/projection/ui/ProjectionSpaceTab.js +7 -4
  128. package/lib/projection/ui/ProjectionSpaceTab.js.map +1 -1
  129. package/lib/projection/ui/ProjectionViewer.d.ts.map +1 -1
  130. package/lib/projection/ui/ProjectionViewer.js +1 -0
  131. package/lib/projection/ui/ProjectionViewer.js.map +1 -1
  132. package/lib/projection/ui/projectionBarReadings.d.ts +4 -1
  133. package/lib/projection/ui/projectionBarReadings.d.ts.map +1 -1
  134. package/lib/projection/ui/projectionBarReadings.js +14 -4
  135. package/lib/projection/ui/projectionBarReadings.js.map +1 -1
  136. package/lib/projection/ui/projectionBarSlots.d.ts +4 -2
  137. package/lib/projection/ui/projectionBarSlots.d.ts.map +1 -1
  138. package/lib/projection/ui/projectionBarSlots.js +3 -7
  139. package/lib/projection/ui/projectionBarSlots.js.map +1 -1
  140. package/lib/projection/ui/projectionMapChoices.d.ts +31 -6
  141. package/lib/projection/ui/projectionMapChoices.d.ts.map +1 -1
  142. package/lib/projection/ui/projectionMapChoices.js +58 -9
  143. package/lib/projection/ui/projectionMapChoices.js.map +1 -1
  144. package/lib/projection/ui/projectionMapChrome.d.ts +3 -9
  145. package/lib/projection/ui/projectionMapChrome.d.ts.map +1 -1
  146. package/lib/projection/ui/projectionMapChrome.js +41 -27
  147. package/lib/projection/ui/projectionMapChrome.js.map +1 -1
  148. package/lib/projection/ui/projectionMapCorner.d.ts +25 -0
  149. package/lib/projection/ui/projectionMapCorner.d.ts.map +1 -0
  150. package/lib/projection/ui/projectionMapCorner.js +46 -0
  151. package/lib/projection/ui/projectionMapCorner.js.map +1 -0
  152. package/lib/projection/ui/projectionMapModel.d.ts +9 -18
  153. package/lib/projection/ui/projectionMapModel.d.ts.map +1 -1
  154. package/lib/projection/ui/projectionMapModel.js +14 -39
  155. package/lib/projection/ui/projectionMapModel.js.map +1 -1
  156. package/lib/projection/ui/projectionMapView.d.ts +3 -1
  157. package/lib/projection/ui/projectionMapView.d.ts.map +1 -1
  158. package/lib/projection/ui/projectionMapView.js +9 -4
  159. package/lib/projection/ui/projectionMapView.js.map +1 -1
  160. package/lib/projection/ui/projectionSelectionSentence.d.ts +10 -0
  161. package/lib/projection/ui/projectionSelectionSentence.d.ts.map +1 -0
  162. package/lib/projection/ui/projectionSelectionSentence.js +18 -0
  163. package/lib/projection/ui/projectionSelectionSentence.js.map +1 -0
  164. package/lib/projection/ui/projectionStateApi.d.ts +47 -0
  165. package/lib/projection/ui/projectionStateApi.d.ts.map +1 -0
  166. package/lib/projection/ui/projectionStateApi.js +9 -0
  167. package/lib/projection/ui/projectionStateApi.js.map +1 -0
  168. package/lib/projection/ui/projectionTabModels.js +1 -1
  169. package/lib/projection/ui/projectionTabModels.js.map +1 -1
  170. package/lib/projection/ui/useProjectionState.d.ts +1 -29
  171. package/lib/projection/ui/useProjectionState.d.ts.map +1 -1
  172. package/lib/projection/ui/useProjectionState.js +13 -5
  173. package/lib/projection/ui/useProjectionState.js.map +1 -1
  174. package/lib/scatter/core/index.d.ts +1 -0
  175. package/lib/scatter/core/index.d.ts.map +1 -1
  176. package/lib/scatter/core/index.js +1 -0
  177. package/lib/scatter/core/index.js.map +1 -1
  178. package/lib/scatter/core/pointShape.d.ts +18 -0
  179. package/lib/scatter/core/pointShape.d.ts.map +1 -0
  180. package/lib/scatter/core/pointShape.js +70 -0
  181. package/lib/scatter/core/pointShape.js.map +1 -0
  182. package/lib/scatter/ui/ScatterPlot.d.ts.map +1 -1
  183. package/lib/scatter/ui/ScatterPlot.js +3 -2
  184. package/lib/scatter/ui/ScatterPlot.js.map +1 -1
  185. package/lib/scatter/ui/ScatterPointLayer.d.ts +14 -1
  186. package/lib/scatter/ui/ScatterPointLayer.d.ts.map +1 -1
  187. package/lib/scatter/ui/ScatterPointLayer.js +15 -3
  188. package/lib/scatter/ui/ScatterPointLayer.js.map +1 -1
  189. package/lib/scatter/ui/scatterFigureProps.d.ts +13 -0
  190. package/lib/scatter/ui/scatterFigureProps.d.ts.map +1 -1
  191. package/lib/scatter/ui/scatterMarkGlyph.d.ts +2 -1
  192. package/lib/scatter/ui/scatterMarkGlyph.d.ts.map +1 -1
  193. package/lib/scatter/ui/scatterMarkGlyph.js +7 -1
  194. package/lib/scatter/ui/scatterMarkGlyph.js.map +1 -1
  195. package/lib/scatter3d/ui/ScatterCloud.d.ts.map +1 -1
  196. package/lib/scatter3d/ui/ScatterCloud.js +2 -1
  197. package/lib/scatter3d/ui/ScatterCloud.js.map +1 -1
  198. package/package.json +1 -1
  199. package/src/build/vite/gitHead.ts +17 -2
  200. package/src/clipboard/ui/ClickToCopy.tsx +190 -0
  201. package/src/clipboard/ui/CopyButton.tsx +2 -2
  202. package/src/clipboard/ui/CopyableValue.tsx +11 -6
  203. package/src/clipboard/ui/index.ts +2 -0
  204. package/src/ecosystem/core/groups.ts +10 -3
  205. package/src/ecosystem/core/sites.ts +39 -0
  206. package/src/ecosystem/core/types.ts +4 -1
  207. package/src/ecosystem/ui/glyphs.tsx +51 -0
  208. package/src/error/ui/ErrorFallback.tsx +3 -1
  209. package/src/overlay/core/index.ts +6 -2
  210. package/src/overlay/core/overlayMarks.ts +51 -6
  211. package/src/overlay/ui/OverlayLegendMark.tsx +5 -0
  212. package/src/pedagogy/ui/ReferenceSectionBlock.tsx +10 -1
  213. package/src/projection/core/index.ts +11 -3
  214. package/src/projection/core/projectionCopy.ts +3 -0
  215. package/src/projection/core/projectionGroupings.ts +174 -0
  216. package/src/projection/core/projectionOptions.ts +18 -7
  217. package/src/projection/core/projectionPanelWords.ts +10 -0
  218. package/src/projection/core/projectionSamples.ts +62 -120
  219. package/src/projection/core/projectionStrings.ts +12 -1
  220. package/src/projection/core/resolveProjectionOptions.ts +66 -4
  221. package/src/projection/ui/ProjectionBar.tsx +4 -2
  222. package/src/projection/ui/ProjectionGroupingPickers.tsx +91 -0
  223. package/src/projection/ui/ProjectionMapControls.tsx +29 -32
  224. package/src/projection/ui/ProjectionMapLegend.tsx +1 -1
  225. package/src/projection/ui/ProjectionMapMore.tsx +14 -4
  226. package/src/projection/ui/ProjectionMapTab.tsx +27 -9
  227. package/src/projection/ui/ProjectionPairsMore.tsx +12 -21
  228. package/src/projection/ui/ProjectionPairsTab.tsx +5 -2
  229. package/src/projection/ui/ProjectionPanel.tsx +7 -3
  230. package/src/projection/ui/ProjectionReadout.tsx +28 -8
  231. package/src/projection/ui/ProjectionSpaceControls.tsx +20 -27
  232. package/src/projection/ui/ProjectionSpaceMore.tsx +12 -3
  233. package/src/projection/ui/ProjectionSpaceTab.tsx +23 -6
  234. package/src/projection/ui/ProjectionViewer.tsx +1 -0
  235. package/src/projection/ui/projectionBarReadings.ts +24 -8
  236. package/src/projection/ui/projectionBarSlots.tsx +10 -9
  237. package/src/projection/ui/projectionMapChoices.ts +71 -14
  238. package/src/projection/ui/projectionMapChrome.ts +55 -30
  239. package/src/projection/ui/projectionMapCorner.ts +67 -0
  240. package/src/projection/ui/projectionMapModel.ts +20 -59
  241. package/src/projection/ui/projectionMapView.ts +15 -8
  242. package/src/projection/ui/projectionSelectionSentence.ts +22 -0
  243. package/src/projection/ui/projectionStateApi.ts +57 -0
  244. package/src/projection/ui/projectionTabModels.ts +1 -1
  245. package/src/projection/ui/useProjectionState.ts +33 -37
  246. package/src/scatter/core/index.ts +1 -0
  247. package/src/scatter/core/pointShape.ts +80 -0
  248. package/src/scatter/ui/ScatterPlot.tsx +4 -1
  249. package/src/scatter/ui/ScatterPointLayer.tsx +39 -11
  250. package/src/scatter/ui/scatterFigureProps.ts +13 -0
  251. package/src/scatter/ui/scatterMarkGlyph.tsx +9 -1
  252. package/src/scatter3d/ui/ScatterCloud.tsx +3 -0
  253. package/styles/chrome.css +163 -0
@@ -8,9 +8,11 @@ import { OverlaySegmented } from '../../overlay/ui/OverlaySegmented.tsx';
8
8
  import { OverlaySelect } from '../../overlay/ui/OverlaySelect.tsx';
9
9
  import { OverlayToggle } from '../../overlay/ui/OverlayToggle.tsx';
10
10
  import type { ScatterSelectionMode } from '../../scatter/core/scatterSelection.ts';
11
+ import { NO_GROUPING } from '../core/projectionOptions.ts';
11
12
  import type { ProjectionResult } from '../core/projectionResult.ts';
12
13
  import { PROJECTION_TAB_DEFAULTS } from '../core/projectionTabDefaults.ts';
13
14
 
15
+ import { ProjectionShapePicker } from './ProjectionGroupingPickers.tsx';
14
16
  import type { ProjectionMapControlsProps } from './ProjectionMapControls.tsx';
15
17
  import { projectionAxisChoices } from './projectionAxisChoices.ts';
16
18
  import { projectionSelectModeChoices } from './projectionWordChoices.ts';
@@ -34,8 +36,8 @@ interface ProjectionMapMoreProps extends ProjectionMapControlsProps {
34
36
  }
35
37
 
36
38
  /**
37
- * What the map keeps behind the cog: the axes, the dot size, what a drag does,
38
- * and the three buttons that act rather than change.
39
+ * What the map keeps behind the cog: the axes, the dot size and shape, what a
40
+ * drag does, and the three buttons that act rather than change.
39
41
  *
40
42
  * Five settings is where a flat list stops being scannable, so they are filed
41
43
  * under three headings — the two directions of the picture, the marks drawn in
@@ -54,13 +56,13 @@ interface ProjectionMapMoreProps extends ProjectionMapControlsProps {
54
56
  * @returns The panel behind the cog.
55
57
  */
56
58
  export function ProjectionMapMore(props: ProjectionMapMoreProps): ReactElement {
57
- const { result, options, onChange, copy, hasGroups = false } = props;
59
+ const { result, options, onChange, copy, groupings } = props;
58
60
  const { selectedCount = 0, onZoomToSelection } = props;
59
61
  const { onResetView, onClearSelection } = props;
60
62
  const { action, help, panel, reason, tab } = copy;
61
63
 
62
64
  const nothingPicked = selectedCount === 0;
63
- const uncoloured = !hasGroups || options.colorBy === 'none';
65
+ const uncoloured = groupings.length === 0 || options.colorBy === NO_GROUPING;
64
66
 
65
67
  return (
66
68
  <OverlayPanel
@@ -124,6 +126,14 @@ export function ProjectionMapMore(props: ProjectionMapMoreProps): ReactElement {
124
126
  unit="px"
125
127
  onChange={(pointRadius) => onChange({ pointRadius })}
126
128
  />
129
+ <ProjectionShapePicker
130
+ label={panel.name.shapeBy}
131
+ options={options}
132
+ onChange={onChange}
133
+ copy={copy}
134
+ groupings={groupings}
135
+ rows={result.scores.rows}
136
+ />
127
137
  <OverlayToggle
128
138
  label={panel.name.showGroupMeans}
129
139
  help={help.showGroupMeans}
@@ -7,11 +7,15 @@ import type { ScatterPointOpen } from '../../scatter/ui/scatterFigureProps.ts';
7
7
  import type { SelectionChange } from '../../scatter/ui/useScatterSelection.ts';
8
8
  import type { ProjectionCopy } from '../core/projectionCopy.ts';
9
9
  import type { ProjectionOptions } from '../core/projectionOptions.ts';
10
+ import { NO_GROUPING } from '../core/projectionOptions.ts';
10
11
  import type {
11
12
  ProjectionMarker,
12
13
  ProjectionResult,
13
14
  } from '../core/projectionResult.ts';
14
- import type { ResolvedProjectionGroups } from '../core/projectionSamples.ts';
15
+ import type {
16
+ ResolvedProjectionGroups,
17
+ ResolvedProjectionShapes,
18
+ } from '../core/projectionSamples.ts';
15
19
 
16
20
  import type { SelectionReport } from './ProjectionMapCaption.tsx';
17
21
  import { ProjectionMapCaption } from './ProjectionMapCaption.tsx';
@@ -25,14 +29,23 @@ import {
25
29
  projectionMapDomain,
26
30
  projectionMapMarkers,
27
31
  projectionScatterGroups,
32
+ projectionScatterShapes,
28
33
  } from './projectionMapModel.ts';
29
34
 
30
35
  /** What {@link ProjectionMapTab} needs. */
31
36
  interface ProjectionMapTabProps {
32
37
  /** What the run produced, whatever produced it. */
33
38
  result: ProjectionResult;
34
- /** The groups, resolved once by the viewer so every tab colours alike. */
39
+ /**
40
+ * The grouping the dots are coloured by, resolved once by the viewer so every
41
+ * tab colours alike; empty while the colour stands for nothing.
42
+ */
35
43
  groups: ResolvedProjectionGroups;
44
+ /**
45
+ * The grouping the dots are shaped by, resolved once by the viewer.
46
+ * @default null — every dot is a disc
47
+ */
48
+ shapes?: ResolvedProjectionShapes | null;
36
49
  /** What the map is showing, already resolved against `result`. */
37
50
  options: ProjectionOptions;
38
51
  /** The words the map writes, already merged over the defaults. */
@@ -44,7 +57,7 @@ interface ProjectionMapTabProps {
44
57
  * "Sample ID" setting to write beside its dot.
45
58
  * @default undefined — the setting has nothing to write and draws nothing
46
59
  */
47
- ids?: readonly string[];
60
+ names?: readonly string[];
48
61
  /** Width of the figure, in pixels. */
49
62
  width: number;
50
63
  /** Height of the figure, in pixels. */
@@ -110,9 +123,10 @@ interface ProjectionMapTabProps {
110
123
  * The map: one dot per sample, an outline per group, and a key floating in the
111
124
  * emptiest corner of the plot.
112
125
  *
113
- * Colour stands for the group and for nothing else, and a selected dot keeps
114
- * its colour and gains a ring — recolouring it would trade the answer the
115
- * reader came for against the question they have just asked. A sample the
126
+ * Colour stands for one grouping and shape, when there is a second, for that
127
+ * one, and a selected dot keeps both and gains a ring — recolouring it would
128
+ * trade the answer the reader came for against the question they have just
129
+ * asked. A sample the
116
130
  * model was built from is drawn filled and one placed into the finished model
117
131
  * afterwards is drawn hollow, because the second kind can land anywhere and
118
132
  * its landing far out is the finding rather than a fault.
@@ -125,7 +139,8 @@ interface ProjectionMapTabProps {
125
139
  */
126
140
  export function ProjectionMapTab(props: ProjectionMapTabProps): ReactElement {
127
141
  const { result, groups, options, copy, chrome, width, height } = props;
128
- const { ids, selected, viewport = null, report = null } = props;
142
+ const { names, shapes = null, selected } = props;
143
+ const { viewport = null, report = null } = props;
129
144
  const { onSelectionChange, onHoverChange, hoverCard } = props;
130
145
  const { onPointDoubleClick } = props;
131
146
  const { onViewportChange, wheelZoom, touchLasso, testId } = props;
@@ -147,7 +162,8 @@ export function ProjectionMapTab(props: ProjectionMapTabProps): ReactElement {
147
162
  [scores, yAxis],
148
163
  );
149
164
 
150
- const colored = colorBy === 'group' && groups.entries.length > 0;
165
+ const shapeList = useMemo(() => projectionScatterShapes(shapes), [shapes]);
166
+ const colored = colorBy !== NO_GROUPING && groups.entries.length > 0;
151
167
  const ellipse = colored ? outlines : null;
152
168
 
153
169
  return (
@@ -160,11 +176,13 @@ export function ProjectionMapTab(props: ProjectionMapTabProps): ReactElement {
160
176
  yAxis={{ domain: yDomain, label: projectionAxisTitle(result, yAxis) }}
161
177
  groupOf={colored ? groups.groupOf : undefined}
162
178
  groups={colored ? projectionScatterGroups(groups) : undefined}
179
+ shapeOf={shapes?.shapeOf}
180
+ shapes={shapeList}
163
181
  ellipse={ellipse}
164
182
  ellipseMinimumPoints={MINIMUM_OUTLINE_POINTS}
165
183
  showGroupMeans={colored && showGroupMeans}
166
184
  showGroupLabels={colored && showGroupLabels}
167
- pointLabels={showIds ? ids : undefined}
185
+ pointLabels={showIds ? names : undefined}
168
186
  markers={projectionMapMarkers(
169
187
  markers ?? NO_MARKERS,
170
188
  groups,
@@ -2,15 +2,12 @@ import type { ReactElement } from 'react';
2
2
 
3
3
  import { OverlayNumber } from '../../overlay/ui/OverlayNumber.tsx';
4
4
  import { OverlayPanel } from '../../overlay/ui/OverlayPanel.tsx';
5
- import { OverlaySegmented } from '../../overlay/ui/OverlaySegmented.tsx';
6
5
  import type { ProjectionCopy } from '../core/projectionCopy.ts';
7
- import type {
8
- ProjectionColorBy,
9
- ProjectionOptions,
10
- } from '../core/projectionOptions.ts';
6
+ import type { ProjectionOptions } from '../core/projectionOptions.ts';
7
+ import type { ProjectionGrouping } from '../core/projectionSamples.ts';
11
8
  import { PROJECTION_TAB_DEFAULTS } from '../core/projectionTabDefaults.ts';
12
9
 
13
- import { projectionColourChoices } from './projectionMapChoices.ts';
10
+ import { ProjectionColourPicker } from './ProjectionGroupingPickers.tsx';
14
11
  import { projectionPairsCeiling } from './projectionPairsCeiling.ts';
15
12
 
16
13
  /** What the grid keeps behind the cog is drawn from. */
@@ -21,17 +18,12 @@ interface ProjectionPairsMoreProps {
21
18
  onChange: (patch: Partial<ProjectionOptions>) => void;
22
19
  /** The words the bar writes, already merged over the defaults. */
23
20
  copy: ProjectionCopy;
24
- /** What the set of groups is called, which is what `Colour by` offers. */
25
- groupLabel: string;
21
+ /** Every grouping the samples carry, which is what `Colour by` offers. */
22
+ groupings: readonly ProjectionGrouping[];
26
23
  /** How many components the run produced, which caps the grid. */
27
24
  axisCount: number;
28
25
  /** Width of the figure, in pixels, which caps it again. */
29
26
  width: number;
30
- /**
31
- * Whether the samples carry groups at all.
32
- * @default false
33
- */
34
- hasGroups?: boolean;
35
27
  }
36
28
 
37
29
  /**
@@ -52,9 +44,9 @@ interface ProjectionPairsMoreProps {
52
44
  export function ProjectionPairsMore(
53
45
  props: ProjectionPairsMoreProps,
54
46
  ): ReactElement {
55
- const { options, onChange, copy, groupLabel, hasGroups = false } = props;
47
+ const { options, onChange, copy, groupings } = props;
56
48
  const { axisCount, width } = props;
57
- const { bar, help, panel, tab } = copy;
49
+ const { help, panel, tab } = copy;
58
50
  const ceiling = projectionPairsCeiling(axisCount, width);
59
51
 
60
52
  return (
@@ -70,13 +62,12 @@ export function ProjectionPairsMore(
70
62
  max={ceiling.max}
71
63
  onChange={(pairCount) => onChange({ pairCount })}
72
64
  />
73
- <OverlaySegmented<ProjectionColorBy>
65
+ <ProjectionColourPicker
74
66
  label={panel.name.colorBy}
75
- help={help.colorBy}
76
- value={options.colorBy}
77
- disabled={!hasGroups}
78
- options={projectionColourChoices(groupLabel, bar.uncoloured)}
79
- onChange={(colorBy) => onChange({ colorBy })}
67
+ options={options}
68
+ onChange={onChange}
69
+ copy={copy}
70
+ groupings={groupings}
80
71
  />
81
72
  <OverlayNumber
82
73
  label={panel.name.pointRadius}
@@ -11,7 +11,10 @@ import { fillCopy } from '../core/fillCopy.ts';
11
11
  import type { ProjectionCopy } from '../core/projectionCopy.ts';
12
12
  import { PROJECTION_COPY } from '../core/projectionCopy.ts';
13
13
  import type { ProjectionOptions } from '../core/projectionOptions.ts';
14
- import { DEFAULT_PROJECTION_OPTIONS } from '../core/projectionOptions.ts';
14
+ import {
15
+ DEFAULT_PROJECTION_OPTIONS,
16
+ NO_GROUPING,
17
+ } from '../core/projectionOptions.ts';
15
18
  import type { ProjectionResult } from '../core/projectionResult.ts';
16
19
  import type { ResolvedProjectionGroups } from '../core/projectionSamples.ts';
17
20
 
@@ -96,7 +99,7 @@ export function ProjectionPairsTab(
96
99
  [groups],
97
100
  );
98
101
 
99
- const colored = options.colorBy === 'group' && inks.length > 0;
102
+ const colored = options.colorBy !== NO_GROUPING && inks.length > 0;
100
103
 
101
104
  return (
102
105
  <div style={{ minHeight: height }} data-testid={testId}>
@@ -13,10 +13,10 @@ import { ProjectionSharesTab } from './ProjectionSharesTab.tsx';
13
13
  import { ProjectionSpaceTab } from './ProjectionSpaceTab.tsx';
14
14
  import { ProjectionVariablesTab } from './ProjectionVariablesTab.tsx';
15
15
  import type { ProjectionMapView } from './projectionMapView.ts';
16
+ import type { ProjectionStateApi } from './projectionStateApi.ts';
16
17
  import type { ProjectionModels } from './projectionTabModels.ts';
17
18
  import type { ProjectionVariableTrack } from './projectionVariablesModel.ts';
18
19
  import type { ProjectionSampleOpen } from './projectionViewerProps.ts';
19
- import type { ProjectionStateApi } from './useProjectionState.ts';
20
20
 
21
21
  /** What {@link ProjectionPanel} needs. */
22
22
  interface ProjectionPanelProps {
@@ -172,7 +172,8 @@ export function ProjectionPanel(props: ProjectionPanelProps): ReactElement {
172
172
  options={state.options}
173
173
  copy={copy}
174
174
  chrome={map.spaceChrome}
175
- ids={samples.ids}
175
+ shapes={state.shapes}
176
+ names={samples.labels ?? samples.ids}
176
177
  width={width}
177
178
  height={height}
178
179
  selected={state.selected}
@@ -192,6 +193,7 @@ export function ProjectionPanel(props: ProjectionPanelProps): ReactElement {
192
193
  result={result}
193
194
  samples={samples}
194
195
  groups={state.groups}
196
+ colorBy={state.options.colorBy}
195
197
  xAxis={state.options.xAxis}
196
198
  yAxis={state.options.yAxis}
197
199
  zAxis={state.options.zAxis}
@@ -212,7 +214,8 @@ export function ProjectionPanel(props: ProjectionPanelProps): ReactElement {
212
214
  options={state.options}
213
215
  copy={copy}
214
216
  chrome={map.chrome}
215
- ids={samples.ids}
217
+ shapes={state.shapes}
218
+ names={samples.labels ?? samples.ids}
216
219
  viewport={map.viewport}
217
220
  onViewportChange={map.setViewport}
218
221
  wheelZoom={wheelZoom}
@@ -231,6 +234,7 @@ export function ProjectionPanel(props: ProjectionPanelProps): ReactElement {
231
234
  result={result}
232
235
  samples={samples}
233
236
  groups={state.groups}
237
+ colorBy={state.options.colorBy}
234
238
  xAxis={state.options.xAxis}
235
239
  yAxis={state.options.yAxis}
236
240
  x={anchor.x}
@@ -19,8 +19,14 @@ interface ProjectionReadoutProps {
19
19
  result: ProjectionResult;
20
20
  /** Who the rows are. */
21
21
  samples: ProjectionSamples;
22
- /** The groups as the figure draws them, for the row's group and its colour. */
22
+ /** The grouping the figure is coloured by, for the colour of the row's group. */
23
23
  groups: ResolvedProjectionGroups;
24
+ /**
25
+ * Which grouping colours the figure, so the row of that grouping carries the
26
+ * colour its dot is drawn in.
27
+ * @default undefined — no row carries a colour
28
+ */
29
+ colorBy?: string;
24
30
  /** Which axis is drawn horizontally, from 0. */
25
31
  xAxis: number;
26
32
  /** Which axis is drawn vertically. */
@@ -85,6 +91,7 @@ export function ProjectionReadout(props: ProjectionReadoutProps): ReactElement {
85
91
  result,
86
92
  samples,
87
93
  groups,
94
+ colorBy,
88
95
  xAxis,
89
96
  yAxis,
90
97
  zAxis,
@@ -104,8 +111,12 @@ export function ProjectionReadout(props: ProjectionReadoutProps): ReactElement {
104
111
  [xAxis, yAxis, zAxis],
105
112
  );
106
113
  const rows = useMemo(
107
- () => readoutRows(index, result, samples, groups, drawn, formatValue),
108
- [drawn, formatValue, groups, index, result, samples],
114
+ () =>
115
+ readoutRows(index, result, samples, drawn, formatValue, {
116
+ groups,
117
+ colorBy,
118
+ }),
119
+ [colorBy, drawn, formatValue, groups, index, result, samples],
109
120
  );
110
121
 
111
122
  return (
@@ -114,7 +125,7 @@ export function ProjectionReadout(props: ProjectionReadoutProps): ReactElement {
114
125
  y={y}
115
126
  boxWidth={boxWidth}
116
127
  boxHeight={boxHeight}
117
- title={samples.ids[index] ?? ''}
128
+ title={samples.labels?.[index] ?? samples.ids[index] ?? ''}
118
129
  rows={rows}
119
130
  maxRows={maxRows}
120
131
  pinned={pinned}
@@ -128,9 +139,9 @@ function readoutRows(
128
139
  index: number,
129
140
  result: ProjectionResult,
130
141
  samples: ProjectionSamples,
131
- groups: ResolvedProjectionGroups,
132
142
  drawn: readonly number[],
133
143
  formatValue: (value: number) => string,
144
+ coloured: { groups: ResolvedProjectionGroups; colorBy?: string },
134
145
  ): OverlayReadoutRow[] {
135
146
  const rows: OverlayReadoutRow[] = [];
136
147
  const { axes, scores } = result;
@@ -145,9 +156,18 @@ function readoutRows(
145
156
  });
146
157
  }
147
158
 
148
- const group = groups.entries[groups.groupOf[index] ?? -1];
149
- if (group !== undefined) {
150
- rows.push({ label: groups.label, value: group.label, color: group.color });
159
+ // Every grouping is written, the one the dots are coloured by with its
160
+ // colour: a card naming only the colour's group would hide the very
161
+ // grouping the shapes stand for.
162
+ const { groups, colorBy } = coloured;
163
+ for (const grouping of samples.groupings ?? []) {
164
+ const value = grouping.groups[index];
165
+ if (value === undefined) continue;
166
+ const group =
167
+ grouping.id === colorBy
168
+ ? groups.entries[groups.groupOf[index] ?? -1]
169
+ : undefined;
170
+ rows.push({ label: grouping.label, value, color: group?.color });
151
171
  }
152
172
 
153
173
  if (known && samples.fields !== undefined) {
@@ -8,11 +8,11 @@ import { OverlayToggle } from '../../overlay/ui/OverlayToggle.tsx';
8
8
  import { OverlayValueMenu } from '../../overlay/ui/OverlayValueMenu.tsx';
9
9
  import type { CloudGesture } from '../../scatter3d/core/cloudGesture.ts';
10
10
  import type { ProjectionCopy } from '../core/projectionCopy.ts';
11
- import type {
12
- ProjectionColorBy,
13
- ProjectionOptions,
14
- } from '../core/projectionOptions.ts';
11
+ import type { ProjectionOptions } from '../core/projectionOptions.ts';
12
+ import { NO_GROUPING } from '../core/projectionOptions.ts';
13
+ import type { ProjectionGrouping } from '../core/projectionSamples.ts';
15
14
 
15
+ import { ProjectionColourPicker } from './ProjectionGroupingPickers.tsx';
16
16
  import type { ProjectionReading } from './projectionBarReadings.ts';
17
17
  import { projectionChipSettings } from './projectionBarReadings.ts';
18
18
  import {
@@ -20,7 +20,6 @@ import {
20
20
  ellipseKey,
21
21
  ellipseSize,
22
22
  } from './projectionEllipse.ts';
23
- import { projectionColourChoices } from './projectionMapChoices.ts';
24
23
  import { projectionCloudGestureChoices } from './projectionWordChoices.ts';
25
24
 
26
25
  /** What the cloud's controls are drawn from, wherever in the bar they sit. */
@@ -31,14 +30,11 @@ export interface ProjectionSpaceControlsProps {
31
30
  onChange: (patch: Partial<ProjectionOptions>) => void;
32
31
  /** The words the bar writes, already merged over the defaults. */
33
32
  copy: ProjectionCopy;
34
- /** What the set of groups is called. */
35
- groupLabel: string;
36
33
  /**
37
- * Whether the samples carry groups at all. Without them the shells stand for
34
+ * Every grouping the samples carry. Without one the shells stand for
38
35
  * nothing, so their control is greyed rather than removed.
39
- * @default false
40
36
  */
41
- hasGroups?: boolean;
37
+ groupings: readonly ProjectionGrouping[];
42
38
  /**
43
39
  * What the settings currently read, built once for the bar and handed down.
44
40
  * @default [] — nothing is written, which is what the cog's panel wants
@@ -71,11 +67,12 @@ export interface ProjectionSpaceControlsProps {
71
67
  export function ProjectionSpaceControls(
72
68
  props: ProjectionSpaceControlsProps,
73
69
  ): ReactElement {
74
- const { options, onChange, copy, groupLabel, hasGroups = false } = props;
70
+ const { options, onChange, copy, groupings } = props;
75
71
  const { readings = NO_READINGS, tier = 'full' } = props;
76
72
  const { bar, help, outline, reason } = copy;
77
73
 
78
- const uncoloured = !hasGroups || options.colorBy === 'none';
74
+ const hasGroups = groupings.length > 0;
75
+ const uncoloured = !hasGroups || options.colorBy === NO_GROUPING;
79
76
  const names = (
80
77
  <>
81
78
  <OverlayToggle
@@ -109,8 +106,7 @@ export function ProjectionSpaceControls(
109
106
  options={options}
110
107
  onChange={onChange}
111
108
  copy={copy}
112
- groupLabel={groupLabel}
113
- hasGroups={hasGroups}
109
+ groupings={groupings}
114
110
  />
115
111
  </OverlayChip>
116
112
  {names}
@@ -151,10 +147,8 @@ interface SpaceSettingsProps {
151
147
  onChange: (patch: Partial<ProjectionOptions>) => void;
152
148
  /** The words the panel writes. */
153
149
  copy: ProjectionCopy;
154
- /** What the set of groups is called, which is what `Colour by` offers. */
155
- groupLabel: string;
156
- /** Whether the samples carry groups at all. */
157
- hasGroups: boolean;
150
+ /** Every grouping the samples carry. */
151
+ groupings: readonly ProjectionGrouping[];
158
152
  }
159
153
 
160
154
  /**
@@ -163,8 +157,8 @@ interface SpaceSettingsProps {
163
157
  * @returns The captioned controls.
164
158
  */
165
159
  function SpaceSettings(props: SpaceSettingsProps): ReactElement {
166
- const { options, onChange, copy, groupLabel, hasGroups } = props;
167
- const { bar, help, outline } = copy;
160
+ const { options, onChange, copy, groupings } = props;
161
+ const { help, outline } = copy;
168
162
 
169
163
  return (
170
164
  <>
@@ -175,19 +169,18 @@ function SpaceSettings(props: SpaceSettingsProps): ReactElement {
175
169
  options={projectionCloudGestureChoices(copy)}
176
170
  onChange={(cloudGesture) => onChange({ cloudGesture })}
177
171
  />
178
- <OverlaySegmented<ProjectionColorBy>
172
+ <ProjectionColourPicker
179
173
  label={help.colorBy.title}
180
- help={help.colorBy}
181
- value={options.colorBy}
182
- disabled={!hasGroups}
183
- options={projectionColourChoices(groupLabel, bar.uncoloured)}
184
- onChange={(colorBy) => onChange({ colorBy })}
174
+ options={options}
175
+ onChange={onChange}
176
+ copy={copy}
177
+ groupings={groupings}
185
178
  />
186
179
  <OverlaySelect
187
180
  label={help.ellipse.title}
188
181
  help={help.ellipse}
189
182
  value={ellipseKey(options.ellipse)}
190
- disabled={!hasGroups || options.colorBy === 'none'}
183
+ disabled={groupings.length === 0 || options.colorBy === NO_GROUPING}
191
184
  options={ellipseChoices(options.ellipse, outline, 'space')}
192
185
  onChange={(value) => onChange({ ellipse: ellipseSize(value) })}
193
186
  />
@@ -11,6 +11,7 @@ import type { CloudGesture } from '../../scatter3d/core/cloudGesture.ts';
11
11
  import type { ProjectionResult } from '../core/projectionResult.ts';
12
12
  import { PROJECTION_TAB_DEFAULTS } from '../core/projectionTabDefaults.ts';
13
13
 
14
+ import { ProjectionShapePicker } from './ProjectionGroupingPickers.tsx';
14
15
  import type { ProjectionSpaceControlsProps } from './ProjectionSpaceControls.tsx';
15
16
  import { projectionAxisChoices } from './projectionAxisChoices.ts';
16
17
  import {
@@ -35,8 +36,8 @@ interface ProjectionSpaceMoreProps extends ProjectionSpaceControlsProps {
35
36
  }
36
37
 
37
38
  /**
38
- * What the cloud keeps behind the cog: its three axes, the dot size, and what
39
- * the two drags do.
39
+ * What the cloud keeps behind the cog: its three axes, the dot size and shape,
40
+ * and what the two drags do.
40
41
  *
41
42
  * It is the map's panel with one picker more and one action fewer. There is no
42
43
  * "zoom to selection" here, because a box has no frame to draw round a crowd:
@@ -49,7 +50,7 @@ interface ProjectionSpaceMoreProps extends ProjectionSpaceControlsProps {
49
50
  export function ProjectionSpaceMore(
50
51
  props: ProjectionSpaceMoreProps,
51
52
  ): ReactElement {
52
- const { result, options, onChange, copy } = props;
53
+ const { result, options, onChange, copy, groupings } = props;
53
54
  const { selectedCount = 0, onResetView, onClearSelection } = props;
54
55
  const { action, help, panel, reason, tab } = copy;
55
56
 
@@ -122,6 +123,14 @@ export function ProjectionSpaceMore(
122
123
  unit="px"
123
124
  onChange={(pointRadius) => onChange({ pointRadius })}
124
125
  />
126
+ <ProjectionShapePicker
127
+ label={panel.name.shapeBy}
128
+ options={options}
129
+ onChange={onChange}
130
+ copy={copy}
131
+ groupings={groupings}
132
+ rows={result.scores.rows}
133
+ />
125
134
  </OverlayGroup>
126
135
  <OverlayGroup label={panel.section.handling}>
127
136
  <OverlaySegmented<CloudGesture>
@@ -7,8 +7,12 @@ import type { OrbitCamera } from '../../scatter3d/core/orbitCamera.ts';
7
7
  import { ScatterCloud } from '../../scatter3d/ui/ScatterCloud.tsx';
8
8
  import type { ProjectionCopy } from '../core/projectionCopy.ts';
9
9
  import type { ProjectionOptions } from '../core/projectionOptions.ts';
10
+ import { NO_GROUPING } from '../core/projectionOptions.ts';
10
11
  import type { ProjectionResult } from '../core/projectionResult.ts';
11
- import type { ResolvedProjectionGroups } from '../core/projectionSamples.ts';
12
+ import type {
13
+ ResolvedProjectionGroups,
14
+ ResolvedProjectionShapes,
15
+ } from '../core/projectionSamples.ts';
12
16
 
13
17
  import type { SelectionReport } from './ProjectionMapCaption.tsx';
14
18
  import { ProjectionMapCaption } from './ProjectionMapCaption.tsx';
@@ -19,6 +23,7 @@ import {
19
23
  projectionAxisTitle,
20
24
  projectionMapCloud,
21
25
  projectionScatterGroups,
26
+ projectionScatterShapes,
22
27
  } from './projectionMapModel.ts';
23
28
  import { MINIMUM_SHELL_POINTS } from './projectionSpaceModel.ts';
24
29
 
@@ -26,8 +31,16 @@ import { MINIMUM_SHELL_POINTS } from './projectionSpaceModel.ts';
26
31
  interface ProjectionSpaceTabProps {
27
32
  /** What the run produced, whatever produced it. */
28
33
  result: ProjectionResult;
29
- /** The groups, resolved once by the viewer so every tab colours alike. */
34
+ /**
35
+ * The grouping the dots are coloured by, resolved once by the viewer so every
36
+ * tab colours alike; empty while the colour stands for nothing.
37
+ */
30
38
  groups: ResolvedProjectionGroups;
39
+ /**
40
+ * The grouping the dots are shaped by, resolved once by the viewer.
41
+ * @default null — every dot is a disc
42
+ */
43
+ shapes?: ResolvedProjectionShapes | null;
31
44
  /** What the cloud is showing, already resolved against `result`. */
32
45
  options: ProjectionOptions;
33
46
  /** The words the cloud writes, already merged over the defaults. */
@@ -38,7 +51,7 @@ interface ProjectionSpaceTabProps {
38
51
  * What each sample is called, in the score matrix's row order.
39
52
  * @default undefined — the setting has nothing to write and draws nothing
40
53
  */
41
- ids?: readonly string[];
54
+ names?: readonly string[];
42
55
  /** Width of the figure, in pixels. */
43
56
  width: number;
44
57
  /** Height of the figure, in pixels. */
@@ -113,7 +126,8 @@ export function ProjectionSpaceTab(
113
126
  props: ProjectionSpaceTabProps,
114
127
  ): ReactElement {
115
128
  const { result, groups, options, copy, chrome, width, height } = props;
116
- const { ids, selected, report = null, camera, zoom } = props;
129
+ const { names, shapes = null, selected } = props;
130
+ const { report = null, camera, zoom } = props;
117
131
  const { onCameraChange, onZoomChange, wheelZoom } = props;
118
132
  const { onSelectionChange, onHoverChange, hoverCard, testId } = props;
119
133
  const { onPointDoubleClick } = props;
@@ -131,7 +145,8 @@ export function ProjectionSpaceTab(
131
145
  [scores, zAxis],
132
146
  );
133
147
 
134
- const colored = colorBy === 'group' && groups.entries.length > 0;
148
+ const shapeList = useMemo(() => projectionScatterShapes(shapes), [shapes]);
149
+ const colored = colorBy !== NO_GROUPING && groups.entries.length > 0;
135
150
 
136
151
  return (
137
152
  <div style={{ position: 'relative' }} data-testid={testId}>
@@ -146,10 +161,12 @@ export function ProjectionSpaceTab(
146
161
  zLabel={axisName(result, zAxis)}
147
162
  groupOf={colored ? groups.groupOf : undefined}
148
163
  groups={colored ? projectionScatterGroups(groups) : undefined}
164
+ shapeOf={shapes?.shapeOf}
165
+ shapes={shapeList}
149
166
  ellipsoid={colored ? shells : null}
150
167
  ellipsoidMinimumPoints={MINIMUM_SHELL_POINTS}
151
168
  showGroupLabels={colored && showGroupLabels}
152
- pointLabels={showIds ? ids : undefined}
169
+ pointLabels={showIds ? names : undefined}
153
170
  pointRadius={pointRadius}
154
171
  outlinedFrom={fittedCount}
155
172
  selected={selected}
@@ -64,6 +64,7 @@ export function ProjectionViewer(props: ProjectionViewerProps): ReactElement {
64
64
  result,
65
65
  copy,
66
66
  groups: state.groups,
67
+ shapes: state.shapes,
67
68
  options: state.options,
68
69
  selected: state.selected,
69
70
  onSettle: state.settleSelection,