@vitessce/scatterplot-embedding 2.0.3 → 3.0.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.
@@ -0,0 +1,170 @@
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, useInitialCoordination, } 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 { embeddingZoom: initialZoom, embeddingTargetX: initialTargetX, embeddingTargetY: initialTargetY, } = useInitialCoordination(COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT], coordinationScopes);
35
+ const observationsLabel = observationsLabelOverride || obsType;
36
+ const [width, height, deckRef] = useDeckCanvasSize();
37
+ const title = titleOverride || `Scatterplot (${mapping})`;
38
+ const [obsLabelsTypes, obsLabelsData] = useMultiObsLabels(coordinationScopes, obsType, loaders, dataset);
39
+ // Get data from loaders using the data hooks.
40
+ const [{ obsIndex: obsEmbeddingIndex, obsEmbedding }, obsEmbeddingStatus, obsEmbeddingUrls,] = useObsEmbeddingData(loaders, dataset, true, {}, {}, { obsType, embeddingType: mapping });
41
+ const cellsCount = obsEmbeddingIndex?.length || 0;
42
+ const [{ obsSets: cellSets, obsSetsMembership }, obsSetsStatus, obsSetsUrls] = useObsSetsData(loaders, dataset, 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, matrixIndicesUrls,] = useObsFeatureMatrixIndices(loaders, dataset, false, { obsType, featureType, featureValueType });
46
+ const [{ featureLabelsMap }, featureLabelsStatus, featureLabelsUrls] = useFeatureLabelsData(loaders, dataset, false, {}, {}, { featureType });
47
+ const isReady = useReady([
48
+ obsEmbeddingStatus,
49
+ obsSetsStatus,
50
+ featureSelectionStatus,
51
+ featureLabelsStatus,
52
+ matrixIndicesStatus,
53
+ ]);
54
+ const urls = useUrls([
55
+ obsEmbeddingUrls,
56
+ obsSetsUrls,
57
+ matrixIndicesUrls,
58
+ featureLabelsUrls,
59
+ ]);
60
+ const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
61
+ const [dynamicCellOpacity, setDynamicCellOpacity] = useState(cellOpacityFixed);
62
+ const [originalViewState, setOriginalViewState] = useState(null);
63
+ const mergedCellSets = useMemo(() => mergeObsSets(cellSets, additionalCellSets), [cellSets, additionalCellSets]);
64
+ const setCellSelectionProp = useCallback((v) => {
65
+ setObsSelection(v, additionalCellSets, cellSetColor, setCellSetSelection, setAdditionalCellSets, setCellSetColor, setCellColorEncoding);
66
+ }, [additionalCellSets, cellSetColor, setCellColorEncoding,
67
+ setAdditionalCellSets, setCellSetColor, setCellSetSelection]);
68
+ const cellColors = useMemo(() => getCellColors({
69
+ cellSets: mergedCellSets,
70
+ cellSetSelection,
71
+ cellSetColor,
72
+ obsIndex: matrixObsIndex,
73
+ theme,
74
+ }), [mergedCellSets, theme,
75
+ cellSetSelection, cellSetColor, matrixObsIndex]);
76
+ // cellSetPolygonCache is an array of tuples like [(key0, val0), (key1, val1), ...],
77
+ // where the keys are cellSetSelection arrays.
78
+ const [cellSetPolygonCache, setCellSetPolygonCache] = useState([]);
79
+ const cacheHas = (cache, key) => cache.findIndex(el => isEqual(el[0], key)) !== -1;
80
+ const cacheGet = (cache, key) => cache.find(el => isEqual(el[0], key))?.[1];
81
+ const cellSetPolygons = useMemo(() => {
82
+ if ((cellSetLabelsVisible || cellSetPolygonsVisible)
83
+ && !cacheHas(cellSetPolygonCache, cellSetSelection)
84
+ && mergedCellSets?.tree?.length
85
+ && obsEmbedding
86
+ && obsEmbeddingIndex
87
+ && cellSetColor?.length) {
88
+ const newCellSetPolygons = getCellSetPolygons({
89
+ obsIndex: obsEmbeddingIndex,
90
+ obsEmbedding,
91
+ cellSets: mergedCellSets,
92
+ cellSetSelection,
93
+ cellSetColor,
94
+ theme,
95
+ });
96
+ setCellSetPolygonCache(cache => [...cache, [cellSetSelection, newCellSetPolygons]]);
97
+ return newCellSetPolygons;
98
+ }
99
+ return cacheGet(cellSetPolygonCache, cellSetSelection) || [];
100
+ }, [cellSetPolygonsVisible, cellSetPolygonCache, cellSetLabelsVisible, theme,
101
+ obsEmbeddingIndex, obsEmbedding, mergedCellSets, cellSetSelection, cellSetColor]);
102
+ const cellSelection = useMemo(() => Array.from(cellColors.keys()), [cellColors]);
103
+ const [xRange, yRange, xExtent, yExtent, numCells] = useMemo(() => {
104
+ if (obsEmbedding && obsEmbedding.data && obsEmbedding.shape) {
105
+ const cellCount = obsEmbedding.shape[1];
106
+ const xE = extent(obsEmbedding.data[0]);
107
+ const yE = extent(obsEmbedding.data[1]);
108
+ const xR = xE[1] - xE[0];
109
+ const yR = yE[1] - yE[0];
110
+ return [xR, yR, xE, yE, cellCount];
111
+ }
112
+ return [null, null, null, null, null];
113
+ }, [obsEmbedding]);
114
+ // After cells have loaded or changed,
115
+ // compute the cell radius scale based on the
116
+ // extents of the cell coordinates on the x/y axes.
117
+ useEffect(() => {
118
+ if (xRange && yRange) {
119
+ const pointSizeDevicePixels = getPointSizeDevicePixels(window.devicePixelRatio, zoom, xRange, yRange, width, height);
120
+ setDynamicCellRadius(pointSizeDevicePixels);
121
+ const nextCellOpacityScale = getPointOpacity(zoom, xRange, yRange, width, height, numCells, averageFillDensity);
122
+ setDynamicCellOpacity(nextCellOpacityScale);
123
+ if (typeof initialTargetX !== 'number' || typeof initialTargetY !== 'number') {
124
+ // The view config did not define an initial viewState so
125
+ // we calculate one based on the data and set it.
126
+ const newTargetX = xExtent[0] + xRange / 2;
127
+ const newTargetY = yExtent[0] + yRange / 2;
128
+ const newZoom = Math.log2(Math.min(width / xRange, height / yRange));
129
+ const notYetInitialized = (typeof targetX !== 'number' || typeof targetY !== 'number');
130
+ const stillDefaultInitialized = (targetX === newTargetX && targetY === -newTargetY);
131
+ if (notYetInitialized || stillDefaultInitialized) {
132
+ setTargetX(newTargetX);
133
+ // Graphics rendering has the y-axis going south so we need to multiply by negative one.
134
+ setTargetY(-newTargetY);
135
+ setZoom(newZoom);
136
+ }
137
+ setOriginalViewState({ target: [newTargetX, -newTargetY, 0], zoom: newZoom });
138
+ }
139
+ else if (!originalViewState) {
140
+ // originalViewState has not yet been set and
141
+ // the view config defined an initial viewState.
142
+ setOriginalViewState({ target: [initialTargetX, initialTargetY, 0], zoom: initialZoom });
143
+ }
144
+ }
145
+ // eslint-disable-next-line react-hooks/exhaustive-deps
146
+ }, [xRange, yRange, xExtent, yExtent, numCells,
147
+ width, height, initialZoom, zoom, initialTargetX, initialTargetY, averageFillDensity]);
148
+ const getObsInfo = useGetObsInfo(observationsLabel, obsLabelsTypes, obsLabelsData, obsSetsMembership);
149
+ const cellSelectionSet = useMemo(() => new Set(cellSelection), [cellSelection]);
150
+ const getCellIsSelected = useCallback((object, { index }) => ((cellSelectionSet || new Set([])).has(obsEmbeddingIndex[index]) ? 1.0 : 0.0), [cellSelectionSet, obsEmbeddingIndex]);
151
+ const cellRadius = (cellRadiusMode === 'manual' ? cellRadiusFixed : dynamicCellRadius);
152
+ const cellOpacity = (cellOpacityMode === 'manual' ? cellOpacityFixed : dynamicCellOpacity);
153
+ const [uint8ExpressionData, expressionExtents] = useUint8FeatureSelection(expressionData);
154
+ // Set up a getter function for gene expression values, to be used
155
+ // by the DeckGL layer to obtain values for instanced attributes.
156
+ const getExpressionValue = useExpressionValueGetter({
157
+ instanceObsIndex: obsEmbeddingIndex,
158
+ matrixObsIndex,
159
+ expressionData: uint8ExpressionData,
160
+ });
161
+ const setViewState = ({ zoom: newZoom, target }) => {
162
+ setZoom(newZoom);
163
+ setTargetX(target[0]);
164
+ setTargetY(target[1]);
165
+ setTargetZ(target[2] || 0);
166
+ };
167
+ 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: () => {
168
+ setComponentHover(uuid);
169
+ }, 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] })] }));
170
+ }
@@ -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.1",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -8,32 +8,42 @@
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.1",
24
+ "@vitessce/legend": "3.0.1",
25
+ "@vitessce/scatterplot": "3.0.1",
26
+ "@vitessce/sets-utils": "3.0.1",
27
+ "@vitessce/utils": "3.0.1",
28
+ "@vitessce/vit-s": "3.0.1"
26
29
  },
