@vitessce/vit-s 3.1.3 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/index.js +1801 -132
  2. package/dist-tsc/data-hook-utils.d.ts +1 -0
  3. package/dist-tsc/data-hook-utils.d.ts.map +1 -1
  4. package/dist-tsc/data-hook-utils.js +12 -4
  5. package/dist-tsc/data-hooks-multilevel-utils.d.ts +77 -0
  6. package/dist-tsc/data-hooks-multilevel-utils.d.ts.map +1 -0
  7. package/dist-tsc/data-hooks-multilevel-utils.js +418 -0
  8. package/dist-tsc/data-hooks-multilevel-utils.test.d.ts +2 -0
  9. package/dist-tsc/data-hooks-multilevel-utils.test.d.ts.map +1 -0
  10. package/dist-tsc/data-hooks-multilevel-utils.test.js +101 -0
  11. package/dist-tsc/data-hooks-multilevel.d.ts +8 -0
  12. package/dist-tsc/data-hooks-multilevel.d.ts.map +1 -0
  13. package/dist-tsc/data-hooks-multilevel.js +125 -0
  14. package/dist-tsc/data-hooks.d.ts +7 -0
  15. package/dist-tsc/data-hooks.d.ts.map +1 -1
  16. package/dist-tsc/data-hooks.js +75 -6
  17. package/dist-tsc/hooks.js +1 -1
  18. package/dist-tsc/index.d.ts +3 -2
  19. package/dist-tsc/index.js +3 -2
  20. package/dist-tsc/shared-mui/components.d.ts.map +1 -1
  21. package/dist-tsc/shared-mui/components.js +3 -2
  22. package/dist-tsc/shared-plot-options/CellColorEncodingOption.d.ts.map +1 -1
  23. package/dist-tsc/shared-plot-options/CellColorEncodingOption.js +2 -1
  24. package/dist-tsc/state/hooks.d.ts +80 -21
  25. package/dist-tsc/state/hooks.d.ts.map +1 -1
  26. package/dist-tsc/state/hooks.js +428 -103
  27. package/dist-tsc/state/hooks.test.d.ts +2 -0
  28. package/dist-tsc/state/hooks.test.d.ts.map +1 -0
  29. package/dist-tsc/state/hooks.test.js +149 -0
  30. package/dist-tsc/state/spatial-reducers.d.ts +25 -0
  31. package/dist-tsc/state/spatial-reducers.d.ts.map +1 -0
  32. package/dist-tsc/state/spatial-reducers.js +251 -0
  33. package/dist-tsc/state/spatial-reducers.test.d.ts +2 -0
  34. package/dist-tsc/state/spatial-reducers.test.d.ts.map +1 -0
  35. package/dist-tsc/state/spatial-reducers.test.js +1382 -0
  36. package/package.json +6 -5
  37. package/src/data-hook-utils.js +19 -3
  38. package/src/data-hooks-multilevel-utils.js +571 -0
  39. package/src/data-hooks-multilevel-utils.test.js +110 -0
  40. package/src/data-hooks-multilevel.js +226 -0
  41. package/src/data-hooks.js +156 -6
  42. package/src/hooks.js +1 -1
  43. package/src/index.js +25 -0
  44. package/src/shared-mui/components.js +9 -4
  45. package/src/shared-plot-options/CellColorEncodingOption.js +2 -1
  46. package/src/state/hooks.js +485 -104
  47. package/src/state/hooks.test.js +182 -0
  48. package/src/state/spatial-reducers.js +291 -0
  49. package/src/state/spatial-reducers.test.js +1432 -0
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ initializeNestedObject,
4
+ nestFeatureSelectionQueryResults,
5
+ getFeatureSelectionQueryKeyScopeTuples,
6
+ nestQueryResults,
7
+ getQueryKeyScopeTuples,
8
+ } from './data-hooks-multilevel-utils.js';
9
+
10
+ describe('recursive data hook utilities for nesting and un-nesting multi-level queries', () => {
11
+ describe('initializeNestedObject', () => {
12
+ it('should initialize an empty nested object', () => {
13
+ const nestedObject = {};
14
+ initializeNestedObject(['a', 'b', 'c'], nestedObject, () => true);
15
+ expect(nestedObject).toEqual({ a: { b: { c: true } } });
16
+ });
17
+
18
+ it('should initialize a non-empty nested object at the same path', () => {
19
+ const nestedObject = { a: { b: { c: [1, 2, 3] } } };
20
+ initializeNestedObject(['a', 'b', 'c'], nestedObject, () => []);
21
+ expect(nestedObject).toEqual({ a: { b: { c: [1, 2, 3] } } });
22
+ });
23
+
24
+ it('should initialize a non-empty nested object at a different path', () => {
25
+ const nestedObject = { a: { b: { c: [1, 2, 3] } } };
26
+ initializeNestedObject(['a', 'b', 'd'], nestedObject, () => []);
27
+ expect(nestedObject).toEqual({ a: { b: { c: [1, 2, 3], d: [] } } });
28
+ });
29
+ it('should initialize two paths', () => {
30
+ const nestedObject = {};
31
+ initializeNestedObject(['a', 'b', 'c'], nestedObject, () => true);
32
+ initializeNestedObject(['d', 'e', 'f'], nestedObject, () => true);
33
+ expect(nestedObject).toEqual({
34
+ a: { b: { c: true } },
35
+ d: { e: { f: true } },
36
+ });
37
+ });
38
+ });
39
+ describe('nestFeatureSelectionQueryResults', () => {
40
+ it('should nest flat query results', () => {
41
+ const queryKeyScopeTuples = [
42
+ [['someQueryKey', 'abc'], { levelScopes: ['a', 'b', 'c'], featureIndex: 0, numFeatures: 3 }],
43
+ [['someQueryKey', 'abc'], { levelScopes: ['a', 'b', 'c'], featureIndex: 1, numFeatures: 3 }],
44
+ [['someQueryKey', 'abd'], { levelScopes: ['a', 'b', 'd'], featureIndex: 0, numFeatures: 2 }],
45
+ ];
46
+ const flatQueryResults = [
47
+ 'abc0',
48
+ 'abc1',
49
+ 'abd0',
50
+ ];
51
+ const nestedData = nestFeatureSelectionQueryResults(queryKeyScopeTuples, flatQueryResults);
52
+ expect(nestedData).toEqual({
53
+ a: { b: { c: ['abc0', 'abc1', undefined], d: ['abd0', undefined] } },
54
+ });
55
+ });
56
+ });
57
+ describe('getFeatureSelectionQueryKeyScopeTuples', () => {
58
+ it('should convert nested selections and matchOn objects to array of tuples', () => {
59
+ const selections = { a: { b: { c: ['geneA', 'geneB', 'geneC'] } } };
60
+ const matchOn = { a: { b: { c: { obsType: 'cell', featureType: 'gene' } } } };
61
+ const queryKeyScopeTuples = getFeatureSelectionQueryKeyScopeTuples(selections, matchOn, 3, 'someDataset', 'someDataType', true);
62
+ expect(queryKeyScopeTuples).toEqual([
63
+ [
64
+ ['someDataset', 'someDataType', { obsType: 'cell', featureType: 'gene' }, 'geneA', true, 'useFeatureSelectionMultiLevel'],
65
+ // scope info (for rolling up later)
66
+ { levelScopes: ['a', 'b', 'c'], featureIndex: 0, numFeatures: 3 },
67
+ ],
68
+ [
69
+ ['someDataset', 'someDataType', { obsType: 'cell', featureType: 'gene' }, 'geneB', true, 'useFeatureSelectionMultiLevel'],
70
+ // scope info (for rolling up later)
71
+ { levelScopes: ['a', 'b', 'c'], featureIndex: 1, numFeatures: 3 },
72
+ ],
73
+ [
74
+ ['someDataset', 'someDataType', { obsType: 'cell', featureType: 'gene' }, 'geneC', true, 'useFeatureSelectionMultiLevel'],
75
+ // scope info (for rolling up later)
76
+ { levelScopes: ['a', 'b', 'c'], featureIndex: 2, numFeatures: 3 },
77
+ ],
78
+ ]);
79
+ });
80
+ });
81
+ describe('nestQueryResults', () => {
82
+ it('should nest flat query results', () => {
83
+ const queryKeyScopeTuples = [
84
+ [['someQueryKey', 'abc'], { levelScopes: ['a', 'b', 'c'] }],
85
+ [['someQueryKey', 'abd'], { levelScopes: ['a', 'b', 'd'] }],
86
+ ];
87
+ const flatQueryResults = [
88
+ { someKey: 'abc0' },
89
+ { someKey: 'abd0' },
90
+ ];
91
+ const nestedData = nestQueryResults(queryKeyScopeTuples, flatQueryResults);
92
+ expect(nestedData).toEqual({
93
+ a: { b: { c: { someKey: 'abc0' }, d: { someKey: 'abd0' } } },
94
+ });
95
+ });
96
+ });
97
+ describe('getQueryKeyScopeTuples', () => {
98
+ it('should convert nested selections and matchOn objects to array of tuples', () => {
99
+ const matchOn = { a: { b: { c: { obsType: 'cell', featureType: 'gene' } } } };
100
+ const queryKeyScopeTuples = getQueryKeyScopeTuples(matchOn, 3, 'someDataset', 'someDataType', true);
101
+ expect(queryKeyScopeTuples).toEqual([
102
+ [
103
+ ['someDataset', 'someDataType', { obsType: 'cell', featureType: 'gene' }, true, 'useDataType'],
104
+ // scope info (for rolling up later)
105
+ { levelScopes: ['a', 'b', 'c'] },
106
+ ],
107
+ ]);
108
+ });
109
+ });
110
+ });
@@ -0,0 +1,226 @@
1
+ import { useMemo } from 'react';
2
+ import { CoordinationType } from '@vitessce/constants-internal';
3
+ import { fromEntries } from '@vitessce/utils';
4
+ import {
5
+ useComplexCoordination,
6
+ useComplexCoordinationSecondary,
7
+ } from './state/hooks.js';
8
+ import {
9
+ useFeatureSelectionMultiLevel,
10
+ useObsFeatureMatrixIndicesMultiLevel,
11
+ useObsLocationsMultiLevel,
12
+ useObsSetsMultiLevel,
13
+ useObsLabelsMultiLevel,
14
+ } from './data-hooks-multilevel-utils.js';
15
+
16
+
17
+ export function useSegmentationMultiFeatureSelection(
18
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
19
+ ) {
20
+ const obsFeatureMatrixCoordination = useComplexCoordinationSecondary(
21
+ [
22
+ CoordinationType.OBS_TYPE,
23
+ CoordinationType.FEATURE_TYPE,
24
+ CoordinationType.FEATURE_VALUE_TYPE,
25
+ ],
26
+ coordinationScopes,
27
+ coordinationScopesBy,
28
+ CoordinationType.SEGMENTATION_LAYER,
29
+ CoordinationType.SEGMENTATION_CHANNEL,
30
+ );
31
+ const featureSelectionCoordination = useComplexCoordinationSecondary(
32
+ [
33
+ CoordinationType.FEATURE_SELECTION,
34
+ ],
35
+ coordinationScopes,
36
+ coordinationScopesBy,
37
+ CoordinationType.SEGMENTATION_LAYER,
38
+ CoordinationType.SEGMENTATION_CHANNEL,
39
+ );
40
+ const matchOnObj = useMemo(() => obsFeatureMatrixCoordination[0],
41
+ // imageCoordination reference changes each render,
42
+ // use coordinationScopes and coordinationScopesBy which are
43
+ // indirect dependencies here.
44
+ [coordinationScopes, coordinationScopesBy]);
45
+ const selections = useMemo(() => fromEntries(Object.entries(featureSelectionCoordination[0])
46
+ .map(([layerScope, layerVal]) => ([
47
+ layerScope,
48
+ fromEntries(
49
+ Object.entries(layerVal)
50
+ .map(([cScope, cVal]) => ([cScope, cVal.featureSelection])),
51
+ ),
52
+ ]))),
53
+ // Need to execute this more frequently, whenever the featureSelections update.
54
+ [coordinationScopes, coordinationScopesBy,
55
+ ...Object.values(featureSelectionCoordination[0] || {})
56
+ .flatMap(layerVal => Object.values(layerVal).map(cVal => cVal.featureSelection)),
57
+ ]);
58
+ const [
59
+ featureData, loadedSelections, extents, normData, featureStatus,
60
+ ] = useFeatureSelectionMultiLevel(
61
+ loaders, dataset, false, matchOnObj, selections, 2,
62
+ );
63
+ return [featureData, loadedSelections, extents, normData, featureStatus];
64
+ }
65
+
66
+ export function useSpotMultiFeatureSelection(
67
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
68
+ ) {
69
+ const obsFeatureMatrixCoordination = useComplexCoordination(
70
+ [
71
+ CoordinationType.OBS_TYPE,
72
+ CoordinationType.FEATURE_TYPE,
73
+ CoordinationType.FEATURE_VALUE_TYPE,
74
+ ],
75
+ coordinationScopes,
76
+ coordinationScopesBy,
77
+ CoordinationType.SPOT_LAYER,
78
+ );
79
+ const featureSelectionCoordination = useComplexCoordination(
80
+ [
81
+ CoordinationType.FEATURE_SELECTION,
82
+ ],
83
+ coordinationScopes,
84
+ coordinationScopesBy,
85
+ CoordinationType.SPOT_LAYER,
86
+ );
87
+ const matchOnObj = useMemo(() => obsFeatureMatrixCoordination[0],
88
+ // imageCoordination reference changes each render,
89
+ // use coordinationScopes and coordinationScopesBy which are
90
+ // indirect dependencies here.
91
+ [coordinationScopes, coordinationScopesBy]);
92
+ const selections = useMemo(() => fromEntries(Object.entries(featureSelectionCoordination[0])
93
+ .map(([layerScope, layerVal]) => ([
94
+ layerScope,
95
+ layerVal.featureSelection,
96
+ ]))),
97
+ // Need to execute this more frequently, whenever the featureSelections update.
98
+ [coordinationScopes, coordinationScopesBy,
99
+ ...Object.values(featureSelectionCoordination[0] || {})
100
+ .flatMap(layerVal => layerVal.featureSelection),
101
+ ]);
102
+ const [
103
+ featureData, loadedSelections, extents, normData, featureStatus,
104
+ ] = useFeatureSelectionMultiLevel(
105
+ loaders, dataset, false, matchOnObj, selections, 1,
106
+ );
107
+ return [featureData, loadedSelections, extents, normData, featureStatus];
108
+ }
109
+
110
+ export function useSegmentationMultiObsFeatureMatrixIndices(
111
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
112
+ ) {
113
+ const obsFeatureMatrixCoordination = useComplexCoordinationSecondary(
114
+ [
115
+ CoordinationType.OBS_TYPE,
116
+ CoordinationType.FEATURE_TYPE,
117
+ CoordinationType.FEATURE_VALUE_TYPE,
118
+ ],
119
+ coordinationScopes,
120
+ coordinationScopesBy,
121
+ CoordinationType.SEGMENTATION_LAYER,
122
+ CoordinationType.SEGMENTATION_CHANNEL,
123
+ );
124
+ const matchOnObj = useMemo(() => obsFeatureMatrixCoordination[0],
125
+ // imageCoordination reference changes each render,
126
+ // use coordinationScopes and coordinationScopesBy which are
127
+ // indirect dependencies here.
128
+ [coordinationScopes, coordinationScopesBy]);
129
+ const [indicesData, indicesDataStatus] = useObsFeatureMatrixIndicesMultiLevel(
130
+ loaders, dataset, false, matchOnObj, 2,
131
+ );
132
+ return [indicesData, indicesDataStatus];
133
+ }
134
+
135
+ export function useSpotMultiObsFeatureMatrixIndices(
136
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
137
+ ) {
138
+ const obsFeatureMatrixCoordination = useComplexCoordination(
139
+ [
140
+ CoordinationType.OBS_TYPE,
141
+ CoordinationType.FEATURE_TYPE,
142
+ CoordinationType.FEATURE_VALUE_TYPE,
143
+ ],
144
+ coordinationScopes,
145
+ coordinationScopesBy,
146
+ CoordinationType.SPOT_LAYER,
147
+ );
148
+ const matchOnObj = useMemo(() => obsFeatureMatrixCoordination[0],
149
+ // imageCoordination reference changes each render,
150
+ // use coordinationScopes and coordinationScopesBy which are
151
+ // indirect dependencies here.
152
+ [coordinationScopes, coordinationScopesBy]);
153
+ const [indicesData, indicesDataStatus] = useObsFeatureMatrixIndicesMultiLevel(
154
+ loaders, dataset, false, matchOnObj, 1,
155
+ );
156
+ return [indicesData, indicesDataStatus];
157
+ }
158
+
159
+ export function usePointMultiObsLabels(
160
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
161
+ ) {
162
+ const obsLabelsCoordination = useComplexCoordination(
163
+ [
164
+ CoordinationType.OBS_TYPE,
165
+ CoordinationType.OBS_LABELS_TYPE,
166
+ ],
167
+ coordinationScopes,
168
+ coordinationScopesBy,
169
+ CoordinationType.POINT_LAYER,
170
+ );
171
+ const matchOnObj = useMemo(() => obsLabelsCoordination[0],
172
+ // imageCoordination reference changes each render,
173
+ // use coordinationScopes and coordinationScopesBy which are
174
+ // indirect dependencies here.
175
+ [coordinationScopes, coordinationScopesBy]);
176
+ const [indicesData, indicesDataStatus] = useObsLabelsMultiLevel(
177
+ loaders, dataset, false, matchOnObj, 1,
178
+ );
179
+ return [indicesData, indicesDataStatus];
180
+ }
181
+
182
+ export function useSegmentationMultiObsLocations(
183
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
184
+ ) {
185
+ const obsTypeCoordination = useComplexCoordinationSecondary(
186
+ [
187
+ CoordinationType.OBS_TYPE,
188
+ ],
189
+ coordinationScopes,
190
+ coordinationScopesBy,
191
+ CoordinationType.SEGMENTATION_LAYER,
192
+ CoordinationType.SEGMENTATION_CHANNEL,
193
+ );
194
+ const matchOnObj = useMemo(() => obsTypeCoordination[0],
195
+ // imageCoordination reference changes each render,
196
+ // use coordinationScopes and coordinationScopesBy which are
197
+ // indirect dependencies here.
198
+ [coordinationScopes, coordinationScopesBy]);
199
+ const [indicesData, indicesDataStatus] = useObsLocationsMultiLevel(
200
+ loaders, dataset, false, matchOnObj, 2,
201
+ );
202
+ return [indicesData, indicesDataStatus];
203
+ }
204
+
205
+ export function useSegmentationMultiObsSets(
206
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
207
+ ) {
208
+ const obsTypeCoordination = useComplexCoordinationSecondary(
209
+ [
210
+ CoordinationType.OBS_TYPE,
211
+ ],
212
+ coordinationScopes,
213
+ coordinationScopesBy,
214
+ CoordinationType.SEGMENTATION_LAYER,
215
+ CoordinationType.SEGMENTATION_CHANNEL,
216
+ );
217
+ const matchOnObj = useMemo(() => obsTypeCoordination[0],
218
+ // imageCoordination reference changes each render,
219
+ // use coordinationScopes and coordinationScopesBy which are
220
+ // indirect dependencies here.
221
+ [coordinationScopes, coordinationScopesBy]);
222
+ const [indicesData, indicesDataStatus] = useObsSetsMultiLevel(
223
+ loaders, dataset, false, matchOnObj, 2,
224
+ );
225
+ return [indicesData, indicesDataStatus];
226
+ }
package/src/data-hooks.js CHANGED
@@ -3,9 +3,10 @@ import { CoordinationType, DataType, STATUS } from '@vitessce/constants-internal
3
3
  import { fromEntries } from '@vitessce/utils';
