@vitessce/scatterplot-embedding 2.0.3 → 3.0.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.
@@ -0,0 +1,167 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useState, useEffect, useCallback, useMemo, } from 'react';
3
+ import { extent } from 'd3-array';
4
+ import { isEqual } from 'lodash-es';
5
+ import plur from 'plur';
6
+ import { TitleInfo, useReady, useUrls, useDeckCanvasSize, useUint8FeatureSelection, useExpressionValueGetter, useGetObsInfo, useObsEmbeddingData, useObsSetsData, useFeatureSelection, useObsFeatureMatrixIndices, useFeatureLabelsData, useMultiObsLabels, useCoordination, useLoaders, useSetComponentHover, useSetComponentViewInfo, } from '@vitessce/vit-s';
7
+ import { setObsSelection, mergeObsSets, getCellSetPolygons } from '@vitessce/sets-utils';
8
+ import { getCellColors, commaNumber } from '@vitessce/utils';
9
+ import { Scatterplot, ScatterplotTooltipSubscriber, ScatterplotOptions, getPointSizeDevicePixels, getPointOpacity, } from '@vitessce/scatterplot';
10
+ import { Legend } from '@vitessce/legend';
11
+ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
12
+ /**
13
+ * A subscriber component for the scatterplot.
14
+ * @param {object} props
15
+ * @param {number} props.uuid The unique identifier for this component.
16
+ * @param {string} props.theme The current theme name.
17
+ * @param {object} props.coordinationScopes The mapping from coordination types to coordination
18
+ * scopes.
19
+ * @param {function} props.removeGridComponent The callback function to pass to TitleInfo,
20
+ * to call when the component has been removed from the grid.
21
+ * @param {string} props.title An override value for the component title.
22
+ * @param {number} props.averageFillDensity Override the average fill density calculation
23
+ * when using dynamic opacity mode.
24
+ */
25
+ export function EmbeddingScatterplotSubscriber(props) {
26
+ const { uuid, coordinationScopes, removeGridComponent, theme, observationsLabelOverride, title: titleOverride,
27
+ // Average fill density for dynamic opacity calculation.
28
+ averageFillDensity, } = props;
29
+ const loaders = useLoaders();
30
+ const setComponentHover = useSetComponentHover();
31
+ const setComponentViewInfo = useSetComponentViewInfo(uuid);
32
+ // Get "props" from the coordination space.
33
+ const [{ dataset, obsType, featureType, featureValueType, embeddingZoom: zoom, embeddingTargetX: targetX, embeddingTargetY: targetY, embeddingTargetZ: targetZ, embeddingType: mapping, obsFilter: cellFilter, obsHighlight: cellHighlight, featureSelection: geneSelection, obsSetSelection: cellSetSelection, obsSetColor: cellSetColor, obsColorEncoding: cellColorEncoding, additionalObsSets: additionalCellSets, embeddingObsSetPolygonsVisible: cellSetPolygonsVisible, embeddingObsSetLabelsVisible: cellSetLabelsVisible, embeddingObsSetLabelSize: cellSetLabelSize, embeddingObsRadius: cellRadiusFixed, embeddingObsRadiusMode: cellRadiusMode, embeddingObsOpacity: cellOpacityFixed, embeddingObsOpacityMode: cellOpacityMode, featureValueColormap: geneExpressionColormap, featureValueColormapRange: geneExpressionColormapRange, tooltipsVisible, }, { setEmbeddingZoom: setZoom, setEmbeddingTargetX: setTargetX, setEmbeddingTargetY: setTargetY, setEmbeddingTargetZ: setTargetZ, setObsFilter: setCellFilter, setObsSetSelection: setCellSetSelection, setObsHighlight: setCellHighlight, setObsSetColor: setCellSetColor, setObsColorEncoding: setCellColorEncoding, setAdditionalObsSets: setAdditionalCellSets, setEmbeddingObsSetPolygonsVisible: setCellSetPolygonsVisible, setEmbeddingObsSetLabelsVisible: setCellSetLabelsVisible, setEmbeddingObsSetLabelSize: setCellSetLabelSize, setEmbeddingObsRadius: setCellRadiusFixed, setEmbeddingObsRadiusMode: setCellRadiusMode, setEmbeddingObsOpacity: setCellOpacityFixed, setEmbeddingObsOpacityMode: setCellOpacityMode, setFeatureValueColormap: setGeneExpressionColormap, setFeatureValueColormapRange: setGeneExpressionColormapRange, setTooltipsVisible, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT], coordinationScopes);
34
+ const observationsLabel = observationsLabelOverride || obsType;
35
+ const [urls, addUrl] = useUrls(loaders, dataset);
36
+ const [width, height, deckRef] = useDeckCanvasSize();
37
+ const title = titleOverride || `Scatterplot (${mapping})`;
38
+ const [obsLabelsTypes, obsLabelsData] = useMultiObsLabels(coordinationScopes, obsType, loaders, dataset, addUrl);
39
+ // Get data from loaders using the data hooks.
40
+ const [{ obsIndex: obsEmbeddingIndex, obsEmbedding }, obsEmbeddingStatus] = useObsEmbeddingData(loaders, dataset, addUrl, true, {}, {}, { obsType, embeddingType: mapping });
41
+ const cellsCount = obsEmbeddingIndex?.length || 0;
42
+ const [{ obsSets: cellSets, obsSetsMembership }, obsSetsStatus] = useObsSetsData(loaders, dataset, addUrl, false, { setObsSetSelection: setCellSetSelection, setObsSetColor: setCellSetColor }, { obsSetSelection: cellSetSelection, obsSetColor: cellSetColor }, { obsType });
43
+ // eslint-disable-next-line no-unused-vars
44
+ const [expressionData, loadedFeatureSelection, featureSelectionStatus] = useFeatureSelection(loaders, dataset, false, geneSelection, { obsType, featureType, featureValueType });
45
+ const [{ obsIndex: matrixObsIndex }, matrixIndicesStatus] = useObsFeatureMatrixIndices(loaders, dataset, addUrl, false, { obsType, featureType, featureValueType });
46
+ const [{ featureLabelsMap }, featureLabelsStatus] = useFeatureLabelsData(loaders, dataset, addUrl, false, {}, {}, { featureType });
47
+ const isReady = useReady([
48
+ obsEmbeddingStatus,
49
+ obsSetsStatus,
50
+ featureSelectionStatus,
51
+ featureLabelsStatus,
52
+ matrixIndicesStatus,
53
+ ]);
54
+ const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
55
+ const [dynamicCellOpacity, setDynamicCellOpacity] = useState(cellOpacityFixed);
56
+ const [originalViewState, setOriginalViewState] = useState(null);
57
+ const mergedCellSets = useMemo(() => mergeObsSets(cellSets, additionalCellSets), [cellSets, additionalCellSets]);
58
+ const setCellSelectionProp = useCallback((v) => {
59
+ setObsSelection(v, additionalCellSets, cellSetColor, setCellSetSelection, setAdditionalCellSets, setCellSetColor, setCellColorEncoding);
60
+ }, [additionalCellSets, cellSetColor, setCellColorEncoding,
61
+ setAdditionalCellSets, setCellSetColor, setCellSetSelection]);
62
+ const cellColors = useMemo(() => getCellColors({
63
+ cellSets: mergedCellSets,
64
+ cellSetSelection,
65
+ cellSetColor,
66
+ obsIndex: matrixObsIndex,
67
+ theme,
68
+ }), [mergedCellSets, theme,
69
+ cellSetSelection, cellSetColor, matrixObsIndex]);
70
+ // cellSetPolygonCache is an array of tuples like [(key0, val0), (key1, val1), ...],
71
+ // where the keys are cellSetSelection arrays.
72
+ const [cellSetPolygonCache, setCellSetPolygonCache] = useState([]);
73
+ const cacheHas = (cache, key) => cache.findIndex(el => isEqual(el[0], key)) !== -1;
74
+ const cacheGet = (cache, key) => cache.find(el => isEqual(el[0], key))?.[1];
75
+ const cellSetPolygons = useMemo(() => {
76
+ if ((cellSetLabelsVisible || cellSetPolygonsVisible)
77
+ && !cacheHas(cellSetPolygonCache, cellSetSelection)
78
+ && mergedCellSets?.tree?.length
79
+ && obsEmbedding
80
+ && obsEmbeddingIndex
81
+ && cellSetColor?.length) {
82
+ const newCellSetPolygons = getCellSetPolygons({
83
+ obsIndex: obsEmbeddingIndex,
84
+ obsEmbedding,
85
+ cellSets: mergedCellSets,
86
+ cellSetSelection,
87
+ cellSetColor,
88
+ theme,
89
+ });
90
+ setCellSetPolygonCache(cache => [...cache, [cellSetSelection, newCellSetPolygons]]);
91
+ return newCellSetPolygons;
92
+ }
93
+ return cacheGet(cellSetPolygonCache, cellSetSelection) || [];
94
+ }, [cellSetPolygonsVisible, cellSetPolygonCache, cellSetLabelsVisible, theme,
95
+ obsEmbeddingIndex, obsEmbedding, mergedCellSets, cellSetSelection, cellSetColor]);
96
+ const cellSelection = useMemo(() => Array.from(cellColors.keys()), [cellColors]);
97
+ const [xRange, yRange, xExtent, yExtent, numCells] = useMemo(() => {
98
+ if (obsEmbedding && obsEmbedding.data && obsEmbedding.shape) {
99
+ const cellCount = obsEmbedding.shape[1];
100
+ const xE = extent(obsEmbedding.data[0]);
101
+ const yE = extent(obsEmbedding.data[1]);
102
+ const xR = xE[1] - xE[0];
103
+ const yR = yE[1] - yE[0];
104
+ return [xR, yR, xE, yE, cellCount];
105
+ }
106
+ return [null, null, null, null, null];
107
+ }, [obsEmbedding]);
108
+ // After cells have loaded or changed,
109
+ // compute the cell radius scale based on the
110
+ // extents of the cell coordinates on the x/y axes.
111
+ useEffect(() => {
112
+ // We do not really need isReady here, since the above useMemo that
113
+ // computes xRange and yRange will only run after obsEmbedding has loaded anyway.
114
+ // However, we include it here to ensure this effect waits as long as possible to run;
115
+ // For some reason, otherwise, in some cases this effect will run before the react-grid-layout
116
+ // initialization animation has finished,
117
+ // prior to `height` and `width` reaching their ultimate values, resulting in
118
+ // an initial viewState for that small view size, which looks bad.
119
+ if (xRange && yRange && isReady) {
120
+ const pointSizeDevicePixels = getPointSizeDevicePixels(window.devicePixelRatio, zoom, xRange, yRange, width, height);
121
+ setDynamicCellRadius(pointSizeDevicePixels);
122
+ const nextCellOpacityScale = getPointOpacity(zoom, xRange, yRange, width, height, numCells, averageFillDensity);
123
+ setDynamicCellOpacity(nextCellOpacityScale);
124
+ if (typeof targetX !== 'number' || typeof targetY !== 'number') {
125
+ // The view config did not define an initial viewState so
126
+ // we calculate one based on the data and set it.
127
+ const newTargetX = xExtent[0] + xRange / 2;
128
+ const newTargetY = yExtent[0] + yRange / 2;
129
+ const newZoom = Math.log2(Math.min(width / xRange, height / yRange));
130
+ setTargetX(newTargetX);
131
+ // Graphics rendering has the y-axis going south so we need to multiply by negative one.
132
+ setTargetY(-newTargetY);
133
+ setZoom(newZoom);
134
+ setOriginalViewState({ target: [newTargetX, -newTargetY, 0], zoom: newZoom });
135
+ }
136
+ else if (!originalViewState) {
137
+ // originalViewState has not yet been set and
138
+ // the view config defined an initial viewState.
139
+ setOriginalViewState({ target: [targetX, targetY, 0], zoom });
140
+ }
141
+ }
142
+ // eslint-disable-next-line react-hooks/exhaustive-deps
143
+ }, [xRange, yRange, isReady, xExtent, yExtent, numCells,
144
+ width, height, zoom, averageFillDensity]);
145
+ const getObsInfo = useGetObsInfo(observationsLabel, obsLabelsTypes, obsLabelsData, obsSetsMembership);
146
+ const cellSelectionSet = useMemo(() => new Set(cellSelection), [cellSelection]);
147
+ const getCellIsSelected = useCallback((object, { index }) => ((cellSelectionSet || new Set([])).has(obsEmbeddingIndex[index]) ? 1.0 : 0.0), [cellSelectionSet, obsEmbeddingIndex]);
148
+ const cellRadius = (cellRadiusMode === 'manual' ? cellRadiusFixed : dynamicCellRadius);
149
+ const cellOpacity = (cellOpacityMode === 'manual' ? cellOpacityFixed : dynamicCellOpacity);
150
+ const [uint8ExpressionData, expressionExtents] = useUint8FeatureSelection(expressionData);
151
+ // Set up a getter function for gene expression values, to be used
152
+ // by the DeckGL layer to obtain values for instanced attributes.
153
+ const getExpressionValue = useExpressionValueGetter({
154
+ instanceObsIndex: obsEmbeddingIndex,
155
+ matrixObsIndex,
156
+ expressionData: uint8ExpressionData,
157
+ });
158
+ const setViewState = ({ zoom: newZoom, target }) => {
159
+ setZoom(newZoom);
160
+ setTargetX(target[0]);
161
+ setTargetY(target[1]);
162
+ setTargetZ(target[2] || 0);
163
+ };
164
+ return (_jsxs(TitleInfo, { title: title, info: `${commaNumber(cellsCount)} ${plur(observationsLabel, cellsCount)}`, removeGridComponent: removeGridComponent, urls: urls, theme: theme, isReady: isReady, options: (_jsx(ScatterplotOptions, { observationsLabel: observationsLabel, cellRadius: cellRadiusFixed, setCellRadius: setCellRadiusFixed, cellRadiusMode: cellRadiusMode, setCellRadiusMode: setCellRadiusMode, cellOpacity: cellOpacityFixed, setCellOpacity: setCellOpacityFixed, cellOpacityMode: cellOpacityMode, setCellOpacityMode: setCellOpacityMode, cellSetLabelsVisible: cellSetLabelsVisible, setCellSetLabelsVisible: setCellSetLabelsVisible, tooltipsVisible: tooltipsVisible, setTooltipsVisible: setTooltipsVisible, cellSetLabelSize: cellSetLabelSize, setCellSetLabelSize: setCellSetLabelSize, cellSetPolygonsVisible: cellSetPolygonsVisible, setCellSetPolygonsVisible: setCellSetPolygonsVisible, cellColorEncoding: cellColorEncoding, setCellColorEncoding: setCellColorEncoding, geneExpressionColormap: geneExpressionColormap, setGeneExpressionColormap: setGeneExpressionColormap, geneExpressionColormapRange: geneExpressionColormapRange, setGeneExpressionColormapRange: setGeneExpressionColormapRange })), children: [_jsx(Scatterplot, { ref: deckRef, uuid: uuid, theme: theme, viewState: { zoom, target: [targetX, targetY, targetZ] }, setViewState: setViewState, originalViewState: originalViewState, obsEmbeddingIndex: obsEmbeddingIndex, obsEmbedding: obsEmbedding, cellFilter: cellFilter, cellSelection: cellSelection, cellHighlight: cellHighlight, cellColors: cellColors, cellSetPolygons: cellSetPolygons, cellSetLabelSize: cellSetLabelSize, cellSetLabelsVisible: cellSetLabelsVisible, cellSetPolygonsVisible: cellSetPolygonsVisible, setCellFilter: setCellFilter, setCellSelection: setCellSelectionProp, setCellHighlight: setCellHighlight, cellRadius: cellRadius, cellOpacity: cellOpacity, cellColorEncoding: cellColorEncoding, geneExpressionColormap: geneExpressionColormap, geneExpressionColormapRange: geneExpressionColormapRange, setComponentHover: () => {
165
+ setComponentHover(uuid);
166
+ }, updateViewInfo: setComponentViewInfo, getExpressionValue: getExpressionValue, getCellIsSelected: getCellIsSelected }), tooltipsVisible && (_jsx(ScatterplotTooltipSubscriber, { parentUuid: uuid, obsHighlight: cellHighlight, width: width, height: height, getObsInfo: getObsInfo })), _jsx(Legend, { visible: true, theme: theme, featureType: featureType, featureValueType: featureValueType, obsColorEncoding: cellColorEncoding, featureSelection: geneSelection, featureLabelsMap: featureLabelsMap, featureValueColormap: geneExpressionColormap, featureValueColormapRange: geneExpressionColormapRange, extent: expressionExtents?.[0] })] }));
167
+ }
@@ -0,0 +1,2 @@
1
+ export { EmbeddingScatterplotSubscriber } from "./EmbeddingScatterplotSubscriber.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":""}
package/dist-tsc/index.js CHANGED
@@ -1 +1 @@
1
- export { EmbeddingScatterplotSubscriber, register } from './EmbeddingScatterplotSubscriber';
1
+ export { EmbeddingScatterplotSubscriber } from './EmbeddingScatterplotSubscriber.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/scatterplot-embedding",
3
- "version": "2.0.3",
3
+ "version": "3.0.0",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -8,25 +8,28 @@
8
8
  "url": "git+https://github.com/vitessce/vitessce.git"