27
30
  "devDependencies": {
28
31
  "react": "^18.0.0",
29
- "vite": "^3.0.0",
30
- "vitest": "^0.23.4"
32
+ "vite": "^4.3.0",
33
+ "vitest": "^0.32.2"
31
34
  },
32
35
  "peerDependencies": {
33
36
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
34
37
  },
35
38
  "scripts": {
36
39
  "bundle": "pnpm exec vite build -c ../../../scripts/vite.config.js",
37
- "test": "pnpm exec vitest --run -r ../../../ --dir ."
40
+ "test": "pnpm exec vitest --run"
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,23 +2,26 @@ 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,
20
22
  useSetComponentHover,
21
23
  useSetComponentViewInfo,
24
+ useInitialCoordination,
22
25
  } from '@vitessce/vit-s';
23
26
  import { setObsSelection, mergeObsSets, getCellSetPolygons } from '@vitessce/sets-utils';
24
27
  import { getCellColors, commaNumber } from '@vitessce/utils';
@@ -27,6 +30,7 @@ import {
27
30
  getPointSizeDevicePixels,
28
31
  getPointOpacity,
29
32
  } from '@vitessce/scatterplot';
33
+ import { Legend } from '@vitessce/legend';
30
34
  import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
31
35
 
32
36
  /**
@@ -36,7 +40,6 @@ import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-inte
36
40
  * @param {string} props.theme The current theme name.
37
41
  * @param {object} props.coordinationScopes The mapping from coordination types to coordination
38
42
  * scopes.
39
- * @param {boolean} props.disableTooltip Should the tooltip be disabled?
40
43
  * @param {function} props.removeGridComponent The callback function to pass to TitleInfo,
41
44
  * to call when the component has been removed from the grid.
42
45
  * @param {string} props.title An override value for the component title.
@@ -49,7 +52,6 @@ export function EmbeddingScatterplotSubscriber(props) {
49
52
  coordinationScopes,
50
53
  removeGridComponent,
51
54
  theme,
52
- disableTooltip = false,
53
55
  observationsLabelOverride,
54
56
  title: titleOverride,
55
57
  // Average fill density for dynamic opacity calculation.
@@ -87,6 +89,7 @@ export function EmbeddingScatterplotSubscriber(props) {
87
89
  embeddingObsOpacityMode: cellOpacityMode,
88
90
  featureValueColormap: geneExpressionColormap,
89
91
  featureValueColormapRange: geneExpressionColormapRange,
92
+ tooltipsVisible,
90
93
  }, {
91
94
  setEmbeddingZoom: setZoom,
92
95
  setEmbeddingTargetX: setTargetX,
@@ -107,27 +110,37 @@ export function EmbeddingScatterplotSubscriber(props) {
107
110
  setEmbeddingObsOpacityMode: setCellOpacityMode,
108
111
  setFeatureValueColormap: setGeneExpressionColormap,
109
112
  setFeatureValueColormapRange: setGeneExpressionColormapRange,
113
+ setTooltipsVisible,
110
114
  }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT], coordinationScopes);
111
115
 
116
+ const {
117
+ embeddingZoom: initialZoom,
118
+ embeddingTargetX: initialTargetX,
119
+ embeddingTargetY: initialTargetY,
120
+ } = useInitialCoordination(
121
+ COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT], coordinationScopes,
122
+ );
123
+
112
124
  const observationsLabel = observationsLabelOverride || obsType;
113
125
 
114
- const [urls, addUrl] = useUrls(loaders, dataset);
115
126
  const [width, height, deckRef] = useDeckCanvasSize();
116
127
 
117
128
  const title = titleOverride || `Scatterplot (${mapping})`;
118
129
 
119
130
  const [obsLabelsTypes, obsLabelsData] = useMultiObsLabels(
120
- coordinationScopes, obsType, loaders, dataset, addUrl,
131
+ coordinationScopes, obsType, loaders, dataset,
121
132
  );
122
133
 
123
134
  // Get data from loaders using the data hooks.
124
- const [{ obsIndex: obsEmbeddingIndex, obsEmbedding }, obsEmbeddingStatus] = useObsEmbeddingData(
125
- loaders, dataset, addUrl, true, {}, {},
135
+ const [
136
+ { obsIndex: obsEmbeddingIndex, obsEmbedding }, obsEmbeddingStatus, obsEmbeddingUrls,
137
+ ] = useObsEmbeddingData(
138
+ loaders, dataset, true, {}, {},
126
139
  { obsType, embeddingType: mapping },
127
140
  );
128
141
  const cellsCount = obsEmbeddingIndex?.length || 0;
129
- const [{ obsSets: cellSets, obsSetsMembership }, obsSetsStatus] = useObsSetsData(
130
- loaders, dataset, addUrl, false,
142
+ const [{ obsSets: cellSets, obsSetsMembership }, obsSetsStatus, obsSetsUrls] = useObsSetsData(
143
+ loaders, dataset, false,
131
144
  { setObsSetSelection: setCellSetSelection, setObsSetColor: setCellSetColor },
132
145
  { obsSetSelection: cellSetSelection, obsSetColor: cellSetColor },
133
146
  { obsType },
@@ -137,21 +150,36 @@ export function EmbeddingScatterplotSubscriber(props) {
137
150
  loaders, dataset, false, geneSelection,
138
151
  { obsType, featureType, featureValueType },
139
152
  );
140
- const [{ obsIndex: matrixObsIndex }, matrixIndicesStatus] = useObsFeatureMatrixIndices(
141
- loaders, dataset, addUrl, false,
153
+ const [
154
+ { obsIndex: matrixObsIndex }, matrixIndicesStatus, matrixIndicesUrls,
155
+ ] = useObsFeatureMatrixIndices(
156
+ loaders, dataset, false,
142
157
  { obsType, featureType, featureValueType },
143
158
  );
159
+ const [{ featureLabelsMap }, featureLabelsStatus, featureLabelsUrls] = useFeatureLabelsData(
160
+ loaders, dataset, false, {}, {},
161
+ { featureType },
162
+ );
144
163
 
145
164
  const isReady = useReady([
146
165
  obsEmbeddingStatus,
147
166
  obsSetsStatus,
148
167
  featureSelectionStatus,
168
+ featureLabelsStatus,
149
169
  matrixIndicesStatus,
150
170
  ]);
171
+ const urls = useUrls([
172
+ obsEmbeddingUrls,
173
+ obsSetsUrls,
174
+ matrixIndicesUrls,
175
+ featureLabelsUrls,
176
+ ]);
151
177
 
152
178
  const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
153
179
  const [dynamicCellOpacity, setDynamicCellOpacity] = useState(cellOpacityFixed);
154
180
 
181
+ const [originalViewState, setOriginalViewState] = useState(null);
182
+
155
183
  const mergedCellSets = useMemo(() => mergeObsSets(
156
184
  cellSets, additionalCellSets,
157
185
  ), [cellSets, additionalCellSets]);
@@ -166,16 +194,13 @@ export function EmbeddingScatterplotSubscriber(props) {
166
194
  setAdditionalCellSets, setCellSetColor, setCellSetSelection]);
167
195
 
168
196
  const cellColors = useMemo(() => getCellColors({
169
- cellColorEncoding,
170
- expressionData: expressionData && expressionData[0],
171
- geneSelection,
172
197
  cellSets: mergedCellSets,
173
198
  cellSetSelection,
174
199
  cellSetColor,
175
200
  obsIndex: matrixObsIndex,
176
201
  theme,
177
- }), [cellColorEncoding, geneSelection, mergedCellSets, theme,
178
- cellSetSelection, cellSetColor, expressionData, matrixObsIndex]);
202
+ }), [mergedCellSets, theme,
203
+ cellSetSelection, cellSetColor, matrixObsIndex]);
179
204
 
180
205
  // cellSetPolygonCache is an array of tuples like [(key0, val0), (key1, val1), ...],
181
206
  // where the keys are cellSetSelection arrays.
@@ -234,19 +259,30 @@ export function EmbeddingScatterplotSubscriber(props) {
234
259
  );
235
260
  setDynamicCellOpacity(nextCellOpacityScale);
236
261
 
237
- if (typeof targetX !== 'number' || typeof targetY !== 'number') {
262
+ if (typeof initialTargetX !== 'number' || typeof initialTargetY !== 'number') {
263
+ // The view config did not define an initial viewState so
264
+ // we calculate one based on the data and set it.
238
265
  const newTargetX = xExtent[0] + xRange / 2;
239
266
  const newTargetY = yExtent[0] + yRange / 2;
240
267
  const newZoom = Math.log2(Math.min(width / xRange, height / yRange));
241
- setTargetX(newTargetX);
242
- // Graphics rendering has the y-axis going south so we need to multiply by negative one.
243
- setTargetY(-newTargetY);
244
- setZoom(newZoom);
268
+ const notYetInitialized = (typeof targetX !== 'number' || typeof targetY !== 'number');
269
+ const stillDefaultInitialized = (targetX === newTargetX && targetY === -newTargetY);
270
+ if (notYetInitialized || stillDefaultInitialized) {
271
+ setTargetX(newTargetX);
272
+ // Graphics rendering has the y-axis going south so we need to multiply by negative one.
273
+ setTargetY(-newTargetY);
274
+ setZoom(newZoom);
275
+ }
276
+ setOriginalViewState({ target: [newTargetX, -newTargetY, 0], zoom: newZoom });
277
+ } else if (!originalViewState) {
278
+ // originalViewState has not yet been set and
279
+ // the view config defined an initial viewState.
280
+ setOriginalViewState({ target: [initialTargetX, initialTargetY, 0], zoom: initialZoom });
245
281
  }
246
282
  }
247
283
  // eslint-disable-next-line react-hooks/exhaustive-deps
248
284
  }, [xRange, yRange, xExtent, yExtent, numCells,
249
- width, height, zoom, averageFillDensity]);
285
+ width, height, initialZoom, zoom, initialTargetX, initialTargetY, averageFillDensity]);
250
286
 
251
287
  const getObsInfo = useGetObsInfo(
252
288
  observationsLabel, obsLabelsTypes, obsLabelsData, obsSetsMembership,
@@ -260,14 +296,23 @@ export function EmbeddingScatterplotSubscriber(props) {
260
296
  const cellRadius = (cellRadiusMode === 'manual' ? cellRadiusFixed : dynamicCellRadius);
261
297
  const cellOpacity = (cellOpacityMode === 'manual' ? cellOpacityFixed : dynamicCellOpacity);
262
298
 
299
+ const [uint8ExpressionData, expressionExtents] = useUint8FeatureSelection(expressionData);
300
+
263
301
  // Set up a getter function for gene expression values, to be used
264
302
  // by the DeckGL layer to obtain values for instanced attributes.
265
303
  const getExpressionValue = useExpressionValueGetter({
266
304
  instanceObsIndex: obsEmbeddingIndex,
267
305
  matrixObsIndex,
268
- expressionData,
306
+ expressionData: uint8ExpressionData,
269
307
  });
270
308
 
309
+ const setViewState = ({ zoom: newZoom, target }) => {
310
+ setZoom(newZoom);
311
+ setTargetX(target[0]);
312
+ setTargetY(target[1]);
313
+ setTargetZ(target[2] || 0);
314
+ };
315
+
271
316
  return (
272
317
  <TitleInfo
273
318
  title={title}
@@ -289,6 +334,8 @@ export function EmbeddingScatterplotSubscriber(props) {
289
334
  setCellOpacityMode={setCellOpacityMode}
290
335
  cellSetLabelsVisible={cellSetLabelsVisible}
291
336
  setCellSetLabelsVisible={setCellSetLabelsVisible}
337
+ tooltipsVisible={tooltipsVisible}
338
+ setTooltipsVisible={setTooltipsVisible}
292
339
  cellSetLabelSize={cellSetLabelSize}
293
340
  setCellSetLabelSize={setCellSetLabelSize}
294
341
  cellSetPolygonsVisible={cellSetPolygonsVisible}
@@ -307,12 +354,8 @@ export function EmbeddingScatterplotSubscriber(props) {
307
354
  uuid={uuid}
308
355
  theme={theme}
309
356
  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
- }}
357
+ setViewState={setViewState}
358
+ originalViewState={originalViewState}
316
359
  obsEmbeddingIndex={obsEmbeddingIndex}
317
360
  obsEmbedding={obsEmbedding}
318
361
  cellFilter={cellFilter}
@@ -339,7 +382,7 @@ export function EmbeddingScatterplotSubscriber(props) {
339
382
  getCellIsSelected={getCellIsSelected}
340
383
 
341
384
  />
342
- {!disableTooltip && (
385
+ {tooltipsVisible && (
343
386
  <ScatterplotTooltipSubscriber
344
387
  parentUuid={uuid}
345
388
  obsHighlight={cellHighlight}
@@ -348,14 +391,18 @@ export function EmbeddingScatterplotSubscriber(props) {
348
391
  getObsInfo={getObsInfo}
349
392
  />
350
393
  )}
394
+ <Legend
395
+ visible
396
+ theme={theme}
397
+ featureType={featureType}
398
+ featureValueType={featureValueType}
399
+ obsColorEncoding={cellColorEncoding}
400
+ featureSelection={geneSelection}
401
+ featureLabelsMap={featureLabelsMap}
402
+ featureValueColormap={geneExpressionColormap}
403
+ featureValueColormapRange={geneExpressionColormapRange}
404
+ extent={expressionExtents?.[0]}
405
+ />
351
406
  </TitleInfo>
352
407
  );
353
408
  }
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
- };