4
4
  import { useQuery, useQueries } from '@tanstack/react-query';
5
5
  import {
6
- getMatchingLoader,
7
6
  useMultiCoordinationValues,
7
+ useComplexCoordination,
8
8
  useSetWarning,
9
+ getMatchingLoader,
9
10
  } from './state/hooks.js';
10
11
  import {
11
12
  LoaderNotFoundError,
@@ -76,6 +77,28 @@ export function useObsEmbeddingData(
76
77
  );
77
78
  }
78
79
 
80
+ export function useObsSpotsData(
81
+ loaders, dataset, isRequired,
82
+ coordinationSetters, initialCoordinationValues, matchOn,
83
+ ) {
84
+ return useDataType(
85
+ DataType.OBS_SPOTS,
86
+ loaders, dataset, isRequired,
87
+ coordinationSetters, initialCoordinationValues, matchOn,
88
+ );
89
+ }
90
+
91
+ export function useObsPointsData(
92
+ loaders, dataset, isRequired,
93
+ coordinationSetters, initialCoordinationValues, matchOn,
94
+ ) {
95
+ return useDataType(
96
+ DataType.OBS_POINTS,
97
+ loaders, dataset, isRequired,
98
+ coordinationSetters, initialCoordinationValues, matchOn,
99
+ );
100
+ }
101
+
79
102
  export function useObsLocationsData(
80
103
  loaders, dataset, isRequired,
81
104
  coordinationSetters, initialCoordinationValues, matchOn,
@@ -214,7 +237,7 @@ export function useFeatureSelection(
214
237
  const payload = await loader.loadGeneSelection({ selection: [featureId] });
215
238
  if (!payload) return null;
216
239
  const { data } = payload;
217
- return { data: data[0], dataKey: selection };
240
+ return { data: data[0], dataKey: featureId };
218
241
  }
219
242
  // Loader does not implement loadGeneSelection.
220
243
  const payload = await loader.load();
@@ -288,7 +311,9 @@ export function useObsFeatureMatrixIndices(
288
311
  placeholderData: placeholderObject,
289
312
  // Include the hook name in the queryKey to prevent the case in which an identical queryKey
290
313
  // in a different hook would cause an accidental cache hit.
291
- queryKey: [dataset, DataType.OBS_FEATURE_MATRIX, matchOn, 'useObsFeatureMatrixIndices'],
314
+ // Note: this uses the same key structure/suffix as
315
+ // getMatrixIndicesQueryKeyScopeTuplesAux for shared caching.
316
+ queryKey: [dataset, DataType.OBS_FEATURE_MATRIX, matchOn, isRequired, 'useObsFeatureMatrixIndices'],
292
317
  // Query function should return an object
293
318
  // { data, dataKey } where dataKey is the loaded gene selection.
294
319
  // TODO: use TypeScript to type the return value?
@@ -350,6 +375,78 @@ export function useObsFeatureMatrixIndices(
350
375
  return [loadedData, dataStatus, urls];
351
376
  }
352
377
 
378
+ export function useMultiObsPoints(
379
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
380
+ ) {
381
+ const obsTypeCoordination = useComplexCoordination(
382
+ [
383
+ CoordinationType.OBS_TYPE,
384
+ ],
385
+ coordinationScopes,
386
+ coordinationScopesBy,
387
+ CoordinationType.POINT_LAYER,
388
+ );
389
+ const matchOnObj = useMemo(() => obsTypeCoordination[0],
390
+ // imageCoordination reference changes each render,
391
+ // use coordinationScopes and coordinationScopesBy which are
392
+ // indirect dependencies here.
393
+ [coordinationScopes, coordinationScopesBy]);
394
+ const [obsPointsData, obsPointsDataStatus, obsPointsUrls] = useDataTypeMulti(
395
+ DataType.OBS_POINTS, loaders, dataset,
396
+ false, {}, {},
397
+ matchOnObj,
398
+ );
399
+ return [obsPointsData, obsPointsDataStatus, obsPointsUrls];
400
+ }
401
+
402
+ export function useMultiObsSpots(
403
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
404
+ ) {
405
+ const obsTypeCoordination = useComplexCoordination(
406
+ [
407
+ CoordinationType.OBS_TYPE,
408
+ ],
409
+ coordinationScopes,
410
+ coordinationScopesBy,
411
+ CoordinationType.SPOT_LAYER,
412
+ );
413
+ const matchOnObj = useMemo(() => obsTypeCoordination[0],
414
+ // imageCoordination reference changes each render,
415
+ // use coordinationScopes and coordinationScopesBy which are
416
+ // indirect dependencies here.
417
+ [coordinationScopes, coordinationScopesBy]);
418
+ const [obsSpotsData, obsSpotsDataStatus, obsSpotsUrls] = useDataTypeMulti(
419
+ DataType.OBS_SPOTS, loaders, dataset,
420
+ false, {}, {},
421
+ matchOnObj,
422
+ );
423
+ return [obsSpotsData, obsSpotsDataStatus, obsSpotsUrls];
424
+ }
425
+
426
+ export function useSpotMultiObsSets(
427
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
428
+ ) {
429
+ const obsTypeCoordination = useComplexCoordination(
430
+ [
431
+ CoordinationType.OBS_TYPE,
432
+ ],
433
+ coordinationScopes,
434
+ coordinationScopesBy,
435
+ CoordinationType.SPOT_LAYER,
436
+ );
437
+ const matchOnObj = useMemo(() => obsTypeCoordination[0],
438
+ // imageCoordination reference changes each render,
439
+ // use coordinationScopes and coordinationScopesBy which are
440
+ // indirect dependencies here.
441
+ [coordinationScopes, coordinationScopesBy]);
442
+ const [obsSetsData, obsSetsDataStatus, obsSetsUrls] = useDataTypeMulti(
443
+ DataType.OBS_SETS, loaders, dataset,
444
+ false, {}, {},
445
+ matchOnObj,
446
+ );
447
+ return [obsSetsData, obsSetsDataStatus, obsSetsUrls];
448
+ }
449
+
353
450
  export function useMultiObsLabels(
354
451
  coordinationScopes, obsType, loaders, dataset,
355
452
  ) {
@@ -363,11 +460,64 @@ export function useMultiObsLabels(
363
460
  { obsLabelsType, obsType },
364
461
  ])),
365
462
  ), [obsLabelsTypes, obsType]);