9
9
  },
10
10
  "license": "MIT",
11
- "main": "dist/index.mjs",
11
+ "type": "module",
12
+ "main": "dist/index.js",
12
13
  "files": [
14
+ "src",
13
15
  "dist",
14
- "src"
16
+ "dist-tsc"
15
17
  ],
16
18
  "dependencies": {
17
19
  "@material-ui/core": "~4.12.3",
18
20
  "d3-array": "^2.4.0",
19
- "lodash": "^4.17.21",
21
+ "lodash-es": "^4.17.21",
20
22
  "plur": "^5.1.0",
21
- "@vitessce/constants-internal": "2.0.3",
22
- "@vitessce/scatterplot": "2.0.3",
23
- "@vitessce/sets-utils": "2.0.3",
24
- "@vitessce/utils": "2.0.3",
25
- "@vitessce/vit-s": "2.0.3"
23
+ "@vitessce/constants-internal": "3.0.0",
24
+ "@vitessce/legend": "3.0.0",
25
+ "@vitessce/scatterplot": "3.0.0",
26
+ "@vitessce/sets-utils": "3.0.0",
27
+ "@vitessce/utils": "3.0.0",
28
+ "@vitessce/vit-s": "3.0.0"
26
29
  },
27
30
  "devDependencies": {
28
31
  "react": "^18.0.0",
29
- "vite": "^3.0.0",
32
+ "vite": "^4.3.0",
30
33
  "vitest": "^0.23.4"
31
34
  },
