@vitessce/vit-s 4.0.3 → 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/index.js +3392 -1143
- package/dist-tsc/VitS.d.ts.map +1 -1
- package/dist-tsc/VitS.js +5 -1
- package/dist-tsc/VitS.js.map +1 -1
- package/dist-tsc/data-hooks-multilevel-utils.d.ts +10 -0
- package/dist-tsc/data-hooks-multilevel-utils.d.ts.map +1 -1
- package/dist-tsc/data-hooks-multilevel-utils.js +12 -0
- package/dist-tsc/data-hooks-multilevel-utils.js.map +1 -1
- package/dist-tsc/data-hooks-multilevel.d.ts +9 -0
- package/dist-tsc/data-hooks-multilevel.d.ts.map +1 -1
- package/dist-tsc/data-hooks-multilevel.js +21 -1
- package/dist-tsc/data-hooks-multilevel.js.map +1 -1
- package/dist-tsc/hooks.d.ts.map +1 -1
- package/dist-tsc/hooks.js +23 -5
- package/dist-tsc/hooks.js.map +1 -1
- package/dist-tsc/hooks.test.d.ts +2 -0
- package/dist-tsc/hooks.test.d.ts.map +1 -0
- package/dist-tsc/hooks.test.js +36 -0
- package/dist-tsc/hooks.test.js.map +1 -0
- package/dist-tsc/index.d.ts +2 -1
- package/dist-tsc/index.js +2 -1
- package/dist-tsc/index.js.map +1 -1
- package/dist-tsc/use-memo-custom-comparison.d.ts +21 -0
- package/dist-tsc/use-memo-custom-comparison.d.ts.map +1 -0
- package/dist-tsc/use-memo-custom-comparison.js +39 -0
- package/dist-tsc/use-memo-custom-comparison.js.map +1 -0
- package/package.json +15 -15
- package/src/VitS.js +7 -1
- package/src/data-hooks-multilevel-utils.js +19 -0
- package/src/data-hooks-multilevel.js +32 -0
- package/src/hooks.js +24 -5
- package/src/hooks.test.jsx +39 -0
- package/src/index.js +13 -0
- package/src/use-memo-custom-comparison.js +61 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/vit-s",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.5",
|
|
4
4
|
"author": "HIDIVE Lab at HMS",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@tanstack/react-query": "^5.80.2",
|
|
20
|
-
"@zarrita/storage": "0.
|
|
20
|
+
"@zarrita/storage": "0.2.0",
|
|
21
21
|
"clsx": "^1.1.1",
|
|
22
22
|
"d3-array": "^2.4.0",
|
|
23
23
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -30,18 +30,18 @@
|
|
|
30
30
|
"react-aria": "^3.47.0",
|
|
31
31
|
"react-error-boundary": "^5.0.0",
|
|
32
32
|
"@placemarkio/flat-drop-files": "^1.0.2",
|
|
33
|
-
"zarrita": "0.
|
|
34
|
-
"@vitessce/styles": "4.0.
|
|
35
|
-
"@vitessce/
|
|
36
|
-
"@vitessce/
|
|
37
|
-
"@vitessce/
|
|
38
|
-
"@vitessce/
|
|
39
|
-
"@vitessce/
|
|
40
|
-
"@vitessce/sets-utils": "4.0.
|
|
41
|
-
"@vitessce/
|
|
42
|
-
"@vitessce/
|
|
43
|
-
"@vitessce/config": "4.0.
|
|
44
|
-
"@vitessce/globals": "4.0.
|
|
33
|
+
"zarrita": "0.7.5",
|
|
34
|
+
"@vitessce/styles": "4.0.5",
|
|
35
|
+
"@vitessce/error": "4.0.5",
|
|
36
|
+
"@vitessce/plugins": "4.0.5",
|
|
37
|
+
"@vitessce/constants-internal": "4.0.5",
|
|
38
|
+
"@vitessce/schemas": "4.0.5",
|
|
39
|
+
"@vitessce/abstract": "4.0.5",
|
|
40
|
+
"@vitessce/sets-utils": "4.0.5",
|
|
41
|
+
"@vitessce/utils": "4.0.5",
|
|
42
|
+
"@vitessce/zarr-utils": "4.0.5",
|
|
43
|
+
"@vitessce/config": "4.0.5",
|
|
44
|
+
"@vitessce/globals": "4.0.5"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"react-dom": "19.2.6",
|
|
51
51
|
"vite": "^7.0.0",
|
|
52
52
|
"vitest": "^3.1.4",
|
|
53
|
-
"@vitessce/types": "4.0.
|
|
53
|
+
"@vitessce/types": "4.0.5"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
package/src/VitS.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from '@tanstack/react-query';
|
|
15
15
|
import { isEqual } from 'lodash-es';
|
|
16
16
|
import { buildConfigSchema, latestConfigSchema } from '@vitessce/schemas';
|
|
17
|
+
import { MatrixTooLargeError } from '@vitessce/error';
|
|
17
18
|
import {
|
|
18
19
|
log,
|
|
19
20
|
setLogLevel, setDebugMode,
|
|
@@ -248,7 +249,12 @@ export function VitS(props) {
|
|
|
248
249
|
defaultOptions: {
|
|
249
250
|
queries: {
|
|
250
251
|
refetchOnWindowFocus: false,
|
|
251
|
-
|
|
252
|
+
// A matrix that does not fit the browser's budget will not fit on a
|
|
253
|
+
// retry either; surface that message immediately rather than after
|
|
254
|
+
// two more attempts and their backoff.
|
|
255
|
+
retry: (failureCount, error) => (
|
|
256
|
+
failureCount < 2 && !(error instanceof MatrixTooLargeError)
|
|
257
|
+
),
|
|
252
258
|
},
|
|
253
259
|
},
|
|
254
260
|
// Reference: https://tkdodo.eu/blog/react-query-error-handling#the-global-callbacks
|
|
@@ -589,3 +589,22 @@ export function useObsLabelsMultiLevel(
|
|
|
589
589
|
depth, DataType.OBS_LABELS,
|
|
590
590
|
);
|
|
591
591
|
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Wrapper around useDataTypeMultiLevel.
|
|
595
|
+
* @param {object} loaders
|
|
596
|
+
* @param {string} dataset
|
|
597
|
+
* @param {boolean} isRequired
|
|
598
|
+
* @param {object} matchOnObj
|
|
599
|
+
* @param {number} depth
|
|
600
|
+
* @returns {array} [data, status, errors]
|
|
601
|
+
*/
|
|
602
|
+
export function useObsColorsMultiLevel(
|
|
603
|
+
loaders, dataset, isRequired, matchOnObj,
|
|
604
|
+
depth,
|
|
605
|
+
) {
|
|
606
|
+
return useDataTypeMultiLevel(
|
|
607
|
+
loaders, dataset, isRequired, matchOnObj,
|
|
608
|
+
depth, DataType.OBS_COLORS,
|
|
609
|
+
);
|
|
610
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useObsLocationsMultiLevel,
|
|
11
11
|
useObsSetsMultiLevel,
|
|
12
12
|
useObsLabelsMultiLevel,
|
|
13
|
+
useObsColorsMultiLevel,
|
|
13
14
|
} from './data-hooks-multilevel-utils.js';
|
|
14
15
|
|
|
15
16
|
|
|
@@ -319,3 +320,34 @@ export function useSegmentationMultiObsSets(
|
|
|
319
320
|
);
|
|
320
321
|
return [indicesData, indicesDataStatus, indicesDataErrors];
|
|
321
322
|
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Wrapper around useObsColorsMultiLevel.
|
|
326
|
+
* @param {object} coordinationScopes
|
|
327
|
+
* @param {object} coordinationScopesBy
|
|
328
|
+
* @param {object} loaders
|
|
329
|
+
* @param {string} dataset
|
|
330
|
+
* @returns {array} [data, status, errors]
|
|
331
|
+
*/
|
|
332
|
+
export function useSegmentationMultiObsColors(
|
|
333
|
+
coordinationScopes, coordinationScopesBy, loaders, dataset,
|
|
334
|
+
) {
|
|
335
|
+
const obsTypeCoordination = useComplexCoordinationSecondary(
|
|
336
|
+
[
|
|
337
|
+
CoordinationType.OBS_TYPE,
|
|
338
|
+
],
|
|
339
|
+
coordinationScopes,
|
|
340
|
+
coordinationScopesBy,
|
|
341
|
+
CoordinationType.SEGMENTATION_LAYER,
|
|
342
|
+
CoordinationType.SEGMENTATION_CHANNEL,
|
|
343
|
+
);
|
|
344
|
+
const matchOnObj = useMemo(() => obsTypeCoordination[0],
|
|
345
|
+
// imageCoordination reference changes each render,
|
|
346
|
+
// use coordinationScopes and coordinationScopesBy which are
|
|
347
|
+
// indirect dependencies here.
|
|
348
|
+
[coordinationScopes, coordinationScopesBy]);
|
|
349
|
+
const [colorsData, colorsDataStatus, colorsDataErrors] = useObsColorsMultiLevel(
|
|
350
|
+
loaders, dataset, false, matchOnObj, 2,
|
|
351
|
+
);
|
|
352
|
+
return [colorsData, colorsDataStatus, colorsDataErrors];
|
|
353
|
+
}
|
package/src/hooks.js
CHANGED
|
@@ -5,6 +5,7 @@ import { debounce, every } from 'lodash-es';
|
|
|
5
5
|
import { extent } from 'd3-array';
|
|
6
6
|
import { useQuery } from '@tanstack/react-query';
|
|
7
7
|
import { capitalize } from '@vitessce/utils';
|
|
8
|
+
import { getObsIndexMap } from '@vitessce/sets-utils';
|
|
8
9
|
import { STATUS, AsyncFunctionType } from '@vitessce/constants-internal';
|
|
9
10
|
import { VITESSCE_CONTAINER } from './classNames.js';
|
|
10
11
|
import { useGridResize, useEmitGridResize } from './state/hooks.js';
|
|
@@ -258,6 +259,10 @@ export function useUint8FeatureSelection(expressionData) {
|
|
|
258
259
|
}, [expressionData]);
|
|
259
260
|
}
|
|
260
261
|
|
|
262
|
+
// Marks an instance index that equals the matrix row index, so that no
|
|
263
|
+
// per-observation mapping needs to be built.
|
|
264
|
+
const IDENTITY_MAPPING = 'identity';
|
|
265
|
+
|
|
261
266
|
export function useExpressionValueGetter(
|
|
262
267
|
{ instanceObsIndex, matrixObsIndex, expressionData },
|
|
263
268
|
) {
|
|
@@ -267,18 +272,32 @@ export function useExpressionValueGetter(
|
|
|
267
272
|
// we need a way to look up an obsFeatureMatrix obsIndex index
|
|
268
273
|
// given an obsEmbedding obsIndex index.
|
|
269
274
|
const toMatrixIndexMap = useMemo(() => {
|
|
270
|
-
if (instanceObsIndex
|
|
271
|
-
|
|
272
|
-
return instanceObsIndex.map(key => matrixIndexMap.get(key));
|
|
275
|
+
if (!instanceObsIndex || !matrixObsIndex) {
|
|
276
|
+
return null;
|
|
273
277
|
}
|
|
274
|
-
|
|
278
|
+
if (instanceObsIndex === matrixObsIndex) {
|
|
279
|
+
// The same array (the usual case when both come from one data source)
|
|
280
|
+
// is already aligned by position.
|
|
281
|
+
return IDENTITY_MAPPING;
|
|
282
|
+
}
|
|
283
|
+
// The map is shared with other consumers of this index via getObsIndexMap.
|
|
284
|
+
const matrixIndexMap = getObsIndexMap(matrixObsIndex);
|
|
285
|
+
const mapping = new Int32Array(instanceObsIndex.length);
|
|
286
|
+
for (let i = 0; i < instanceObsIndex.length; i += 1) {
|
|
287
|
+
const rowIndex = matrixIndexMap.get(instanceObsIndex[i]);
|
|
288
|
+
mapping[i] = rowIndex === undefined ? -1 : rowIndex;
|
|
289
|
+
}
|
|
290
|
+
return mapping;
|
|
275
291
|
}, [instanceObsIndex, matrixObsIndex]);
|
|
276
292
|
|
|
277
293
|
// Set up a getter function for gene expression values, to be used
|
|
278
294
|
// by the DeckGL layer to obtain values for instanced attributes.
|
|
279
295
|
const getExpressionValue = useCallback((entry, { index: instanceIndex }) => {
|
|
280
296
|
if (toMatrixIndexMap && expressionData && expressionData[0]) {
|
|
281
|
-
const rowIndex = toMatrixIndexMap
|
|
297
|
+
const rowIndex = toMatrixIndexMap === IDENTITY_MAPPING
|
|
298
|
+
? instanceIndex
|
|
299
|
+
: toMatrixIndexMap[instanceIndex];
|
|
300
|
+
// An observation absent from the matrix (-1) reads as undefined, as before.
|
|
282
301
|
const val = expressionData[0][rowIndex];
|
|
283
302
|
return val;
|
|
284
303
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { renderHook } from '@testing-library/react';
|
|
3
|
+
import { useExpressionValueGetter } from './hooks.js';
|
|
4
|
+
|
|
5
|
+
describe('useExpressionValueGetter', () => {
|
|
6
|
+
const expressionData = [new Uint8Array([10, 20, 30])];
|
|
7
|
+
|
|
8
|
+
it('reads by position when the instance and matrix indices are the same array', () => {
|
|
9
|
+
const obsIndex = ['a', 'b', 'c'];
|
|
10
|
+
const { result } = renderHook(() => useExpressionValueGetter({
|
|
11
|
+
instanceObsIndex: obsIndex, matrixObsIndex: obsIndex, expressionData,
|
|
12
|
+
}));
|
|
13
|
+
expect(result.current(null, { index: 0 })).toEqual(10);
|
|
14
|
+
expect(result.current(null, { index: 2 })).toEqual(30);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('maps through observation IDs when the indices differ', () => {
|
|
18
|
+
const { result } = renderHook(() => useExpressionValueGetter({
|
|
19
|
+
instanceObsIndex: ['c', 'unknown', 'a'],
|
|
20
|
+
matrixObsIndex: ['a', 'b', 'c'],
|
|
21
|
+
expressionData,
|
|
22
|
+
}));
|
|
23
|
+
expect(result.current(null, { index: 0 })).toEqual(30);
|
|
24
|
+
// An observation absent from the matrix reads as undefined, as before.
|
|
25
|
+
expect(result.current(null, { index: 1 })).toEqual(undefined);
|
|
26
|
+
expect(result.current(null, { index: 2 })).toEqual(10);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns 0 without indices or without data', () => {
|
|
30
|
+
const { result: noIndices } = renderHook(() => useExpressionValueGetter({
|
|
31
|
+
instanceObsIndex: null, matrixObsIndex: ['a'], expressionData,
|
|
32
|
+
}));
|
|
33
|
+
expect(noIndices.current(null, { index: 0 })).toEqual(0);
|
|
34
|
+
const { result: noData } = renderHook(() => useExpressionValueGetter({
|
|
35
|
+
instanceObsIndex: ['a'], matrixObsIndex: ['a'], expressionData: null,
|
|
36
|
+
}));
|
|
37
|
+
expect(noData.current(null, { index: 0 })).toEqual(0);
|
|
38
|
+
});
|
|
39
|
+
});
|
package/src/index.js
CHANGED
|
@@ -93,6 +93,7 @@ export {
|
|
|
93
93
|
useSegmentationMultiObsFeatureMatrixIndices,
|
|
94
94
|
useSegmentationMultiObsLocations,
|
|
95
95
|
useSegmentationMultiObsSets,
|
|
96
|
+
useSegmentationMultiObsColors,
|
|
96
97
|
} from './data-hooks-multilevel.js';
|
|
97
98
|
export {
|
|
98
99
|
useHasLoader,
|
|
@@ -107,3 +108,15 @@ export { logConfig } from './view-config-utils.js';
|
|
|
107
108
|
export { useAsyncFunction, usePageModeView } from './contexts.js';
|
|
108
109
|
export { createLoaders } from './vitessce-grid-utils.js';
|
|
109
110
|
export { createOnDrop } from './on-drop.js';
|
|
111
|
+
export {
|
|
112
|
+
useMemoCustomComparison,
|
|
113
|
+
shallowDiff,
|
|
114
|
+
shallowDiffByLayer,
|
|
115
|
+
shallowDiffByLayerWithKeys,
|
|
116
|
+
shallowDiffByChannel,
|
|
117
|
+
shallowDiffByChannelWithKeys,
|
|
118
|
+
shallowDiffByLayerCoordination,
|
|
119
|
+
shallowDiffByLayerCoordinationWithKeys,
|
|
120
|
+
shallowDiffByChannelCoordination,
|
|
121
|
+
shallowDiffByChannelCoordinationWithKeys,
|
|
122
|
+
} from './use-memo-custom-comparison.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
/* eslint-disable max-len */
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A variant of useMemo that accepts a custom equality function
|
|
7
|
+
* to determine whether the dependencies have changed.
|
|
8
|
+
* @template T
|
|
9
|
+
* @param {() => T} factory - Function that computes the memoized value.
|
|
10
|
+
* @param {any} dependencies - The useMemo dependencies.
|
|
11
|
+
* @param {(prevDeps: any, nextDeps: any) => boolean} customIsEqual - Custom equality function,
|
|
12
|
+
* which receives the previous and next dependencies and returns true if they are considered equal.
|
|
13
|
+
* @returns {T} The memoized value.
|
|
14
|
+
*/
|
|
15
|
+
export function useMemoCustomComparison(factory, dependencies, customIsEqual) {
|
|
16
|
+
const ref = useRef(/** @type {{ deps: any; value: T } | undefined} */ (undefined));
|
|
17
|
+
|
|
18
|
+
if (ref.current === undefined || !customIsEqual(ref.current.deps, dependencies)) {
|
|
19
|
+
ref.current = { deps: dependencies, value: factory() };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return ref.current.value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// Comparison utilties inspired by componentDidUpdate in spatial-beta/Spatial.js:
|
|
27
|
+
export const shallowDiff = (prevDeps, nextDeps, depName) => prevDeps[depName] !== nextDeps[depName];
|
|
28
|
+
export const shallowDiffByLayer = (prevDeps, nextDeps, depName, scopeName) => (
|
|
29
|
+
prevDeps?.[depName]?.[scopeName] !== nextDeps?.[depName]?.[scopeName]
|
|
30
|
+
);
|
|
31
|
+
// Rather than checking equality of the entire object,
|
|
32
|
+
// here, we only shallowly compare the specific properties that are relevant.
|
|
33
|
+
export const shallowDiffByLayerWithKeys = (prevDeps, nextDeps, depName, scopeName, keys) => keys.some(
|
|
34
|
+
k => (prevDeps?.[depName]?.[scopeName]?.[k] !== nextDeps?.[depName]?.[scopeName]?.[k]),
|
|
35
|
+
);
|
|
36
|
+
export const shallowDiffByChannel = (prevDeps, nextDeps, depName, firstName, secondName) => (
|
|
37
|
+
prevDeps?.[depName]?.[firstName]?.[secondName]
|
|
38
|
+
!== nextDeps?.[depName]?.[firstName]?.[secondName]
|
|
39
|
+
);
|
|
40
|
+
export const shallowDiffByChannelWithKeys = (prevDeps, nextDeps, depName, firstName, secondName, keys) => keys.some(
|
|
41
|
+
k => (
|
|
42
|
+
prevDeps?.[depName]?.[firstName]?.[secondName]?.[k]
|
|
43
|
+
!== nextDeps?.[depName]?.[firstName]?.[secondName]?.[k]
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
export const shallowDiffByLayerCoordination = (prevDeps, nextDeps, depName, layerScope) => (
|
|
47
|
+
prevDeps?.[depName]?.[0]?.[layerScope]
|
|
48
|
+
!== nextDeps?.[depName]?.[0]?.[layerScope]
|
|
49
|
+
);
|
|
50
|
+
export const shallowDiffByLayerCoordinationWithKeys = (prevDeps, nextDeps, depName, layerScope, keys) => keys.some(
|
|
51
|
+
k => prevDeps?.[depName]?.[0]?.[layerScope]?.[k]
|
|
52
|
+
!== nextDeps?.[depName]?.[0]?.[layerScope]?.[k],
|
|
53
|
+
);
|
|
54
|
+
export const shallowDiffByChannelCoordination = (prevDeps, nextDeps, depName, layerScope, channelScope) => (
|
|
55
|
+
prevDeps?.[depName]?.[0]?.[layerScope]?.[channelScope]
|
|
56
|
+
!== nextDeps?.[depName]?.[0]?.[layerScope]?.[channelScope]
|
|
57
|
+
);
|
|
58
|
+
export const shallowDiffByChannelCoordinationWithKeys = (prevDeps, nextDeps, depName, layerScope, channelScope, keys) => keys.some(
|
|
59
|
+
k => prevDeps?.[depName]?.[0]?.[layerScope]?.[channelScope]?.[k]
|
|
60
|
+
!== nextDeps?.[depName]?.[0]?.[layerScope]?.[channelScope]?.[k],
|
|
61
|
+
);
|