@vitessce/scatterplot-embedding 4.0.4 → 4.0.5
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-C2BJz3Np.js → deflate-aaOFaah9.js} +1 -1
- package/dist/{index-5HRu2Z3O.js → index-QPcBcBv4.js} +419 -5447
- package/dist/index.js +1 -1
- package/dist/{jpeg-BDB9TI7a.js → jpeg-0GPV7wHB.js} +1 -1
- package/dist/{lerc-CDQiFN0R.js → lerc-b0eKtxKR.js} +1 -1
- package/dist/{lzw-D7BZ1wcC.js → lzw-BdtUMX-Z.js} +1 -1
- package/dist/{packbits-CE3TkByu.js → packbits-XKIpoMsP.js} +1 -1
- package/dist/{raw-Dafa0Swn.js → raw-gRNpvpQg.js} +1 -1
- package/dist/{webimage-B4Leod1Q.js → webimage-CbAzpg9y.js} +1 -1
- package/dist-tsc/EmbeddingScatterplotSubscriber.d.ts.map +1 -1
- package/dist-tsc/EmbeddingScatterplotSubscriber.js +66 -50
- package/dist-tsc/EmbeddingScatterplotSubscriber.js.map +1 -1
- package/package.json +8 -8
- package/src/EmbeddingScatterplotSubscriber.js +80 -57
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, {
|
|
2
|
-
useState, useEffect, useCallback, useMemo,
|
|
2
|
+
useState, useEffect, useCallback, useMemo, useDeferredValue,
|
|
3
3
|
} from 'react';
|
|
4
4
|
import { extent, quantileSorted } from 'd3-array';
|
|
5
5
|
import { isEqual } from 'lodash-es';
|
|
@@ -28,8 +28,8 @@ import {
|
|
|
28
28
|
useCoordinationScopes,
|
|
29
29
|
} from '@vitessce/vit-s';
|
|
30
30
|
import {
|
|
31
|
-
setObsSelection, mergeObsSets, getCellSetPolygons,
|
|
32
|
-
stratifyArrays,
|
|
31
|
+
setObsSelection, mergeObsSets, getCellSetPolygons, treeToColorIndicesArray,
|
|
32
|
+
colorIndicesFromCodes, getObsIndexMap, stratifyArrays,
|
|
33
33
|
} from '@vitessce/sets-utils';
|
|
34
34
|
import { pluralize as plur, commaNumber, aggregateFeatureArrays } from '@vitessce/utils';
|
|
35
35
|
import {
|
|
@@ -187,7 +187,8 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
187
187
|
);
|
|
188
188
|
const cellsCount = obsEmbeddingIndex?.length || 0;
|
|
189
189
|
const [
|
|
190
|
-
{ obsSets: cellSets, obsSetsMembership
|
|
190
|
+
{ obsSets: cellSets, obsSetsMembership, obsSetsColumns },
|
|
191
|
+
obsSetsStatus, obsSetsUrls, obsSetsError,
|
|
191
192
|
] = useObsSetsData(
|
|
192
193
|
loaders, dataset, false,
|
|
193
194
|
{ setObsSetSelection: setCellSetSelection, setObsSetColor: setCellSetColor },
|
|
@@ -261,6 +262,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
261
262
|
]);
|
|
262
263
|
|
|
263
264
|
const [dynamicCellRadius, setDynamicCellRadius] = useState(cellRadiusFixed);
|
|
265
|
+
const [isSelectionPending, setIsSelectionPending] = useState(false);
|
|
264
266
|
const [dynamicCellOpacity, setDynamicCellOpacity] = useState(cellOpacityFixed);
|
|
265
267
|
|
|
266
268
|
const [originalViewState, setOriginalViewState] = useState(null);
|
|
@@ -278,14 +280,38 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
278
280
|
}, [additionalCellSets, cellSetColor, setCellColorEncoding,
|
|
279
281
|
setAdditionalCellSets, setCellSetColor, setCellSetSelection]);
|
|
280
282
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
283
|
+
// A set selection or color change re-encodes every observation in the memos
|
|
284
|
+
// below, which at atlas scale takes longer than a frame. Deferring these values
|
|
285
|
+
// lets React commit the urgent update first — the sets manager checkbox that
|
|
286
|
+
// initiated the change paints immediately — and re-render this view afterwards.
|
|
287
|
+
// The urgent render sees the previous values, so the memos below keep their
|
|
288
|
+
// cached results and cost nothing in that first commit.
|
|
289
|
+
const deferredCellSetSelection = useDeferredValue(cellSetSelection);
|
|
290
|
+
const deferredCellSetColor = useDeferredValue(cellSetColor);
|
|
291
|
+
const deferredSampleSetSelection = useDeferredValue(sampleSetSelection);
|
|
292
|
+
|
|
293
|
+
// Positional rather than keyed by observation ID: at atlas scale an ID-keyed color
|
|
294
|
+
// Map costs one string hash lookup per point per render, plus a per-observation
|
|
295
|
+
// color array whenever the selected sets carry confidence scores.
|
|
296
|
+
const obsColorIndices = useMemo(() => (
|
|
297
|
+
// When the loader provides raw categorical codes over this same observation
|
|
298
|
+
// axis (checked by reference), the encoding is one typed-array pass with no
|
|
299
|
+
// tree walk. colorIndicesFromCodes returns null for selections it cannot
|
|
300
|
+
// resolve (e.g. user-defined lasso selections), falling back to the tree.
|
|
301
|
+
(obsSetsColumns && obsSetsColumns.obsIndex === obsEmbeddingIndex
|
|
302
|
+
&& colorIndicesFromCodes({
|
|
303
|
+
columns: obsSetsColumns.columns,
|
|
304
|
+
obsIndex: obsEmbeddingIndex,
|
|
305
|
+
selectedNamePaths: deferredCellSetSelection,
|
|
306
|
+
cellSetColor: deferredCellSetColor,
|
|
307
|
+
theme,
|
|
308
|
+
}))
|
|
309
|
+
|| treeToColorIndicesArray(
|
|
310
|
+
mergedCellSets, deferredCellSetSelection, deferredCellSetColor,
|
|
311
|
+
obsEmbeddingIndex, theme,
|
|
312
|
+
)
|
|
313
|
+
), [mergedCellSets, deferredCellSetSelection, deferredCellSetColor,
|
|
314
|
+
obsEmbeddingIndex, theme, obsSetsColumns]);
|
|
289
315
|
|
|
290
316
|
// cellSetPolygonCache is an array of tuples like [(key0, val0), (key1, val1), ...],
|
|
291
317
|
// where the keys are cellSetSelection arrays.
|
|
@@ -294,28 +320,29 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
294
320
|
const cacheGet = (cache, key) => cache.find(el => isEqual(el[0], key))?.[1];
|
|
295
321
|
const cellSetPolygons = useMemo(() => {
|
|
296
322
|
if ((cellSetLabelsVisible || cellSetPolygonsVisible)
|
|
297
|
-
&& !cacheHas(cellSetPolygonCache,
|
|
323
|
+
&& !cacheHas(cellSetPolygonCache, deferredCellSetSelection)
|
|
298
324
|
&& mergedCellSets?.tree?.length
|
|
299
325
|
&& obsEmbedding
|
|
300
326
|
&& obsEmbeddingIndex
|
|
301
|
-
&&
|
|
327
|
+
&& deferredCellSetColor?.length) {
|
|
302
328
|
const newCellSetPolygons = getCellSetPolygons({
|
|
303
329
|
obsIndex: obsEmbeddingIndex,
|
|
304
330
|
obsEmbedding,
|
|
305
331
|
cellSets: mergedCellSets,
|
|
306
|
-
cellSetSelection,
|
|
307
|
-
cellSetColor,
|
|
332
|
+
cellSetSelection: deferredCellSetSelection,
|
|
333
|
+
cellSetColor: deferredCellSetColor,
|
|
308
334
|
theme,
|
|
309
335
|
});
|
|
310
|
-
setCellSetPolygonCache(
|
|
336
|
+
setCellSetPolygonCache(
|
|
337
|
+
cache => [...cache, [deferredCellSetSelection, newCellSetPolygons]],
|
|
338
|
+
);
|
|
311
339
|
return newCellSetPolygons;
|
|
312
340
|
}
|
|
313
|
-
return cacheGet(cellSetPolygonCache,
|
|
341
|
+
return cacheGet(cellSetPolygonCache, deferredCellSetSelection) || [];
|
|
314
342
|
}, [cellSetPolygonsVisible, cellSetPolygonCache, cellSetLabelsVisible, theme,
|
|
315
|
-
obsEmbeddingIndex, obsEmbedding, mergedCellSets,
|
|
316
|
-
|
|
343
|
+
obsEmbeddingIndex, obsEmbedding, mergedCellSets, deferredCellSetSelection,
|
|
344
|
+
deferredCellSetColor]);
|
|
317
345
|
|
|
318
|
-
const cellSelection = useMemo(() => Array.from(cellColors.keys()), [cellColors]);
|
|
319
346
|
|
|
320
347
|
const [xRange, yRange, xExtent, yExtent, numCells] = useMemo(() => {
|
|
321
348
|
if (obsEmbedding && obsEmbedding.data && obsEmbedding.shape) {
|
|
@@ -373,10 +400,13 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
373
400
|
observationsLabel, obsLabelsTypes, obsLabelsData, obsSetsMembership,
|
|
374
401
|
);
|
|
375
402
|
|
|
376
|
-
|
|
403
|
+
// With no set selection every observation counts as selected, matching the
|
|
404
|
+
// behavior of the ID-keyed color map this replaced. Uses the deferred selection
|
|
405
|
+
// so that it stays consistent with obsColorIndices within each commit.
|
|
406
|
+
const allCellsSelected = !(deferredCellSetSelection && mergedCellSets);
|
|
377
407
|
const getCellIsSelected = useCallback((object, { index }) => (
|
|
378
|
-
(
|
|
379
|
-
), [
|
|
408
|
+
(allCellsSelected || obsColorIndices.colorIndices[index] !== 0) ? 1.0 : 0.0
|
|
409
|
+
), [allCellsSelected, obsColorIndices]);
|
|
380
410
|
|
|
381
411
|
const cellRadius = (cellRadiusMode === 'manual' ? cellRadiusFixed : dynamicCellRadius);
|
|
382
412
|
const cellOpacity = (cellOpacityMode === 'manual' ? cellOpacityFixed : dynamicCellOpacity);
|
|
@@ -468,8 +498,11 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
468
498
|
// useExpressionValueGetter hook and getter function.
|
|
469
499
|
const [alignedEmbeddingIndex, alignedEmbeddingData] = useMemo(() => {
|
|
470
500
|
// Sort the embedding data according to the matrix obsIndex.
|
|
471
|
-
if (obsEmbedding?.data && obsEmbeddingIndex && matrixObsIndex
|
|
472
|
-
|
|
501
|
+
if (obsEmbedding?.data && obsEmbeddingIndex && matrixObsIndex
|
|
502
|
+
&& obsEmbeddingIndex !== matrixObsIndex) {
|
|
503
|
+
// (The same index array, as when both come from one data source,
|
|
504
|
+
// is already aligned and skips this copy.)
|
|
505
|
+
const matrixIndexMap = getObsIndexMap(matrixObsIndex);
|
|
473
506
|
const toMatrixIndex = obsEmbeddingIndex.map(key => matrixIndexMap.get(key));
|
|
474
507
|
|
|
475
508
|
const newEmbeddingIndex = new Array(obsEmbeddingIndex.length);
|
|
@@ -489,42 +522,32 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
489
522
|
return [obsEmbeddingIndex, obsEmbedding];
|
|
490
523
|
}, [matrixObsIndex, obsEmbeddingIndex, obsEmbedding]);
|
|
491
524
|
|
|
492
|
-
const sampleIdToObsIdsMap = useMemo(() => {
|
|
493
|
-
// sampleEdges maps obsId -> sampleId.
|
|
494
|
-
// However when we stratify we want to map sampleId -> [obsId1, obsId2, ...].
|
|
495
|
-
// Here we create this reverse mapping.
|
|
496
|
-
if (sampleEdges) {
|
|
497
|
-
const result = new Map();
|
|
498
|
-
Array.from(sampleEdges.entries()).forEach(([obsId, sampleId]) => {
|
|
499
|
-
if (!result.has(sampleId)) {
|
|
500
|
-
result.set(sampleId, [obsId]);
|
|
501
|
-
} else {
|
|
502
|
-
result.get(sampleId).push(obsId);
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
return result;
|
|
506
|
-
}
|
|
507
|
-
return null;
|
|
508
|
-
}, [sampleEdges]);
|
|
509
|
-
|
|
510
525
|
// Stratify multiple arrays: per-cellSet and per-sampleSet.
|
|
526
|
+
// The strata feed only the contour layers and, when the points are hidden, the
|
|
527
|
+
// observation count shown in the title — so with contours off and points on,
|
|
528
|
+
// this per-observation pass is skipped entirely rather than recomputed on
|
|
529
|
+
// every selection change.
|
|
530
|
+
const isStratifiedDataNeeded = embeddingContoursVisible || !embeddingPointsVisible;
|
|
511
531
|
const [stratifiedData, stratifiedDataCount] = useMemo(() => {
|
|
512
|
-
if (alignedEmbeddingData?.data) {
|
|
532
|
+
if (isStratifiedDataNeeded && alignedEmbeddingData?.data) {
|
|
513
533
|
const [result, cellCountResult] = stratifyArrays(
|
|
514
|
-
sampleEdges,
|
|
515
|
-
sampleSets,
|
|
516
|
-
alignedEmbeddingIndex, mergedCellSets,
|
|
534
|
+
sampleEdges,
|
|
535
|
+
sampleSets, deferredSampleSetSelection,
|
|
536
|
+
alignedEmbeddingIndex, mergedCellSets, deferredCellSetSelection, {
|
|
517
537
|
obsEmbeddingX: alignedEmbeddingData.data[0],
|
|
518
538
|
obsEmbeddingY: alignedEmbeddingData.data[1],
|
|
519
539
|
...(uint8ExpressionData?.[0] ? { featureValue: uint8ExpressionData } : {}),
|
|
520
540
|
}, featureAggregationStrategyToUse,
|
|
541
|
+
// Raw codes let the strata come from typed arrays when the indices match.
|
|
542
|
+
{ obsSetsColumns },
|
|
521
543
|
);
|
|
522
544
|
return [result, cellCountResult];
|
|
523
545
|
}
|
|
524
546
|
return [null, null];
|
|
525
|
-
}, [alignedEmbeddingIndex, alignedEmbeddingData,
|
|
526
|
-
|
|
527
|
-
|
|
547
|
+
}, [isStratifiedDataNeeded, alignedEmbeddingIndex, alignedEmbeddingData,
|
|
548
|
+
uint8ExpressionData, sampleEdges, sampleSets, deferredSampleSetSelection,
|
|
549
|
+
deferredCellSetSelection, mergedCellSets, featureAggregationStrategyToUse,
|
|
550
|
+
obsSetsColumns,
|
|
528
551
|
]);
|
|
529
552
|
|
|
530
553
|
const setViewState = ({ zoom: newZoom, target }) => {
|
|
@@ -548,7 +571,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
548
571
|
removeGridComponent={removeGridComponent}
|
|
549
572
|
urls={urls}
|
|
550
573
|
theme={theme}
|
|
551
|
-
isReady={isReady}
|
|
574
|
+
isReady={isReady && !isSelectionPending}
|
|
552
575
|
helpText={helpText}
|
|
553
576
|
errors={errors}
|
|
554
577
|
options={(
|
|
@@ -595,6 +618,7 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
595
618
|
<Scatterplot
|
|
596
619
|
ref={deckRef}
|
|
597
620
|
uuid={uuid}
|
|
621
|
+
onSelectionBusy={setIsSelectionPending}
|
|
598
622
|
theme={theme}
|
|
599
623
|
viewState={{ zoom, target: [targetX, targetY, targetZ] }}
|
|
600
624
|
setViewState={setViewState}
|
|
@@ -602,9 +626,8 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
602
626
|
obsEmbeddingIndex={obsEmbeddingIndex}
|
|
603
627
|
obsEmbedding={obsEmbedding}
|
|
604
628
|
cellFilter={cellFilter}
|
|
605
|
-
cellSelection={cellSelection}
|
|
606
629
|
cellHighlight={cellHighlight}
|
|
607
|
-
|
|
630
|
+
obsColorIndices={obsColorIndices}
|
|
608
631
|
cellSetPolygons={cellSetPolygons}
|
|
609
632
|
cellSetLabelSize={cellSetLabelSize}
|
|
610
633
|
cellSetLabelsVisible={cellSetLabelsVisible}
|
|
@@ -624,12 +647,12 @@ export function EmbeddingScatterplotSubscriber(props) {
|
|
|
624
647
|
getExpressionValue={getExpressionValue}
|
|
625
648
|
getCellIsSelected={getCellIsSelected}
|
|
626
649
|
|
|
627
|
-
obsSetSelection={
|
|
628
|
-
sampleSetSelection={
|
|
650
|
+
obsSetSelection={deferredCellSetSelection}
|
|
651
|
+
sampleSetSelection={deferredSampleSetSelection}
|
|
629
652
|
// InternMap data structures where keys are
|
|
630
653
|
// obsSet -> sampleSet -> arrayKey -> [].
|
|
631
654
|
stratifiedData={stratifiedData}
|
|
632
|
-
obsSetColor={
|
|
655
|
+
obsSetColor={deferredCellSetColor}
|
|
633
656
|
sampleSetColor={sampleSetColor}
|
|
634
657
|
contourThresholds={contourThresholds}
|
|
635
658
|
contourColorEncoding={contourColorEncoding}
|