@vitessce/vit-s 3.3.2 → 3.3.3

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.
@@ -388,7 +388,7 @@ export function useFeatureSelectionMultiLevel(
388
388
  // (i.e., a single number, despite possibly different numbers of queries).
389
389
  // Reference: https://github.com/TanStack/query/issues/3049#issuecomment-1253201068
390
390
  // eslint-disable-next-line react-hooks/exhaustive-deps
391
- }, [featureQueries.reduce((a, h) => a + h.dataUpdatedAt, 0)]);
391
+ }, [featureQueries.reduce((a, h) => a + h.dataUpdatedAt, 0), queryKeyScopeTuples]);
392
392
 
393
393
  return [geneData, loadedGeneName, extents, normData, dataStatus];
394
394
  }
@@ -479,8 +479,10 @@ export function useObsFeatureMatrixIndicesMultiLevel(
479
479
  // We use .reduce to ensure the number of dependencies is stable
480
480
  // (i.e., a single number, despite possibly different numbers of queries).
481
481
  // Reference: https://github.com/TanStack/query/issues/3049#issuecomment-1253201068
482
+ // We also want to re-compute if any of the keys within the matchOnObj change,
483
+ // so we also use queryKeyScopeTuples as a dependency.
482
484
  // eslint-disable-next-line react-hooks/exhaustive-deps
483
- }, [indicesQueries.reduce((a, h) => a + h.dataUpdatedAt, 0)]);
485
+ }, [indicesQueries.reduce((a, h) => a + h.dataUpdatedAt, 0), queryKeyScopeTuples]);
484
486
 
485
487
  return [indicesData, dataStatus];
486
488
  }
@@ -535,7 +537,7 @@ function useDataTypeMultiLevel(
535
537
  // (i.e., a single number, despite possibly different numbers of queries).
536
538
  // Reference: https://github.com/TanStack/query/issues/3049#issuecomment-1253201068
537
539
  // eslint-disable-next-line react-hooks/exhaustive-deps
538
- }, [locationsQueries.reduce((a, h) => a + h.dataUpdatedAt, 0)]);
540
+ }, [locationsQueries.reduce((a, h) => a + h.dataUpdatedAt, 0), queryKeyScopeTuples]);
539
541
 
540
542
  return [locationsData, dataStatus];
541
543
  }
@@ -42,6 +42,8 @@ export function useSegmentationMultiFeatureSelection(
42
42
  // use coordinationScopes and coordinationScopesBy which are
43
43
  // indirect dependencies here.
44
44
  [coordinationScopes, coordinationScopesBy]);
45
+ const useMemoDependency = Object.values(featureSelectionCoordination[0] || {})
46
+ .flatMap(layerVal => Object.values(layerVal).map(cVal => cVal.featureSelection));
45
47
  const selections = useMemo(() => fromEntries(Object.entries(featureSelectionCoordination[0])
46
48
  .map(([layerScope, layerVal]) => ([
47
49
  layerScope,
@@ -52,8 +54,9 @@ export function useSegmentationMultiFeatureSelection(
52
54
  ]))),
53
55
  // Need to execute this more frequently, whenever the featureSelections update.
54
56
  [coordinationScopes, coordinationScopesBy,
55
- ...Object.values(featureSelectionCoordination[0] || {})
56
- .flatMap(layerVal => Object.values(layerVal).map(cVal => cVal.featureSelection)),
57
+ // We need to ensure there are always the same number of
58
+ // entries in the full dependency array.
59
+ JSON.stringify(useMemoDependency.length > 0 ? useMemoDependency : [null]),
57
60
  ]);
58
61
  const [
59
62
  featureData, loadedSelections, extents, normData, featureStatus,
package/src/data-hooks.js CHANGED
@@ -334,7 +334,7 @@ export function useObsFeatureMatrixIndices(
334
334
  obsIndex: payloadData.rows,
335
335
  featureIndex: payloadData.cols,
336
336
  },
337
- urls: [{ url, name: DataType.OBS_FEATURE_MATRIX }],
337
+ urls: (url ? [{ url, name: DataType.OBS_FEATURE_MATRIX }] : null),
338
338
  };
339
339
  }
340
340
  // No loadAttrs function.