366
- const [obsLabelsData, obsLabelsDataStatus] = useDataTypeMulti(
463
+ const [obsLabelsData, obsLabelsDataStatus, obsLabelsUrls] = useDataTypeMulti(
367
464
  DataType.OBS_LABELS, loaders, dataset,
368
465
  false, {}, {},
369
466
  obsLabelsMatchOnObj,
370
467
  );
371
- const urls = null; // TODO?
372
- return [obsLabelsTypes, obsLabelsData, obsLabelsDataStatus, urls];
468
+ return [obsLabelsTypes, obsLabelsData, obsLabelsDataStatus, obsLabelsUrls];
469
+ }
470
+
471
+ export function useMultiObsSegmentations(
472
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
473
+ ) {
474
+ const imageCoordination = useComplexCoordination(
475
+ [
476
+ CoordinationType.FILE_UID,
477
+ ],
478
+ coordinationScopes,
479
+ coordinationScopesBy,
480
+ CoordinationType.SEGMENTATION_LAYER,
481
+ );
482
+ const matchOnObj = useMemo(() => imageCoordination[0],
483
+ // imageCoordination reference changes each render,
484
+ // use coordinationScopes and coordinationScopesBy which are
485
+ // indirect dependencies here.
486
+ [coordinationScopes, coordinationScopesBy]);
487
+ const [
488
+ obsSegmentationsData,
489
+ obsSegmentationsDataStatus,
490
+ obsSegmentationsUrls,
491
+ ] = useDataTypeMulti(
492
+ DataType.OBS_SEGMENTATIONS, loaders, dataset,
493
+ false, {}, {},
494
+ matchOnObj,
495
+ );
496
+ return [obsSegmentationsData, obsSegmentationsDataStatus, obsSegmentationsUrls];
497
+ }
498
+
499
+ export function useMultiImages(
500
+ coordinationScopes, coordinationScopesBy, loaders, dataset,
501
+ ) {
502
+ // TODO: delegate the generation of matchOnObj to a different hoook and pass as a parameter?
503
+ // (in all of the useMulti data hooks)?
504
+ const imageCoordination = useComplexCoordination(
505
+ [
506
+ CoordinationType.FILE_UID,
507
+ ],
508
+ coordinationScopes,
509
+ coordinationScopesBy,
510
+ CoordinationType.IMAGE_LAYER,
511
+ );
512
+ const matchOnObj = useMemo(() => imageCoordination[0],
513
+ // imageCoordination reference changes each render,
514
+ // use coordinationScopes and coordinationScopesBy which are
515
+ // indirect dependencies here.
516
+ [coordinationScopes, coordinationScopesBy]);
517
+ const [imageData, imageDataStatus, imageUrls] = useDataTypeMulti(
518
+ DataType.IMAGE, loaders, dataset,
519
+ false, {}, {},
520
+ matchOnObj,
521
+ );
522
+ return [imageData, imageDataStatus, imageUrls];
373
523
  }
