@vitessce/scatterplot-embedding 3.4.6 → 3.4.8
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-79df35ef.js → deflate-3cea0656.js} +1 -1
- package/dist/{index-0e886311.js → index-773a6157.js} +6435 -6646
- package/dist/index.js +1 -1
- package/dist/{jpeg-e78923e1.js → jpeg-804eb1f0.js} +1 -1
- package/dist/{lerc-6a2fa149.js → lerc-2b14ee6b.js} +1 -1
- package/dist/{lzw-a9b3350a.js → lzw-973ca3f2.js} +1 -1
- package/dist/{packbits-1e1b593d.js → packbits-bfe475e6.js} +1 -1
- package/dist/{raw-517f7071.js → raw-42ff0533.js} +1 -1
- package/dist/{webimage-fd28665b.js → webimage-8445b416.js} +1 -1
- package/dist-tsc/EmbeddingScatterplotSubscriber.d.ts.map +1 -1
- package/dist-tsc/EmbeddingScatterplotSubscriber.js +101 -6
- package/dist-tsc/constants.d.ts +2 -0
- package/dist-tsc/constants.d.ts.map +1 -0
- package/dist-tsc/constants.js +1 -0
- package/package.json +7 -7
- package/src/EmbeddingScatterplotSubscriber.js +156 -1
- package/src/constants.js +1 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as inflate_1 } from "./pako.esm-68f84e2a.js";
|
|
2
|
-
import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-
|
|
2
|
+
import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-773a6157.js";
|
|
3
3
|
import "react";
|
|
4
4
|
import "@vitessce/vit-s";
|
|
5
5
|
import "react-dom";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmbeddingScatterplotSubscriber.d.ts","sourceRoot":"","sources":["../src/EmbeddingScatterplotSubscriber.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EmbeddingScatterplotSubscriber.d.ts","sourceRoot":"","sources":["../src/EmbeddingScatterplotSubscriber.js"],"names":[],"mappings":"AAyCA;;;;;;;;;;;;GAYG;AACH,sDAVG;IAAsB,IAAI,EAAlB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;IAEU,mBAAmB;IAErB,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;CAEhB,eAkgBA"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useEffect, useCallback, useMemo, } from 'react';
|
|
3
|
-
import { extent } from 'd3-array';
|
|
3
|
+
import { extent, quantileSorted } from 'd3-array';
|
|
4
4
|
import { isEqual } from 'lodash-es';
|
|
5
|
-
import { TitleInfo, useReady, useUrls, useDeckCanvasSize, useUint8FeatureSelection, useExpressionValueGetter, useGetObsInfo, useObsEmbeddingData, useObsSetsData, useFeatureSelection, useObsFeatureMatrixIndices, useFeatureLabelsData, useMultiObsLabels, useCoordination, useLoaders, useSetComponentHover, useSetComponentViewInfo, useInitialCoordination, } from '@vitessce/vit-s';
|
|
6
|
-
import { setObsSelection, mergeObsSets, getCellSetPolygons, getCellColors, } from '@vitessce/sets-utils';
|
|
5
|
+
import { TitleInfo, useReady, useUrls, useDeckCanvasSize, useUint8FeatureSelection, useExpressionValueGetter, useGetObsInfo, useObsEmbeddingData, useObsSetsData, useFeatureSelection, useObsFeatureMatrixIndices, useFeatureLabelsData, useMultiObsLabels, useSampleSetsData, useSampleEdgesData, useCoordination, useLoaders, useSetComponentHover, useSetComponentViewInfo, useInitialCoordination, } from '@vitessce/vit-s';
|
|
6
|
+
import { setObsSelection, mergeObsSets, getCellSetPolygons, getCellColors, stratifyArrays, } from '@vitessce/sets-utils';
|
|
7
7
|
import { pluralize as plur, commaNumber } from '@vitessce/utils';
|
|
8
8
|
import { Scatterplot, ScatterplotTooltipSubscriber, ScatterplotOptions, getPointSizeDevicePixels, getPointOpacity, } from '@vitessce/scatterplot';
|
|
9
9
|
import { Legend } from '@vitessce/legend';
|
|
10
10
|
import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
|
|
11
|
+
import { DEFAULT_CONTOUR_PERCENTILES } from './constants.js';
|
|
11
12
|
/**
|
|
12
13
|
* A subscriber component for the scatterplot.
|
|
13
14
|
* @param {object} props
|
|
@@ -29,7 +30,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
29
30
|
const setComponentHover = useSetComponentHover();
|
|
30
31
|
const setComponentViewInfo = useSetComponentViewInfo(uuid);
|
|
31
32
|
// Get "props" from the coordination space.
|
|
32
|
-
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);
|
|
33
|
+
const [{ dataset, obsType, featureType, featureValueType, sampleType, 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, sampleSetSelection, sampleSetColor, embeddingPointsVisible, embeddingContoursVisible, embeddingContoursFilled, embeddingContourPercentiles: contourPercentiles, contourColorEncoding, contourColor, }, { 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, setEmbeddingPointsVisible, setEmbeddingContoursVisible, setEmbeddingContoursFilled, setEmbeddingContourPercentiles: setContourPercentiles, setContourColorEncoding, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT], coordinationScopes);
|
|
33
34
|
const { embeddingZoom: initialZoom, embeddingTargetX: initialTargetX, embeddingTargetY: initialTargetY, } = useInitialCoordination(COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT], coordinationScopes);
|
|
34
35
|
const observationsLabel = observationsLabelOverride || obsType;
|
|
35
36
|
const [width, height, deckRef] = useDeckCanvasSize();
|
|
@@ -43,18 +44,24 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
43
44
|
const [expressionData, loadedFeatureSelection, featureSelectionStatus] = useFeatureSelection(loaders, dataset, false, geneSelection, { obsType, featureType, featureValueType });
|
|
44
45
|
const [{ obsIndex: matrixObsIndex }, matrixIndicesStatus, matrixIndicesUrls,] = useObsFeatureMatrixIndices(loaders, dataset, false, { obsType, featureType, featureValueType });
|
|
45
46
|
const [{ featureLabelsMap }, featureLabelsStatus, featureLabelsUrls] = useFeatureLabelsData(loaders, dataset, false, {}, {}, { featureType });
|
|
47
|
+
const [{ sampleSets }, sampleSetsStatus, sampleSetsUrl] = useSampleSetsData(loaders, dataset, false, {}, {}, { sampleType });
|
|
48
|
+
const [{ sampleEdges }, sampleEdgesStatus, sampleEdgesUrl] = useSampleEdgesData(loaders, dataset, false, {}, {}, { obsType, sampleType });
|
|
46
49
|
const isReady = useReady([
|
|
47
50
|
obsEmbeddingStatus,
|
|
48
51
|
obsSetsStatus,
|
|
49
52
|
featureSelectionStatus,
|
|
50
53
|
featureLabelsStatus,
|
|
51
54
|
matrixIndicesStatus,
|
|
55
|
+
sampleSetsStatus,
|
|
56
|
+
sampleEdgesStatus,
|
|
52
57
|
]);
|
|
53
58
|
const urls = useUrls([
|
|
54
59
|
obsEmbeddingUrls,
|
|
55
60
|
obsSetsUrls,
|
|
56
61
|
matrixIndicesUrls,
|
|
57
62
|
featureLabelsUrls,
|
|
63
|
+
sampleSetsUrl,
|
|
64
|
+
sampleEdgesUrl,
|
|
58
65
|
]);
|
|
59
66
|
const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
|
|
60
67
|
const [dynamicCellOpacity, setDynamicCellOpacity] = useState(cellOpacityFixed);
|
|
@@ -157,13 +164,101 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
157
164
|
matrixObsIndex,
|
|
158
165
|
expressionData: uint8ExpressionData,
|
|
159
166
|
});
|
|
167
|
+
// Sort the expression data array so that we can compute percentiles
|
|
168
|
+
// using the d3 quantileSorted function for improved performance.
|
|
169
|
+
const sortedWeights = useMemo(() => {
|
|
170
|
+
if (uint8ExpressionData?.[0]) {
|
|
171
|
+
const weights = uint8ExpressionData[0];
|
|
172
|
+
return weights.toSorted();
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}, [uint8ExpressionData]);
|
|
176
|
+
// Compute contour thresholds based on the entire expression data distribution
|
|
177
|
+
// (not per-cellSet or per-sampleSet).
|
|
178
|
+
const contourThresholds = useMemo(() => {
|
|
179
|
+
if (sortedWeights) {
|
|
180
|
+
const thresholds = (contourPercentiles || DEFAULT_CONTOUR_PERCENTILES)
|
|
181
|
+
.map(p => quantileSorted(sortedWeights, p))
|
|
182
|
+
.map(t => Math.max(t, 1.0));
|
|
183
|
+
return thresholds;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}, [contourPercentiles, sortedWeights]);
|
|
187
|
+
// It is possible for the embedding index+data to be out of order
|
|
188
|
+
// with respect to the matrix index+data. Here, we align the embedding
|
|
189
|
+
// data so that the rows are ordered the same as the matrix rows.
|
|
190
|
+
// TODO: refactor this as a hook that can be used elsewhere to align data
|
|
191
|
+
// from different data types with the expression matrix data.
|
|
192
|
+
// Need to fallback to the original ordering if no matrix data is present.
|
|
193
|
+
// TODO: do this everywhere and remove the need for the
|
|
194
|
+
// useExpressionValueGetter hook and getter function.
|
|
195
|
+
const [alignedEmbeddingIndex, alignedEmbeddingData] = useMemo(() => {
|
|
196
|
+
// Sort the embedding data according to the matrix obsIndex.
|
|
197
|
+
if (obsEmbedding?.data && obsEmbeddingIndex && matrixObsIndex) {
|
|
198
|
+
const matrixIndexMap = new Map(matrixObsIndex.map((key, i) => ([key, i])));
|
|
199
|
+
const toMatrixIndex = obsEmbeddingIndex.map(key => matrixIndexMap.get(key));
|
|
200
|
+
const newEmbeddingIndex = new Array(obsEmbeddingIndex.length);
|
|
201
|
+
const newEmbeddingData = [
|
|
202
|
+
new obsEmbedding.data[0].constructor(obsEmbedding.data[0].length),
|
|
203
|
+
new obsEmbedding.data[1].constructor(obsEmbedding.data[1].length),
|
|
204
|
+
];
|
|
205
|
+
for (let i = 0; i < obsEmbeddingIndex.length; i++) {
|
|
206
|
+
const matrixRowIndex = toMatrixIndex[i];
|
|
207
|
+
newEmbeddingData[0][matrixRowIndex] = obsEmbedding.data[0][i];
|
|
208
|
+
newEmbeddingData[1][matrixRowIndex] = obsEmbedding.data[1][i];
|
|
209
|
+
newEmbeddingIndex[matrixRowIndex] = obsEmbeddingIndex[i];
|
|
210
|
+
}
|
|
211
|
+
return [newEmbeddingIndex, { ...obsEmbedding, data: newEmbeddingData }];
|
|
212
|
+
}
|
|
213
|
+
// Fall back to original ordering if no matrix data is present to align with.
|
|
214
|
+
return [obsEmbeddingIndex, obsEmbedding];
|
|
215
|
+
}, [matrixObsIndex, obsEmbeddingIndex, obsEmbedding]);
|
|
216
|
+
const sampleIdToObsIdsMap = useMemo(() => {
|
|
217
|
+
// sampleEdges maps obsId -> sampleId.
|
|
218
|
+
// However when we stratify we want to map sampleId -> [obsId1, obsId2, ...].
|
|
219
|
+
// Here we create this reverse mapping.
|
|
220
|
+
if (sampleEdges) {
|
|
221
|
+
const result = new Map();
|
|
222
|
+
Array.from(sampleEdges.entries()).forEach(([obsId, sampleId]) => {
|
|
223
|
+
if (!result.has(sampleId)) {
|
|
224
|
+
result.set(sampleId, [obsId]);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
result.get(sampleId).push(obsId);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}, [sampleEdges]);
|
|
234
|
+
// Stratify multiple arrays: per-cellSet and per-sampleSet.
|
|
235
|
+
const stratifiedData = useMemo(() => {
|
|
236
|
+
if (alignedEmbeddingData?.data) {
|
|
237
|
+
const result = stratifyArrays(sampleEdges, sampleIdToObsIdsMap, sampleSets, sampleSetSelection, alignedEmbeddingIndex, mergedCellSets, cellSetSelection, {
|
|
238
|
+
obsEmbeddingX: alignedEmbeddingData.data[0],
|
|
239
|
+
obsEmbeddingY: alignedEmbeddingData.data[1],
|
|
240
|
+
// TODO: aggregate and transform expression data if needed prior to passing here
|
|
241
|
+
...(uint8ExpressionData?.[0] ? { featureValue: uint8ExpressionData?.[0] } : {}),
|
|
242
|
+
});
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}, [alignedEmbeddingIndex, alignedEmbeddingData, uint8ExpressionData,
|
|
247
|
+
sampleEdges, sampleIdToObsIdsMap, sampleSets, sampleSetSelection,
|
|
248
|
+
cellSetSelection, mergedCellSets,
|
|
249
|
+
]);
|
|
160
250
|
const setViewState = ({ zoom: newZoom, target }) => {
|
|
161
251
|
setZoom(newZoom);
|
|
162
252
|
setTargetX(target[0]);
|
|
163
253
|
setTargetY(target[1]);
|
|
164
254
|
setTargetZ(target[2] || 0);
|
|
165
255
|
};
|
|
166
|
-
return (_jsxs(TitleInfo, { title: title, info: `${commaNumber(cellsCount)} ${plur(observationsLabel, cellsCount)}`, closeButtonVisible: closeButtonVisible, downloadButtonVisible: downloadButtonVisible, 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: () => {
|
|
256
|
+
return (_jsxs(TitleInfo, { title: title, info: `${commaNumber(cellsCount)} ${plur(observationsLabel, cellsCount)}`, closeButtonVisible: closeButtonVisible, downloadButtonVisible: downloadButtonVisible, 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, embeddingPointsVisible: embeddingPointsVisible, setEmbeddingPointsVisible: setEmbeddingPointsVisible, embeddingContoursVisible: embeddingContoursVisible, setEmbeddingContoursVisible: setEmbeddingContoursVisible, embeddingContoursFilled: embeddingContoursFilled, setEmbeddingContoursFilled: setEmbeddingContoursFilled, contourPercentiles: contourPercentiles, setContourPercentiles: setContourPercentiles, defaultContourPercentiles: DEFAULT_CONTOUR_PERCENTILES, contourColorEncoding: contourColorEncoding, setContourColorEncoding: setContourColorEncoding })), 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: () => {
|
|
167
257
|
setComponentHover(uuid);
|
|
168
|
-
}, updateViewInfo: setComponentViewInfo, getExpressionValue: getExpressionValue, getCellIsSelected: getCellIsSelected
|
|
258
|
+
}, updateViewInfo: setComponentViewInfo, getExpressionValue: getExpressionValue, getCellIsSelected: getCellIsSelected, obsSetSelection: cellSetSelection, sampleSetSelection: sampleSetSelection,
|
|
259
|
+
// InternMap data structures where keys are
|
|
260
|
+
// obsSet -> sampleSet -> arrayKey -> [].
|
|
261
|
+
stratifiedData: stratifiedData, obsSetColor: cellSetColor, sampleSetColor: sampleSetColor, contourThresholds: contourThresholds, contourColorEncoding: contourColorEncoding, contourColor: contourColor, contoursFilled: embeddingContoursFilled, embeddingPointsVisible: embeddingPointsVisible, embeddingContoursVisible: embeddingContoursVisible }), 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],
|
|
262
|
+
// Contour percentile legend
|
|
263
|
+
pointsVisible: embeddingPointsVisible, contoursVisible: embeddingContoursVisible, contoursFilled: embeddingContoursFilled, contourPercentiles: contourPercentiles || DEFAULT_CONTOUR_PERCENTILES, contourThresholds: contourThresholds })] }));
|
|
169
264
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.js"],"names":[],"mappings":"AAAA,mDAA6D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_CONTOUR_PERCENTILES = [0.09, 0.9, 0.99];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/scatterplot-embedding",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.8",
|
|
4
4
|
"author": "Gehlenborg Lab",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"d3-array": "^2.4.0",
|
|
21
21
|
"lodash-es": "^4.17.21",
|
|
22
22
|
"react-aria": "^3.28.0",
|
|
23
|
-
"@vitessce/
|
|
24
|
-
"@vitessce/
|
|
25
|
-
"@vitessce/scatterplot": "3.4.
|
|
26
|
-
"@vitessce/sets-utils": "3.4.
|
|
27
|
-
"@vitessce/
|
|
28
|
-
"@vitessce/
|
|
23
|
+
"@vitessce/constants-internal": "3.4.8",
|
|
24
|
+
"@vitessce/legend": "3.4.8",
|
|
25
|
+
"@vitessce/scatterplot": "3.4.8",
|
|
26
|
+
"@vitessce/sets-utils": "3.4.8",
|
|
27
|
+
"@vitessce/utils": "3.4.8",
|
|
28
|
+
"@vitessce/vit-s": "3.4.8"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"react": "^18.0.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, {
|
|
2
2
|
useState, useEffect, useCallback, useMemo,
|
|
3
3
|
} from 'react';
|
|
4
|
-
import { extent } from 'd3-array';
|
|
4
|
+
import { extent, quantileSorted } from 'd3-array';
|
|
5
5
|
import { isEqual } from 'lodash-es';
|
|
6
6
|
import {
|
|
7
7
|
TitleInfo,
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
useObsFeatureMatrixIndices,
|
|
17
17
|
useFeatureLabelsData,
|
|
18
18
|
useMultiObsLabels,
|
|
19
|
+
useSampleSetsData,
|
|
20
|
+
useSampleEdgesData,
|
|
19
21
|
useCoordination,
|
|
20
22
|
useLoaders,
|
|
21
23
|
useSetComponentHover,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
} from '@vitessce/vit-s';
|
|
25
27
|
import {
|
|
26
28
|
setObsSelection, mergeObsSets, getCellSetPolygons, getCellColors,
|
|
29
|
+
stratifyArrays,
|
|
27
30
|
} from '@vitessce/sets-utils';
|
|
28
31
|
import { pluralize as plur, commaNumber } from '@vitessce/utils';
|
|
29
32
|
import {
|
|
@@ -33,6 +36,8 @@ import {
|
|
|
33
36
|
} from '@vitessce/scatterplot';
|
|
34
37
|
import { Legend } from '@vitessce/legend';
|
|
35
38
|
import { ViewType, COMPONENT_COORDINATION_TYPES } from '@vitessce/constants-internal';
|
|
39
|
+
import { DEFAULT_CONTOUR_PERCENTILES } from './constants.js';
|
|
40
|
+
|
|
36
41
|
|
|
37
42
|
/**
|
|
38
43
|
* A subscriber component for the scatterplot.
|
|
@@ -71,6 +76,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
71
76
|
obsType,
|
|
72
77
|
featureType,
|
|
73
78
|
featureValueType,
|
|
79
|
+
sampleType,
|
|
74
80
|
embeddingZoom: zoom,
|
|
75
81
|
embeddingTargetX: targetX,
|
|
76
82
|
embeddingTargetY: targetY,
|
|
@@ -93,6 +99,14 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
93
99
|
featureValueColormap: geneExpressionColormap,
|
|
94
100
|
featureValueColormapRange: geneExpressionColormapRange,
|
|
95
101
|
tooltipsVisible,
|
|
102
|
+
sampleSetSelection,
|
|
103
|
+
sampleSetColor,
|
|
104
|
+
embeddingPointsVisible,
|
|
105
|
+
embeddingContoursVisible,
|
|
106
|
+
embeddingContoursFilled,
|
|
107
|
+
embeddingContourPercentiles: contourPercentiles,
|
|
108
|
+
contourColorEncoding,
|
|
109
|
+
contourColor,
|
|
96
110
|
}, {
|
|
97
111
|
setEmbeddingZoom: setZoom,
|
|
98
112
|
setEmbeddingTargetX: setTargetX,
|
|
@@ -114,6 +128,11 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
114
128
|
setFeatureValueColormap: setGeneExpressionColormap,
|
|
115
129
|
setFeatureValueColormapRange: setGeneExpressionColormapRange,
|
|
116
130
|
setTooltipsVisible,
|
|
131
|
+
setEmbeddingPointsVisible,
|
|
132
|
+
setEmbeddingContoursVisible,
|
|
133
|
+
setEmbeddingContoursFilled,
|
|
134
|
+
setEmbeddingContourPercentiles: setContourPercentiles,
|
|
135
|
+
setContourColorEncoding,
|
|
117
136
|
}] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.SCATTERPLOT], coordinationScopes);
|
|
118
137
|
|
|
119
138
|
const {
|
|
@@ -164,18 +183,32 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
164
183
|
{ featureType },
|
|
165
184
|
);
|
|
166
185
|
|
|
186
|
+
const [{ sampleSets }, sampleSetsStatus, sampleSetsUrl] = useSampleSetsData(
|
|
187
|
+
loaders, dataset, false, {}, {},
|
|
188
|
+
{ sampleType },
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const [{ sampleEdges }, sampleEdgesStatus, sampleEdgesUrl] = useSampleEdgesData(
|
|
192
|
+
loaders, dataset, false, {}, {},
|
|
193
|
+
{ obsType, sampleType },
|
|
194
|
+
);
|
|
195
|
+
|
|
167
196
|
const isReady = useReady([
|
|
168
197
|
obsEmbeddingStatus,
|
|
169
198
|
obsSetsStatus,
|
|
170
199
|
featureSelectionStatus,
|
|
171
200
|
featureLabelsStatus,
|
|
172
201
|
matrixIndicesStatus,
|
|
202
|
+
sampleSetsStatus,
|
|
203
|
+
sampleEdgesStatus,
|
|
173
204
|
]);
|
|
174
205
|
const urls = useUrls([
|
|
175
206
|
obsEmbeddingUrls,
|
|
176
207
|
obsSetsUrls,
|
|
177
208
|
matrixIndicesUrls,
|
|
178
209
|
featureLabelsUrls,
|
|
210
|
+
sampleSetsUrl,
|
|
211
|
+
sampleEdgesUrl,
|
|
179
212
|
]);
|
|
180
213
|
|
|
181
214
|
const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
|
|
@@ -309,6 +342,98 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
309
342
|
expressionData: uint8ExpressionData,
|
|
310
343
|
});
|
|
311
344
|
|
|
345
|
+
// Sort the expression data array so that we can compute percentiles
|
|
346
|
+
// using the d3 quantileSorted function for improved performance.
|
|
347
|
+
const sortedWeights = useMemo(() => {
|
|
348
|
+
if (uint8ExpressionData?.[0]) {
|
|
349
|
+
const weights = uint8ExpressionData[0];
|
|
350
|
+
return weights.toSorted();
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}, [uint8ExpressionData]);
|
|
354
|
+
|
|
355
|
+
// Compute contour thresholds based on the entire expression data distribution
|
|
356
|
+
// (not per-cellSet or per-sampleSet).
|
|
357
|
+
const contourThresholds = useMemo(() => {
|
|
358
|
+
if (sortedWeights) {
|
|
359
|
+
const thresholds = (contourPercentiles || DEFAULT_CONTOUR_PERCENTILES)
|
|
360
|
+
.map(p => quantileSorted(sortedWeights, p))
|
|
361
|
+
.map(t => Math.max(t, 1.0));
|
|
362
|
+
return thresholds;
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}, [contourPercentiles, sortedWeights]);
|
|
366
|
+
|
|
367
|
+
// It is possible for the embedding index+data to be out of order
|
|
368
|
+
// with respect to the matrix index+data. Here, we align the embedding
|
|
369
|
+
// data so that the rows are ordered the same as the matrix rows.
|
|
370
|
+
// TODO: refactor this as a hook that can be used elsewhere to align data
|
|
371
|
+
// from different data types with the expression matrix data.
|
|
372
|
+
// Need to fallback to the original ordering if no matrix data is present.
|
|
373
|
+
// TODO: do this everywhere and remove the need for the
|
|
374
|
+
// useExpressionValueGetter hook and getter function.
|
|
375
|
+
const [alignedEmbeddingIndex, alignedEmbeddingData] = useMemo(() => {
|
|
376
|
+
// Sort the embedding data according to the matrix obsIndex.
|
|
377
|
+
if (obsEmbedding?.data && obsEmbeddingIndex && matrixObsIndex) {
|
|
378
|
+
const matrixIndexMap = new Map(matrixObsIndex.map((key, i) => ([key, i])));
|
|
379
|
+
const toMatrixIndex = obsEmbeddingIndex.map(key => matrixIndexMap.get(key));
|
|
380
|
+
|
|
381
|
+
const newEmbeddingIndex = new Array(obsEmbeddingIndex.length);
|
|
382
|
+
const newEmbeddingData = [
|
|
383
|
+
new obsEmbedding.data[0].constructor(obsEmbedding.data[0].length),
|
|
384
|
+
new obsEmbedding.data[1].constructor(obsEmbedding.data[1].length),
|
|
385
|
+
];
|
|
386
|
+
for (let i = 0; i < obsEmbeddingIndex.length; i++) {
|
|
387
|
+
const matrixRowIndex = toMatrixIndex[i];
|
|
388
|
+
newEmbeddingData[0][matrixRowIndex] = obsEmbedding.data[0][i];
|
|
389
|
+
newEmbeddingData[1][matrixRowIndex] = obsEmbedding.data[1][i];
|
|
390
|
+
newEmbeddingIndex[matrixRowIndex] = obsEmbeddingIndex[i];
|
|
391
|
+
}
|
|
392
|
+
return [newEmbeddingIndex, { ...obsEmbedding, data: newEmbeddingData }];
|
|
393
|
+
}
|
|
394
|
+
// Fall back to original ordering if no matrix data is present to align with.
|
|
395
|
+
return [obsEmbeddingIndex, obsEmbedding];
|
|
396
|
+
}, [matrixObsIndex, obsEmbeddingIndex, obsEmbedding]);
|
|
397
|
+
|
|
398
|
+
const sampleIdToObsIdsMap = useMemo(() => {
|
|
399
|
+
// sampleEdges maps obsId -> sampleId.
|
|
400
|
+
// However when we stratify we want to map sampleId -> [obsId1, obsId2, ...].
|
|
401
|
+
// Here we create this reverse mapping.
|
|
402
|
+
if (sampleEdges) {
|
|
403
|
+
const result = new Map();
|
|
404
|
+
Array.from(sampleEdges.entries()).forEach(([obsId, sampleId]) => {
|
|
405
|
+
if (!result.has(sampleId)) {
|
|
406
|
+
result.set(sampleId, [obsId]);
|
|
407
|
+
} else {
|
|
408
|
+
result.get(sampleId).push(obsId);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}, [sampleEdges]);
|
|
415
|
+
|
|
416
|
+
// Stratify multiple arrays: per-cellSet and per-sampleSet.
|
|
417
|
+
const stratifiedData = useMemo(() => {
|
|
418
|
+
if (alignedEmbeddingData?.data) {
|
|
419
|
+
const result = stratifyArrays(
|
|
420
|
+
sampleEdges, sampleIdToObsIdsMap,
|
|
421
|
+
sampleSets, sampleSetSelection,
|
|
422
|
+
alignedEmbeddingIndex, mergedCellSets, cellSetSelection, {
|
|
423
|
+
obsEmbeddingX: alignedEmbeddingData.data[0],
|
|
424
|
+
obsEmbeddingY: alignedEmbeddingData.data[1],
|
|
425
|
+
// TODO: aggregate and transform expression data if needed prior to passing here
|
|
426
|
+
...(uint8ExpressionData?.[0] ? { featureValue: uint8ExpressionData?.[0] } : {}),
|
|
427
|
+
},
|
|
428
|
+
);
|
|
429
|
+
return result;
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}, [alignedEmbeddingIndex, alignedEmbeddingData, uint8ExpressionData,
|
|
433
|
+
sampleEdges, sampleIdToObsIdsMap, sampleSets, sampleSetSelection,
|
|
434
|
+
cellSetSelection, mergedCellSets,
|
|
435
|
+
]);
|
|
436
|
+
|
|
312
437
|
const setViewState = ({ zoom: newZoom, target }) => {
|
|
313
438
|
setZoom(newZoom);
|
|
314
439
|
setTargetX(target[0]);
|
|
@@ -351,6 +476,17 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
351
476
|
setGeneExpressionColormap={setGeneExpressionColormap}
|
|
352
477
|
geneExpressionColormapRange={geneExpressionColormapRange}
|
|
353
478
|
setGeneExpressionColormapRange={setGeneExpressionColormapRange}
|
|
479
|
+
embeddingPointsVisible={embeddingPointsVisible}
|
|
480
|
+
setEmbeddingPointsVisible={setEmbeddingPointsVisible}
|
|
481
|
+
embeddingContoursVisible={embeddingContoursVisible}
|
|
482
|
+
setEmbeddingContoursVisible={setEmbeddingContoursVisible}
|
|
483
|
+
embeddingContoursFilled={embeddingContoursFilled}
|
|
484
|
+
setEmbeddingContoursFilled={setEmbeddingContoursFilled}
|
|
485
|
+
contourPercentiles={contourPercentiles}
|
|
486
|
+
setContourPercentiles={setContourPercentiles}
|
|
487
|
+
defaultContourPercentiles={DEFAULT_CONTOUR_PERCENTILES}
|
|
488
|
+
contourColorEncoding={contourColorEncoding}
|
|
489
|
+
setContourColorEncoding={setContourColorEncoding}
|
|
354
490
|
/>
|
|
355
491
|
)}
|
|
356
492
|
>
|
|
@@ -386,6 +522,19 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
386
522
|
getExpressionValue={getExpressionValue}
|
|
387
523
|
getCellIsSelected={getCellIsSelected}
|
|
388
524
|
|
|
525
|
+
obsSetSelection={cellSetSelection}
|
|
526
|
+
sampleSetSelection={sampleSetSelection}
|
|
527
|
+
// InternMap data structures where keys are
|
|
528
|
+
// obsSet -> sampleSet -> arrayKey -> [].
|
|
529
|
+
stratifiedData={stratifiedData}
|
|
530
|
+
obsSetColor={cellSetColor}
|
|
531
|
+
sampleSetColor={sampleSetColor}
|
|
532
|
+
contourThresholds={contourThresholds}
|
|
533
|
+
contourColorEncoding={contourColorEncoding}
|
|
534
|
+
contourColor={contourColor}
|
|
535
|
+
contoursFilled={embeddingContoursFilled}
|
|
536
|
+
embeddingPointsVisible={embeddingPointsVisible}
|
|
537
|
+
embeddingContoursVisible={embeddingContoursVisible}
|
|
389
538
|
/>
|
|
390
539
|
{tooltipsVisible && (
|
|
391
540
|
<ScatterplotTooltipSubscriber
|
|
@@ -407,6 +556,12 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
407
556
|
featureValueColormap={geneExpressionColormap}
|
|
408
557
|
featureValueColormapRange={geneExpressionColormapRange}
|
|
409
558
|
extent={expressionExtents?.[0]}
|
|
559
|
+
// Contour percentile legend
|
|
560
|
+
pointsVisible={embeddingPointsVisible}
|
|
561
|
+
contoursVisible={embeddingContoursVisible}
|
|
562
|
+
contoursFilled={embeddingContoursFilled}
|
|
563
|
+
contourPercentiles={contourPercentiles || DEFAULT_CONTOUR_PERCENTILES}
|
|
564
|
+
contourThresholds={contourThresholds}
|
|
410
565
|
/>
|
|
411
566
|
</TitleInfo>
|
|
412
567
|
);
|
package/src/constants.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_CONTOUR_PERCENTILES = [0.09, 0.9, 0.99];
|