@@ -377,6 +377,7 @@ export function useObsFeatureMatrixIndices(
377
377
 
378
378
  export function useMultiObsPoints(
379
379
  coordinationScopes, coordinationScopesBy, loaders, dataset,
380
+ mergeCoordination, viewUid,
380
381
  ) {
381
382
  const obsTypeCoordination = useComplexCoordination(
382
383
  [
@@ -394,13 +395,14 @@ export function useMultiObsPoints(
394
395
  const [obsPointsData, obsPointsDataStatus, obsPointsUrls] = useDataTypeMulti(
395
396
  DataType.OBS_POINTS, loaders, dataset,
396
397
  false, {}, {},
397
- matchOnObj,
398
+ matchOnObj, mergeCoordination, viewUid,
398
399
  );
399
400
  return [obsPointsData, obsPointsDataStatus, obsPointsUrls];
400
401
  }
401
402
 
402
403
  export function useMultiObsSpots(
403
404
  coordinationScopes, coordinationScopesBy, loaders, dataset,
405
+ mergeCoordination, viewUid,
404
406
  ) {
405
407
  const obsTypeCoordination = useComplexCoordination(
406
408
  [
@@ -418,7 +420,7 @@ export function useMultiObsSpots(
418
420
  const [obsSpotsData, obsSpotsDataStatus, obsSpotsUrls] = useDataTypeMulti(
419
421
  DataType.OBS_SPOTS, loaders, dataset,
420
422
  false, {}, {},
421
- matchOnObj,
423
+ matchOnObj, mergeCoordination, viewUid,
422
424
  );
423
425
  return [obsSpotsData, obsSpotsDataStatus, obsSpotsUrls];
424
426
  }
@@ -470,6 +472,7 @@ export function useMultiObsLabels(
470
472
 
471
473
  export function useMultiObsSegmentations(
472
474
  coordinationScopes, coordinationScopesBy, loaders, dataset,
475
+ mergeCoordination, viewUid,
473
476
  ) {
474
477
  const imageCoordination = useComplexCoordination(
475
478
  [
@@ -491,13 +494,14 @@ export function useMultiObsSegmentations(
491
494
  ] = useDataTypeMulti(
492
495
  DataType.OBS_SEGMENTATIONS, loaders, dataset,
493
496
  false, {}, {},
494
- matchOnObj,
497
+ matchOnObj, mergeCoordination, viewUid,
495
498
  );
496
499
  return [obsSegmentationsData, obsSegmentationsDataStatus, obsSegmentationsUrls];
497
500
  }
498
501
 
499
502
  export function useMultiImages(
500
503
  coordinationScopes, coordinationScopesBy, loaders, dataset,
504
+ mergeCoordination, viewUid,
501
505
  ) {
502
506
  // TODO: delegate the generation of matchOnObj to a different hoook and pass as a parameter?
503
507
  // (in all of the useMulti data hooks)?
@@ -517,7 +521,7 @@ export function useMultiImages(
517
521
  const [imageData, imageDataStatus, imageUrls] = useDataTypeMulti(
518
522
  DataType.IMAGE, loaders, dataset,
519
523
  false, {}, {},
520
- matchOnObj,
524
+ matchOnObj, mergeCoordination, viewUid,
521
525
  );
522
526
  return [imageData, imageDataStatus, imageUrls];
523
527
  }
package/src/index.js CHANGED
@@ -31,6 +31,7 @@ export {
31
31
  useMultiDatasetCoordination,
32
32
  useDatasetUids,
33
33
  useLoaders,
34
+ useMergeCoordination,
34
35
  useMatchingLoader,
35
36
  useViewConfigStore,
36
37
  useViewConfigStoreApi,
@@ -4,9 +4,10 @@ import { useRef, useCallback, useMemo } from 'react';
4
4
  import create from 'zustand';
5
5
  import createContext from 'zustand/context';
6
6
  import shallow from 'zustand/shallow';
7
- import { isMatch, merge, cloneDeep } from 'lodash-es';
7
+ import { isMatch, cloneDeep } from 'lodash-es';
8
8
  import { CoordinationType } from '@vitessce/constants-internal';
9
9
  import { fromEntries, capitalize } from '@vitessce/utils';
10
+ import { getCoordinationSpaceAndScopes } from '@vitessce/config';
10
11
  import {
11
12
  removeImageChannelInMetaCoordinationScopesHelper,
12
13
  addImageChannelInMetaCoordinationScopesHelper,
@@ -52,7 +53,15 @@ export function getScopes(coordinationScopes, metaSpace) {
52
53
  metaScopesArr.forEach((metaScope) => {
53
54
  // Merge the original coordinationScopes with the matching meta-coordinationScopes
54
55
  // from the coordinationSpace.
55
- result = merge(result, metaSpace[metaScope]);
56
+ let o1 = result;
57
+ const o2 = metaSpace[metaScope] || {};
58
+ Object.entries(o2).forEach(([cType, cScope]) => {
59
+ o1 = {
60
+ ...o1,
61
+ [cType]: cScope,
62
+ };
63
+ });
64
+ result = o1;
56
65
  });
57
66
  }
58
67
  }
@@ -80,7 +89,29 @@ export function getScopesBy(coordinationScopes, coordinationScopesBy, metaSpaceB
80
89
  metaScopesArr.forEach((metaScope) => {
81
90
  // Merge the original coordinationScopesBy with the matching meta-coordinationScopesBy
82
91
  // from the coordinationSpace.
83
- result = merge(result, metaSpaceBy[metaScope]);
92
+ let o1 = result;
93
+ const o2 = metaSpaceBy[metaScope] || {};
94
+ // Cannot simply use lodash merge(o1, o2)
95
+ // because we do not want to merge (objects/arrays) at the leaf
96
+ // (i.e., secondaryScopeVal) level.
97
+ // We want the values in o2 to take precedence over the values in o1.
98
+ Object.entries(o2).forEach(([primaryType, primaryObj]) => {
99
+ Object.entries(primaryObj).forEach(([secondaryType, secondaryObj]) => {
100
+ Object.entries(secondaryObj).forEach(([primaryScope, secondaryScopeVal]) => {
101
+ o1 = {
102
+ ...o1,
103
+ [primaryType]: {
104
+ ...(o1?.[primaryType] || {}),
105
+ [secondaryType]: {
106
+ ...(o1?.[primaryType]?.[secondaryType] || {}),
107
+ [primaryScope]: secondaryScopeVal,
108
+ },
109
+ },
110
+ };
111
+ });
112
+ });
113
+ });
114
+ result = o1;
84
115
  });
85
116
  }
86
117
  }
@@ -173,6 +204,87 @@ export const createViewConfigStore = (initialLoaders, initialConfig) => create(s
173
204
  },
174
205
  };
175
206
  }),
207
+ mergeCoordination: (newCoordinationValues, scopePrefix, viewUid) => set((state) => {
208
+ const { coordinationSpace, layout } = state.viewConfig;
209
+ const {
210
+ coordinationSpace: newCoordinationSpace,
211
+ coordinationScopes,
212
+ } = getCoordinationSpaceAndScopes(newCoordinationValues, scopePrefix);
213
+ // Merge coordination objects in coordination space
214
+ Object.entries(newCoordinationSpace).forEach(([coordinationType, coordinationObj]) => {
215
+ if (coordinationType === CoordinationType.META_COORDINATION_SCOPES) {
216
+ // Perform an extra level of merging for meta-coordination scopes.
217
+ Object.entries(coordinationObj).forEach(([coordinationScope, coordinationValue]) => {
218
+ coordinationSpace[coordinationType] = {
219
+ ...coordinationSpace[coordinationType],
220
+ [coordinationScope]: {
221
+ ...coordinationValue,
222
+ ...(coordinationSpace[coordinationType]?.[coordinationScope] || {}),
223
+ },
224
+ };
225
+ });
226
+ } else if (coordinationType === CoordinationType.META_COORDINATION_SCOPES_BY) {
227
+ // Perform two extra levels of merging for meta-coordination scopesBy.
228
+ Object.entries(coordinationObj).forEach(([coordinationScope, coordinationValue]) => {
229
+ Object.entries(coordinationValue).forEach(([primaryType, primaryObj]) => {
230
+ Object.entries(primaryObj).forEach(([secondaryType, secondaryObj]) => {
231
+ coordinationSpace[coordinationType] = {
232
+ ...coordinationSpace[coordinationType],
233
+ [coordinationScope]: {
234
+ ...(coordinationSpace[coordinationType]?.[coordinationScope] || {}),
235
+ [primaryType]: {
236
+ ...(coordinationSpace[coordinationType]?.[coordinationScope]?.[primaryType] || {}),
237
+ [secondaryType]: {
238
+ ...secondaryObj,
239
+ ...(coordinationSpace[coordinationType]?.[coordinationScope]?.[primaryType]?.[secondaryType] || {}),
240
+ },
241
+ },
242
+ },
243
+ };
244
+ });
245
+ });
246
+ });
247
+ } else {
248
+ coordinationSpace[coordinationType] = {
249
+ ...coordinationObj,
250
+ // Existing coordination values should be preserved,
251
+ // so that user-defined values take precedence over auto-initialization values.
252
+ ...(coordinationSpace[coordinationType] || {}),
253
+ };
254
+ }
255
+ });
256
+
257
+ const newViewConfig = {
258
+ ...state.viewConfig,
259
+ coordinationSpace: {
260
+ ...coordinationSpace,
261
+ },
262
+ layout: layout.map((viewObj) => {
263
+ if (viewObj.uid === viewUid) {
264
+ // Merge coordination scopes for views
265
+ return {
266
+ ...viewObj,
267
+ coordinationScopes: {
268
+ ...viewObj.coordinationScopes,
269
+ [CoordinationType.META_COORDINATION_SCOPES]: [
270
+ ...(coordinationScopes[CoordinationType.META_COORDINATION_SCOPES] || []),
271
+ ...(viewObj.coordinationScopes[CoordinationType.META_COORDINATION_SCOPES] || []),
272
+ ],
273
+ [CoordinationType.META_COORDINATION_SCOPES_BY]: [
274
+ ...(coordinationScopes[CoordinationType.META_COORDINATION_SCOPES_BY] || []),
275
+ ...(viewObj.coordinationScopes[CoordinationType.META_COORDINATION_SCOPES_BY] || []),
276
+ ],
277
+ },
278
+ };
279
+ }
280
+ return viewObj;
281
+ }),
282
+ };
283
+ // console.log('newViewConfig', newViewConfig);
284
+ return {
285
+ viewConfig: newViewConfig,
286
+ };
287
+ }),
176
288
  removeImageChannelInMetaCoordinationScopes: (coordinationScopesRaw, layerScope, channelScope) => set((state) => {
177
289
  const { coordinationSpace } = state.viewConfig;
178
290
  return {
@@ -969,6 +1081,16 @@ export function useSetLoaders() {
969
1081
  return useViewConfigStore(state => state.setLoaders);
970
1082
  }
971
1083
 
1084
+ /**
1085
+ * Obtain the loaders setter function from
1086
+ * the global app state.
1087
+ * @returns {function} The loaders setter function
1088
+ * in the `useViewConfigStore` store.
1089
+ */
1090
+ export function useMergeCoordination() {
1091
+ return useViewConfigStore(state => state.mergeCoordination);
1092
+ }
1093
+
972
1094
  /**
973
1095
  * Obtain the view config setter function from
974
1096
  * the global app state.
@@ -25,7 +25,7 @@ export function getMetaScope(coordinationScopes, coordinationSpace, parameter) {
25
25
  metaScopesArr.forEach((metaScope) => {
26
26
  // Merge the original coordinationScopes with the matching meta-coordinationScopes
27
27
  // from the coordinationSpace.
28
- if (metaSpace[metaScope][parameter]) {
28
+ if (metaSpace[metaScope]?.[parameter]) {
29
29
  latestMetaScope = metaScope;
30
30
  }
31
31
  });
@@ -97,7 +97,7 @@ function withDefaults(
97
97
  const defaultKeys = DATA_TYPE_COORDINATION_VALUE_USAGE[dataType]
98
98
  .filter(k => (
99
99
  Object.keys(defaultCoordinationValues).includes(k)
100
- && defaultCoordinationValues[k]
100
+ // && defaultCoordinationValues[k]
101
101
  && !Object.keys(coordinationValues).includes(k)
102
102
  ));
103
103
  const defaultValues = pick(defaultCoordinationValues, defaultKeys);