package/src/hooks.js CHANGED
@@ -151,7 +151,7 @@ export function useReady(statusValues) {
151
151
  export function useUrls(urls) {
152
152
  const mergedUrls = useMemo(
153
153
  () => urls.filter(a => Array.isArray(a)).flat().filter((url, index, array) => {
154
- const firstIndex = array.findIndex(u => u.name === url.name);
154
+ const firstIndex = array.findIndex(u => u && url && u.name === url.name);
155
155
  return index === firstIndex;
156
156
  }),
157
157
  // eslint-disable-next-line react-hooks/exhaustive-deps
package/src/index.js CHANGED
@@ -17,9 +17,16 @@ export {
17
17
  useGridItemSize,
18
18
  } from './hooks.js';
19
19
  export {
20
+ useCoordinationScopes,
21
+ useCoordinationScopesBy,
20
22
  useInitialCoordination,
21
23
  useCoordination,
22
24
  useComplexCoordination,
25
+ useComplexCoordinationSecondary,
26
+ useMultiCoordinationScopes,
27
+ useMultiCoordinationScopesNonNull,
28
+ useMultiCoordinationScopesSecondary,
29
+ useMultiCoordinationScopesSecondaryNonNull,
23
30
  useMultiCoordinationValues,
24
31
  useMultiDatasetCoordination,
25
32
  useDatasetUids,
@@ -35,6 +42,8 @@ export {
35
42
  useSetWarning,
36
43
  useAuxiliaryCoordination,
37
44
  useComponentLayout,
45
+ useRemoveImageChannelInMetaCoordinationScopes,
46
+ useAddImageChannelInMetaCoordinationScopes,
38
47
  } from './state/hooks.js';
39
48
  export {
40
49
  useDescription,
@@ -44,7 +53,14 @@ export {
44
53
  useFeatureSelection,
45
54
  useObsFeatureMatrixIndices,
46
55
  useMultiObsLabels,
56
+ useMultiObsSpots,
57
+ useMultiObsPoints,
58
+ useSpotMultiObsSets,
59
+ useMultiObsSegmentations,
60
+ useMultiImages,
47
61
 
62
+ useObsSpotsData,
63
+ useObsPointsData,
48
64
  useObsLocationsData,
49
65
  useObsSegmentationsData,
50
66
  useNeighborhoodsData,
@@ -54,6 +70,15 @@ export {
54
70
  useFeatureLabelsData,
55
71
  useGenomicProfilesData,
56
72
  } from './data-hooks.js';
73
+ export {
74
+ usePointMultiObsLabels,
75
+ useSpotMultiFeatureSelection,
76
+ useSpotMultiObsFeatureMatrixIndices,
77
+ useSegmentationMultiFeatureSelection,
78
+ useSegmentationMultiObsFeatureMatrixIndices,
79
+ useSegmentationMultiObsLocations,
80
+ useSegmentationMultiObsSets,
81
+ } from './data-hooks-multilevel.js';
57
82
  export {
58
83
  useHasLoader,
59
84
  } from './data-hook-utils.js';
@@ -8,6 +8,7 @@ import {
8
8
  ClickAwayListener,
9
9
  Fade,
10
10
  } from '@material-ui/core';
11
+ import clsx from 'clsx';
11
12
  import { useVitessceContainer } from '../hooks.js';
12
13
 
13
14
  const useStyles = makeStyles(() => ({
@@ -30,6 +31,8 @@ export function PopperMenu(props) {
30
31
  children,
31
32
  buttonClassName,
32
33
  placement = 'bottom-end',
34
+ withPaper = true,
35
+ containerClassName,
33
36
  'aria-label': ariaLabel,
34
37
  } = props;
35
38
  const classes = useStyles();
@@ -49,7 +52,7 @@ export function PopperMenu(props) {
49
52
  const getTooltipContainer = useVitessceContainer(anchorRef);
50
53
 
51
54
  return (
52
- <div ref={anchorRef} className={classes.container}>
55
+ <div ref={anchorRef} className={clsx(classes.container, containerClassName)}>
53
56
  <IconButton
54
57
  aria-describedby={id}
55
58
  onClick={handleClick}
@@ -72,9 +75,11 @@ export function PopperMenu(props) {
72
75
  {({ TransitionProps }) => (
73
76
  <ClickAwayListener onClickAway={handleClose}>
74
77
  <Fade {...TransitionProps} timeout={100}>
75
- <Paper elevation={4} className={classes.paper}>
76
- <MenuList>{children}</MenuList>
77
- </Paper>
78
+ {withPaper ? (
79
+ <Paper elevation={4} className={classes.paper}>
80
+ <MenuList>{children}</MenuList>
81
+ </Paper>
82
+ ) : children}
78
83
  </Fade>
79
84
  </ClickAwayListener>
80
85
  )}
@@ -1,4 +1,5 @@
1
- import React, { useId } from 'react';
1
+ import React from 'react';
2
+ import { useId } from 'react-aria';
2
3
  import { TableCell, TableRow } from '@material-ui/core';
3
4
  import { capitalize } from '@vitessce/utils';
4
5
  import OptionSelect from './OptionSelect.js';