@vitessce/scatterplot-embedding 4.0.10 → 4.1.1
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/dist/{deflate-CHWmGKzs.js → deflate-C-lrlL8q.js} +1 -1
- package/dist/{index-DoXbfOn2.js → index-DT4WpL6f.js} +8191 -4250
- package/dist/index.js +1 -1
- package/dist/{jpeg-DfpY4ESw.js → jpeg-BhxJy6Ji.js} +1 -1
- package/dist/{lerc-zU6xDjk2.js → lerc-BTOEb3Od.js} +1 -1
- package/dist/{lzw-qjsUiTV8.js → lzw-CTz-2vus.js} +1 -1
- package/dist/{packbits-DpcvKlj_.js → packbits-Da4S_mTv.js} +1 -1
- package/dist/{raw-DWdrKuNt.js → raw-D7eQMEmN.js} +1 -1
- package/dist/{webimage-DP9dgaA0.js → webimage-DPam-3kr.js} +1 -1
- package/dist-tsc/DualEmbeddingScatterplotSubscriber.d.ts.map +1 -1
- package/dist-tsc/DualEmbeddingScatterplotSubscriber.js +2 -2
- package/dist-tsc/DualEmbeddingScatterplotSubscriber.js.map +1 -1
- package/dist-tsc/EmbeddingScatterplotSubscriber.d.ts +6 -2
- package/dist-tsc/EmbeddingScatterplotSubscriber.d.ts.map +1 -1
- package/dist-tsc/EmbeddingScatterplotSubscriber.js +34 -14
- package/dist-tsc/EmbeddingScatterplotSubscriber.js.map +1 -1
- package/dist-tsc/EmbeddingScatterplotSubscriber.test.d.ts +2 -0
- package/dist-tsc/EmbeddingScatterplotSubscriber.test.d.ts.map +1 -0
- package/dist-tsc/EmbeddingScatterplotSubscriber.test.js +103 -0
- package/dist-tsc/EmbeddingScatterplotSubscriber.test.js.map +1 -0
- package/package.json +8 -8
- package/src/DualEmbeddingScatterplotSubscriber.js +2 -0
- package/src/EmbeddingScatterplotSubscriber.js +52 -10
- package/src/EmbeddingScatterplotSubscriber.test.jsx +105 -0
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
useObsSetsData,
|
|
16
16
|
useAnnotationStoryData,
|
|
17
17
|
useAnnotationFrameCoordination,
|
|
18
|
+
useAnnotationEditingForView,
|
|
18
19
|
useFeatureSelection,
|
|
19
20
|
useObsFeatureMatrixIndices,
|
|
20
21
|
useFeatureLabelsData,
|
|
@@ -33,14 +34,19 @@ import {
|
|
|
33
34
|
setObsSelection, mergeObsSets, getCellSetPolygons, treeToColorIndicesArray,
|
|
34
35
|
colorIndicesFromCodes, getObsIndexMap, stratifyArrays,
|
|
35
36
|
} from '@vitessce/sets-utils';
|
|
36
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
pluralize as plur, commaNumber, aggregateFeatureArrays, getAnnotationFrameCoordinationValues,
|
|
39
|
+
} from '@vitessce/utils';
|
|
37
40
|
import {
|
|
38
41
|
Scatterplot, ScatterplotTooltipSubscriber, ScatterplotOptions,
|
|
39
42
|
getPointSizeDevicePixels,
|
|
40
43
|
getPointOpacity,
|
|
44
|
+
useAnnotationFrameTransition,
|
|
41
45
|
} from '@vitessce/scatterplot';
|
|
42
46
|
import { Legend } from '@vitessce/legend';
|
|
43
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
ViewType, CoordinationType, COMPONENT_COORDINATION_TYPES, ViewHelpMapping,
|
|
49
|
+
} from '@vitessce/constants-internal';
|
|
44
50
|
import { DEFAULT_CONTOUR_PERCENTILES } from './constants.js';
|
|
45
51
|
|
|
46
52
|
const DEFAULT_FEATURE_AGGREGATION_STRATEGY = 'first';
|
|
@@ -48,8 +54,11 @@ const DEFAULT_FEATURE_AGGREGATION_STRATEGY = 'first';
|
|
|
48
54
|
/**
|
|
49
55
|
* A subscriber component for the scatterplot.
|
|
50
56
|
* @param {object} props
|
|
51
|
-
* @param {number} props.uuid The unique identifier for this component
|
|
52
|
-
*
|
|
57
|
+
* @param {number} props.uuid The unique identifier for this component.
|
|
58
|
+
* @param {string} props.viewUid The uid of the corresponding view in the view config layout,
|
|
59
|
+
* used for looking up its coordination mapping via `useViewMapping`.
|
|
60
|
+
* Optional, defaults to `uuid`. Differs from `uuid` when rendered by a parent
|
|
61
|
+
* (e.g., the dual scatterplot) that renders multiple child scatterplots for one view.
|
|
53
62
|
* @param {string} props.theme The current theme name.
|
|
54
63
|
* @param {function} props.removeGridComponent The callback function to pass to TitleInfo,
|
|
55
64
|
* to call when the component has been removed from the grid.
|
|
@@ -60,6 +69,7 @@ const DEFAULT_FEATURE_AGGREGATION_STRATEGY = 'first';
|
|
|
60
69
|
export function EmbeddingScatterplotSubscriber(props) {
|
|
61
70
|
const {
|
|
62
71
|
uuid,
|
|
72
|
+
viewUid: viewUidFromProps,
|
|
63
73
|
closeButtonVisible,
|
|
64
74
|
downloadButtonVisible,
|
|
65
75
|
removeGridComponent,
|
|
@@ -73,8 +83,11 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
73
83
|
sampleSetSelection: sampleSetSelectionFromProps,
|
|
74
84
|
// Circle scale factor:
|
|
75
85
|
circleScaleFactor = 0.8,
|
|
86
|
+
// Passed down from the ancestor <Vitessce/> or <VitS/> component.
|
|
87
|
+
areAnnotationsEditable,
|
|
76
88
|
} = props;
|
|
77
89
|
|
|
90
|
+
const viewUid = viewUidFromProps ?? uuid;
|
|
78
91
|
const loaders = useLoaders();
|
|
79
92
|
// useViewMapping looks up this view's coordination mapping directly from the
|
|
80
93
|
// view config (by uuid), after accounting for meta-coordination, so there is
|
|
@@ -84,14 +97,14 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
84
97
|
const [
|
|
85
98
|
// eslint-disable-next-line no-unused-vars
|
|
86
99
|
coordinationScopes, _coordinationScopesBy, coordinationValues,
|
|
87
|
-
] = useViewMapping(
|
|
100
|
+
] = useViewMapping(viewUid);
|
|
88
101
|
const setComponentHover = useSetComponentHover();
|
|
89
102
|
const setComponentViewInfo = useSetComponentViewInfo(uuid);
|
|
90
103
|
|
|
91
104
|
// Merge the coordination values that the current annotation frame defines for this view
|
|
92
105
|
// into the coordination space, whenever the frame changes. The story itself is read-only.
|
|
93
106
|
// TODO: handle the rendering of alternative views/layouts (in vit-s?)
|
|
94
|
-
useAnnotationFrameCoordination(
|
|
107
|
+
useAnnotationFrameCoordination(viewUid, coordinationScopes, coordinationValues);
|
|
95
108
|
|
|
96
109
|
// Get "props" from the coordination space.
|
|
97
110
|
const [{
|
|
@@ -122,6 +135,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
122
135
|
featureValueColormap: geneExpressionColormap,
|
|
123
136
|
featureValueColormapRange: geneExpressionColormapRange,
|
|
124
137
|
tooltipsVisible,
|
|
138
|
+
tooltipObsSetsMode,
|
|
125
139
|
sampleSetSelection: sampleSetSelectionFromCoordination,
|
|
126
140
|
sampleSetColor,
|
|
127
141
|
embeddingPointsVisible,
|
|
@@ -137,6 +151,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
137
151
|
annotationSemanticZoom,
|
|
138
152
|
annotationTransitionDuration,
|
|
139
153
|
annotationEditable,
|
|
154
|
+
annotationFrameIndex,
|
|
140
155
|
}, {
|
|
141
156
|
setEmbeddingZoom: setZoom,
|
|
142
157
|
setEmbeddingTargetX: setTargetX,
|
|
@@ -167,7 +182,14 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
167
182
|
setAnnotationStory,
|
|
168
183
|
}] = useCoordination(
|
|
169
184
|
COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT], coordinationScopes,
|
|
170
|
-
coordinationValues,
|
|
185
|
+
coordinationValues, viewUid,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Props for drawing annotation shapes in this view (while authoring the story
|
|
189
|
+
// via the annotation controller), and for highlighting the selected shape.
|
|
190
|
+
const annotationEditingProps = useAnnotationEditingForView(
|
|
191
|
+
viewUid,
|
|
192
|
+
Boolean(areAnnotationsEditable && annotationEditable && typeof annotationFrameIndex === 'number'),
|
|
171
193
|
);
|
|
172
194
|
|
|
173
195
|
const {
|
|
@@ -329,6 +351,10 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
329
351
|
// cached results and cost nothing in that first commit.
|
|
330
352
|
const deferredCellSetSelection = useDeferredValue(cellSetSelection);
|
|
331
353
|
const deferredCellSetColor = useDeferredValue(cellSetColor);
|
|
354
|
+
// A lasso selection made while coloring by feature values also switches the
|
|
355
|
+
// encoding to set selection. Defer it with the selection and colors, so the
|
|
356
|
+
// points do not first show the previous selection's colors for every cell.
|
|
357
|
+
const deferredCellColorEncoding = useDeferredValue(cellColorEncoding);
|
|
332
358
|
const deferredSampleSetSelection = useDeferredValue(sampleSetSelection);
|
|
333
359
|
|
|
334
360
|
// Positional rather than keyed by observation ID: at atlas scale an ID-keyed color
|
|
@@ -439,6 +465,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
439
465
|
|
|
440
466
|
const getObsInfo = useGetObsInfo(
|
|
441
467
|
observationsLabel, obsLabelsTypes, obsLabelsData, obsSetsMembership,
|
|
468
|
+
cellSetSelection, tooltipObsSetsMode,
|
|
442
469
|
);
|
|
443
470
|
|
|
444
471
|
// With no set selection every observation counts as selected, matching the
|
|
@@ -591,6 +618,20 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
591
618
|
obsSetsColumns,
|
|
592
619
|
]);
|
|
593
620
|
|
|
621
|
+
// Animate the 2D zoom/target when they change due to the current annotation frame.
|
|
622
|
+
const annotationFrameCoordinationValues = getAnnotationFrameCoordinationValues(
|
|
623
|
+
annotationStory, annotationFrameIndex, viewUid,
|
|
624
|
+
);
|
|
625
|
+
const annotationTransitionProps = useAnnotationFrameTransition(
|
|
626
|
+
{ zoom, targetX, targetY },
|
|
627
|
+
{
|
|
628
|
+
zoom: annotationFrameCoordinationValues?.[CoordinationType.EMBEDDING_ZOOM],
|
|
629
|
+
targetX: annotationFrameCoordinationValues?.[CoordinationType.EMBEDDING_TARGET_X],
|
|
630
|
+
targetY: annotationFrameCoordinationValues?.[CoordinationType.EMBEDDING_TARGET_Y],
|
|
631
|
+
},
|
|
632
|
+
annotationTransitionDuration,
|
|
633
|
+
);
|
|
634
|
+
|
|
594
635
|
const setViewState = ({ zoom: newZoom, target }) => {
|
|
595
636
|
setZoom(newZoom);
|
|
596
637
|
setTargetX(target[0]);
|
|
@@ -661,7 +702,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
661
702
|
uuid={uuid}
|
|
662
703
|
onSelectionBusy={setIsSelectionPending}
|
|
663
704
|
theme={theme}
|
|
664
|
-
viewState={{ zoom, target: [targetX, targetY, targetZ] }}
|
|
705
|
+
viewState={{ zoom, target: [targetX, targetY, targetZ], ...annotationTransitionProps }}
|
|
665
706
|
setViewState={setViewState}
|
|
666
707
|
originalViewState={originalViewState}
|
|
667
708
|
obsEmbeddingIndex={obsEmbeddingIndex}
|
|
@@ -678,7 +719,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
678
719
|
setCellHighlight={setCellHighlight}
|
|
679
720
|
cellRadius={cellRadius}
|
|
680
721
|
cellOpacity={cellOpacity}
|
|
681
|
-
cellColorEncoding={
|
|
722
|
+
cellColorEncoding={deferredCellColorEncoding}
|
|
682
723
|
geneExpressionColormap={geneExpressionColormap}
|
|
683
724
|
geneExpressionColormapRange={geneExpressionColormapRange}
|
|
684
725
|
setComponentHover={() => {
|
|
@@ -711,6 +752,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
711
752
|
annotationSemanticZoom={annotationSemanticZoom}
|
|
712
753
|
annotationTransitionDuration={annotationTransitionDuration}
|
|
713
754
|
annotationEditable={annotationEditable}
|
|
755
|
+
{...annotationEditingProps}
|
|
714
756
|
/>
|
|
715
757
|
{tooltipsVisible && width && height ? (
|
|
716
758
|
<ScatterplotTooltipSubscriber
|
|
@@ -728,7 +770,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
728
770
|
theme={theme}
|
|
729
771
|
featureType={featureType}
|
|
730
772
|
featureValueType={featureValueType}
|
|
731
|
-
obsColorEncoding={
|
|
773
|
+
obsColorEncoding={deferredCellColorEncoding}
|
|
732
774
|
featureSelection={geneSelection}
|
|
733
775
|
featureLabelsMap={featureLabelsMap}
|
|
734
776
|
featureValueColormap={geneExpressionColormap}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { it, expect, vi } from 'vitest';
|
|
3
|
+
import { render, act } from '@testing-library/react';
|
|
4
|
+
import { Scatterplot } from '@vitessce/scatterplot';
|
|
5
|
+
import { EmbeddingScatterplotSubscriber } from './EmbeddingScatterplotSubscriber.js';
|
|
6
|
+
|
|
7
|
+
const state = vi.hoisted(() => ({
|
|
8
|
+
values: {}, setters: {}, index: [], embedding: null, sets: null,
|
|
9
|
+
}));
|
|
10
|
+
vi.mock('@vitessce/vit-s', () => ({
|
|
11
|
+
TitleInfo: ({ children }) => children,
|
|
12
|
+
useReady: () => true,
|
|
13
|
+
useUrls: () => [],
|
|
14
|
+
useDeckCanvasSize: () => [0, 0, null],
|
|
15
|
+
useUint8FeatureSelection: () => ({}),
|
|
16
|
+
useExpressionValueGetter: () => () => 0,
|
|
17
|
+
useGetObsInfo: () => () => ({}),
|
|
18
|
+
useObsEmbeddingData: () => [{ obsIndex: state.index, obsEmbedding: state.embedding }, 'success', [], null],
|
|
19
|
+
useObsSetsData: () => [{ obsSets: state.sets }, 'success', [], null],
|
|
20
|
+
useAnnotationStoryData: () => [{}, 'success', [], null],
|
|
21
|
+
useAnnotationFrameCoordination: () => {},
|
|
22
|
+
useAnnotationEditingForView: () => ({}),
|
|
23
|
+
useFeatureSelection: () => [null, [], 'success', []],
|
|
24
|
+
useObsFeatureMatrixIndices: () => [{ obsIndex: state.index }, 'success', [], null],
|
|
25
|
+
useFeatureLabelsData: () => [{}, 'success', [], null],
|
|
26
|
+
useMultiObsLabels: () => [[], [], [], [], []],
|
|
27
|
+
useSampleSetsData: () => [{}, 'success', [], null],
|
|
28
|
+
useSampleEdgesData: () => [{}, 'success', [], null],
|
|
29
|
+
useCoordination: () => [state.values, state.setters],
|
|
30
|
+
useLoaders: () => ({}),
|
|
31
|
+
useSetComponentHover: () => () => {},
|
|
32
|
+
useSetComponentViewInfo: () => () => {},
|
|
33
|
+
useInitialCoordination: () => ({ embeddingZoom: 0, embeddingTargetX: 0, embeddingTargetY: 0 }),
|
|
34
|
+
useExpandedFeatureLabelsMap: () => [null, 'success'],
|
|
35
|
+
useViewMapping: () => [{}, {}, {}],
|
|
36
|
+
}));
|
|
37
|
+
vi.mock('@vitessce/scatterplot', () => ({
|
|
38
|
+
Scatterplot: vi.fn(() => null),
|
|
39
|
+
ScatterplotTooltipSubscriber: () => null,
|
|
40
|
+
ScatterplotOptions: () => null,
|
|
41
|
+
getPointSizeDevicePixels: () => 1,
|
|
42
|
+
getPointOpacity: () => 1,
|
|
43
|
+
useAnnotationFrameTransition: () => ({}),
|
|
44
|
+
}));
|
|
45
|
+
vi.mock('@vitessce/legend', () => ({ Legend: () => null }));
|
|
46
|
+
|
|
47
|
+
it('switches from feature value coloring to a lasso selection without showing the previous selection', () => {
|
|
48
|
+
state.index = Array.from({ length: 1000 }, (_, i) => `cell-${i}`);
|
|
49
|
+
state.embedding = {
|
|
50
|
+
shape: [2, state.index.length],
|
|
51
|
+
data: [
|
|
52
|
+
Float32Array.from(state.index, (_, i) => i % 32),
|
|
53
|
+
Float32Array.from(state.index, (_, i) => Math.floor(i / 32)),
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
state.sets = {
|
|
57
|
+
version: '0.1.3',
|
|
58
|
+
tree: [{
|
|
59
|
+
name: 'Types',
|
|
60
|
+
children: [{ name: 'All', set: state.index.map(id => [id, null]) }],
|
|
61
|
+
}],
|
|
62
|
+
};
|
|
63
|
+
// Coloring by feature values, with a previous set selection still stored.
|
|
64
|
+
state.values = {
|
|
65
|
+
dataset: 'A',
|
|
66
|
+
obsType: 'cell',
|
|
67
|
+
embeddingType: 'UMAP',
|
|
68
|
+
embeddingZoom: 0,
|
|
69
|
+
embeddingTargetX: 0,
|
|
70
|
+
embeddingTargetY: 0,
|
|
71
|
+
embeddingPointsVisible: true,
|
|
72
|
+
embeddingContoursVisible: false,
|
|
73
|
+
obsSetSelection: [['Types', 'All']],
|
|
74
|
+
additionalObsSets: null,
|
|
75
|
+
obsSetColor: [{ path: ['Types', 'All'], color: [0, 0, 255] }],
|
|
76
|
+
obsColorEncoding: 'geneSelection',
|
|
77
|
+
};
|
|
78
|
+
state.setters = Object.fromEntries([
|
|
79
|
+
'AdditionalObsSets', 'ObsSetSelection', 'ObsSetColor', 'ObsColorEncoding',
|
|
80
|
+
].map(name => [`set${name}`, (value) => {
|
|
81
|
+
state.values = { ...state.values, [name[0].toLowerCase() + name.slice(1)]: value };
|
|
82
|
+
}]));
|
|
83
|
+
const props = { uuid: 'umap', theme: 'dark' };
|
|
84
|
+
const { rerender } = render(<EmbeddingScatterplotSubscriber {...props} />);
|
|
85
|
+
const initialColorIndices = Scatterplot.mock.lastCall[0].obsColorIndices;
|
|
86
|
+
const currentSelect = Scatterplot.mock.lastCall[0].setCellSelection;
|
|
87
|
+
Scatterplot.mockClear();
|
|
88
|
+
|
|
89
|
+
act(() => {
|
|
90
|
+
// The same callback the selection layer calls after its hit test.
|
|
91
|
+
currentSelect(state.index.slice(0, 600));
|
|
92
|
+
rerender(<EmbeddingScatterplotSubscriber {...props} />);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const lastPlot = Scatterplot.mock.lastCall[0];
|
|
96
|
+
expect(lastPlot.cellColorEncoding).toEqual('cellSetSelection');
|
|
97
|
+
expect(lastPlot.obsColorIndices).not.toBe(initialColorIndices);
|
|
98
|
+
// Set selection coloring is only shown together with the new colors. Showing
|
|
99
|
+
// it with the previous colors would color every cell by the previous selection.
|
|
100
|
+
Scatterplot.mock.calls.forEach(([plot]) => {
|
|
101
|
+
if (plot.cellColorEncoding === 'cellSetSelection') {
|
|
102
|
+
expect(plot.obsSetSelection).toEqual(state.values.obsSetSelection);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|