@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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { E } from "./index-0e886311.js";
1
+ import { E } from "./index-773a6157.js";
2
2
  import "react";
3
3
  import "@vitessce/vit-s";
4
4
  import "react-dom";
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0e886311.js";
1
+ import { B as BaseDecoder } from "./index-773a6157.js";
2
2
  import "react";
3
3
  import "@vitessce/vit-s";
4
4
  import "react-dom";
@@ -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-0e886311.js";
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,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0e886311.js";
1
+ import { B as BaseDecoder } from "./index-773a6157.js";
2
2
  import "react";
3
3
  import "@vitessce/vit-s";
4
4
  import "react-dom";
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0e886311.js";
1
+ import { B as BaseDecoder } from "./index-773a6157.js";
2
2
  import "react";
3
3
  import "@vitessce/vit-s";
4
4
  import "react-dom";
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0e886311.js";
1
+ import { B as BaseDecoder } from "./index-773a6157.js";
2
2
  import "react";
3
3
  import "@vitessce/vit-s";
4
4
  import "react-dom";
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-0e886311.js";
1
+ import { B as BaseDecoder } from "./index-773a6157.js";
2
2
  import "react";
3
3
  import "@vitessce/vit-s";
4
4
  import "react-dom";
@@ -1 +1 @@
1
- {"version":3,"file":"EmbeddingScatterplotSubscriber.d.ts","sourceRoot":"","sources":["../src/EmbeddingScatterplotSubscriber.js"],"names":[],"mappings":"AAoCA;;;;;;;;;;;;GAYG;AACH;IAVyB,IAAI,EAAlB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;IAEU,mBAAmB;IAErB,KAAK,EAAnB,MAAM;IACQ,kBAAkB,EAAhC,MAAM;gBA8WhB"}
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 }), 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] })] }));
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,2 @@
1
+ export const DEFAULT_CONTOUR_PERCENTILES: number[];
2
+ //# sourceMappingURL=constants.d.ts.map
@@ -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.6",
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/legend": "3.4.6",
24
- "@vitessce/utils": "3.4.6",
25
- "@vitessce/scatterplot": "3.4.6",
26
- "@vitessce/sets-utils": "3.4.6",
27
- "@vitessce/vit-s": "3.4.6",
28
- "@vitessce/constants-internal": "3.4.6"
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
  );
@@ -0,0 +1 @@
1
+ export const DEFAULT_CONTOUR_PERCENTILES = [0.09, 0.9, 0.99];