32
35
  "peerDependencies": {
@@ -35,5 +38,12 @@
35
38
  "scripts": {
36
39
  "bundle": "pnpm exec vite build -c ../../../scripts/vite.config.js",
37
40
  "test": "pnpm exec vitest --run -r ../../../ --dir ."
41
+ },
42
+ "module": "dist/index.js",
43
+ "exports": {
44
+ ".": {
45
+ "types": "./dist-tsc/index.d.ts",
46
+ "import": "./dist/index.js"
47
+ }
38
48
  }
39
49
  }
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
- import TableCell from '@material-ui/core/TableCell';
3
- import TableRow from '@material-ui/core/TableRow';
2
+ import { TableCell, TableRow } from '@material-ui/core';
4
3
  import { usePlotOptionsStyles, OptionSelect } from '@vitessce/vit-s';
5
4
 
6
5
  export default function EmbeddingScatterplotOptions(props) {
@@ -2,18 +2,20 @@ import React, {
2
2
  useState, useEffect, useCallback, useMemo,
3
3
  } from 'react';
4
4
  import { extent } from 'd3-array';
5
- import isEqual from 'lodash/isEqual';
5
+ import { isEqual } from 'lodash-es';
6
6
  import plur from 'plur';
7
7
  import {
8
8
  TitleInfo,
9
- registerPluginViewType,
10
9
  useReady, useUrls,
11
10
  useDeckCanvasSize,
12
- useExpressionValueGetter, useGetObsInfo,
11
+ useUint8FeatureSelection,
12
+ useExpressionValueGetter,
13
+ useGetObsInfo,
13
14
  useObsEmbeddingData,
14
15
  useObsSetsData,
15
16
  useFeatureSelection,
16
17
  useObsFeatureMatrixIndices,
18
+ useFeatureLabelsData,
17
19
  useMultiObsLabels,
18
20
  useCoordination,
19
21
  useLoaders,
@@ -27,6 +29,7 @@ import {
27
29
  getPointSizeDevicePixels,
28
30
  getPointOpacity,
29
31
  } from '@vitessce/scatterplot';
32
+ import { Legend } from '@vitessce/legend';
30
33
  import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
31
34
 
32
35
  /**
@@ -36,7 +39,6 @@ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-inte
36
39
  * @param {string} props.theme The current theme name.
37
40
  * @param {object} props.coordinationScopes The mapping from coordination types to coordination
38
41
  * scopes.
39
- * @param {boolean} props.disableTooltip Should the tooltip be disabled?
40
42
  * @param {function} props.removeGridComponent The callback function to pass to TitleInfo,
41
43
  * to call when the component has been removed from the grid.
42
44
  * @param {string} props.title An override value for the component title.
@@ -49,7 +51,6 @@ export function EmbeddingScatterplotSubscriber(props) {
49
51
  coordinationScopes,
50
52
  removeGridComponent,
51
53
  theme,
52
- disableTooltip = false,
53
54
  observationsLabelOverride,
54
55
  title: titleOverride,
55
56
  // Average fill density for dynamic opacity calculation.
@@ -87,6 +88,7 @@ export function EmbeddingScatterplotSubscriber(props) {
87
88
  embeddingObsOpacityMode: cellOpacityMode,
88
89
  featureValueColormap: geneExpressionColormap,
89
90
  featureValueColormapRange: geneExpressionColormapRange,
91
+ tooltipsVisible,
90
92
  }, {
91
93
  setEmbeddingZoom: setZoom,
92
94
  setEmbeddingTargetX: setTargetX,
@@ -107,6 +109,7 @@ export function EmbeddingScatterplotSubscriber(props) {
107
109
  setEmbeddingObsOpacityMode: setCellOpacityMode,
108
110
  setFeatureValueColormap: setGeneExpressionColormap,
109
111
  setFeatureValueColormapRange: setGeneExpressionColormapRange,
112
+ setTooltipsVisible,
110
113
  }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT], coordinationScopes);
111
114
 
112
115
  const observationsLabel = observationsLabelOverride || obsType;
@@ -141,17 +144,24 @@ export function EmbeddingScatterplotSubscriber(props) {
141
144
  loaders, dataset, addUrl, false,
142
145
  { obsType, featureType, featureValueType },
143
146
  );
147
+ const [{ featureLabelsMap }, featureLabelsStatus] = useFeatureLabelsData(
148
+ loaders, dataset, addUrl, false, {}, {},
149
+ { featureType },
150
+ );
144
151
 
145
152
  const isReady = useReady([
146
153
  obsEmbeddingStatus,
147
154
  obsSetsStatus,
148
155
  featureSelectionStatus,
156
+ featureLabelsStatus,
149
157
  matrixIndicesStatus,
150
158
  ]);
151
159
 
152
160
  const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
153
161
  const [dynamicCellOpacity, setDynamicCellOpacity] = useState(cellOpacityFixed);
154
162
 
163
+ const [originalViewState, setOriginalViewState] = useState(null);
164
+
155
165
  const mergedCellSets = useMemo(() => mergeObsSets(
156
166
  cellSets, additionalCellSets,
157
167
  ), [cellSets, additionalCellSets]);
@@ -166,16 +176,13 @@ export function EmbeddingScatterplotSubscriber(props) {
166
176
  setAdditionalCellSets, setCellSetColor, setCellSetSelection]);
167
177
 
168
178
  const cellColors = useMemo(() => getCellColors({
169
- cellColorEncoding,
170
- expressionData: expressionData && expressionData[0],
171
- geneSelection,
172
179
  cellSets: mergedCellSets,
173
180
  cellSetSelection,
174
181
  cellSetColor,
175
182
  obsIndex: matrixObsIndex,
176
183
  theme,
177
- }), [cellColorEncoding, geneSelection, mergedCellSets, theme,
178
- cellSetSelection, cellSetColor, expressionData, matrixObsIndex]);
184
+ }), [mergedCellSets, theme,
185
+ cellSetSelection, cellSetColor, matrixObsIndex]);
179
186
 
180
187
  // cellSetPolygonCache is an array of tuples like [(key0, val0), (key1, val1), ...],
181
188
  // where the keys are cellSetSelection arrays.
@@ -223,7 +230,14 @@ export function EmbeddingScatterplotSubscriber(props) {
223
230
  // compute the cell radius scale based on the
224
231
  // extents of the cell coordinates on the x/y axes.
225
232
  useEffect(() => {
226
- if (xRange && yRange) {
233
+ // We do not really need isReady here, since the above useMemo that
234
+ // computes xRange and yRange will only run after obsEmbedding has loaded anyway.
235
+ // However, we include it here to ensure this effect waits as long as possible to run;
236
+ // For some reason, otherwise, in some cases this effect will run before the react-grid-layout
237
+ // initialization animation has finished,
238
+ // prior to `height` and `width` reaching their ultimate values, resulting in
239
+ // an initial viewState for that small view size, which looks bad.
240
+ if (xRange && yRange && isReady) {
227
241
  const pointSizeDevicePixels = getPointSizeDevicePixels(
228
242
  window.devicePixelRatio, zoom, xRange, yRange, width, height,
229
243
  );
@@ -235,6 +249,8 @@ export function EmbeddingScatterplotSubscriber(props) {
235
249
  setDynamicCellOpacity(nextCellOpacityScale);
236
250
 
237
251
  if (typeof targetX !== 'number' || typeof targetY !== 'number') {
252
+ // The view config did not define an initial viewState so
253
+ // we calculate one based on the data and set it.
238
254
  const newTargetX = xExtent[0] + xRange / 2;
239
255
  const newTargetY = yExtent[0] + yRange / 2;
240
256
  const newZoom = Math.log2(Math.min(width / xRange, height / yRange));
@@ -242,10 +258,15 @@ export function EmbeddingScatterplotSubscriber(props) {
242
258
  // Graphics rendering has the y-axis going south so we need to multiply by negative one.
243
259
  setTargetY(-newTargetY);
244
260
  setZoom(newZoom);
261
+ setOriginalViewState({ target: [newTargetX, -newTargetY, 0], zoom: newZoom });
262
+ } else if (!originalViewState) {
263
+ // originalViewState has not yet been set and
264
+ // the view config defined an initial viewState.
265
+ setOriginalViewState({ target: [targetX, targetY, 0], zoom });
245
266
  }
246
267
  }
247
268
  // eslint-disable-next-line react-hooks/exhaustive-deps
248
- }, [xRange, yRange, xExtent, yExtent, numCells,
269
+ }, [xRange, yRange, isReady, xExtent, yExtent, numCells,
249
270
  width, height, zoom, averageFillDensity]);
250
271
 
251
272
  const getObsInfo = useGetObsInfo(
@@ -260,14 +281,23 @@ export function EmbeddingScatterplotSubscriber(props) {
260
281
  const cellRadius = (cellRadiusMode === 'manual' ? cellRadiusFixed : dynamicCellRadius);
261
282
  const cellOpacity = (cellOpacityMode === 'manual' ? cellOpacityFixed : dynamicCellOpacity);
262
283
 
284
+ const [uint8ExpressionData, expressionExtents] = useUint8FeatureSelection(expressionData);
285
+
263
286
  // Set up a getter function for gene expression values, to be used
264
287
  // by the DeckGL layer to obtain values for instanced attributes.
265
288
  const getExpressionValue = useExpressionValueGetter({
266
289
  instanceObsIndex: obsEmbeddingIndex,
267
290
  matrixObsIndex,
268
- expressionData,
291
+ expressionData: uint8ExpressionData,
269
292
  });
270
293
 
294
+ const setViewState = ({ zoom: newZoom, target }) => {
295
+ setZoom(newZoom);
296
+ setTargetX(target[0]);
297
+ setTargetY(target[1]);
298
+ setTargetZ(target[2] || 0);
299
+ };
300
+
271
301
  return (
272
302
  <TitleInfo
273
303
  title={title}
@@ -289,6 +319,8 @@ export function EmbeddingScatterplotSubscriber(props) {
289
319
  setCellOpacityMode={setCellOpacityMode}
290
320
  cellSetLabelsVisible={cellSetLabelsVisible}
291
321
  setCellSetLabelsVisible={setCellSetLabelsVisible}
322
+ tooltipsVisible={tooltipsVisible}
323
+ setTooltipsVisible={setTooltipsVisible}
292
324
  cellSetLabelSize={cellSetLabelSize}
293
325
  setCellSetLabelSize={setCellSetLabelSize}
294
326
  cellSetPolygonsVisible={cellSetPolygonsVisible}
@@ -307,12 +339,8 @@ export function EmbeddingScatterplotSubscriber(props) {
307
339
  uuid={uuid}
308
340
  theme={theme}
309
341
  viewState={{ zoom, target: [targetX, targetY, targetZ] }}
310
- setViewState={({ zoom: newZoom, target }) => {
311
- setZoom(newZoom);
312
- setTargetX(target[0]);
313
- setTargetY(target[1]);
314
- setTargetZ(target[2] || 0);
315
- }}
342
+ setViewState={setViewState}
343
+ originalViewState={originalViewState}
316
344
  obsEmbeddingIndex={obsEmbeddingIndex}
317
345
  obsEmbedding={obsEmbedding}
318
346
  cellFilter={cellFilter}
@@ -339,7 +367,7 @@ export function EmbeddingScatterplotSubscriber(props) {
339
367
  getCellIsSelected={getCellIsSelected}
340
368
 
341
369
  />
342
- {!disableTooltip && (
370
+ {tooltipsVisible && (
343
371
  <ScatterplotTooltipSubscriber
344
372
  parentUuid={uuid}
345
373
  obsHighlight={cellHighlight}
@@ -348,14 +376,18 @@ export function EmbeddingScatterplotSubscriber(props) {
348
376
  getObsInfo={getObsInfo}
349
377
  />
350
378
  )}
379
+ <Legend
380
+ visible
381
+ theme={theme}
382
+ featureType={featureType}
383
+ featureValueType={featureValueType}
384
+ obsColorEncoding={cellColorEncoding}
385
+ featureSelection={geneSelection}
386
+ featureLabelsMap={featureLabelsMap}
387
+ featureValueColormap={geneExpressionColormap}
388
+ featureValueColormapRange={geneExpressionColormapRange}
389
+ extent={expressionExtents?.[0]}
390
+ />
351
391
  </TitleInfo>
352
392
  );
353
393
  }
354
-
355
- export function register() {
356
- registerPluginViewType(
357
- ViewType.SCATTERPLOT,
358
- EmbeddingScatterplotSubscriber,
359
- COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT],
360
- );
361
- }
package/src/index.js CHANGED
@@ -1 +1 @@
1
- export { EmbeddingScatterplotSubscriber, register } from './EmbeddingScatterplotSubscriber';
1
+ export { EmbeddingScatterplotSubscriber } from './EmbeddingScatterplotSubscriber.js';
package/dist/index.mjs DELETED
@@ -1,8 +0,0 @@
1
- import { E, r } from "./index.30ffff67.mjs";
2
- import "react";
3
- import "@vitessce/vit-s";
4
- import "react-dom";
5
- export {
6
- E as EmbeddingScatterplotSubscriber,
7
- r as register
8
- };