@vitessce/vit-s 3.1.3 → 3.2.0

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 (46) hide show
  1. package/dist/index.js +1673 -130
  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/state/hooks.d.ts +80 -21
  23. package/dist-tsc/state/hooks.d.ts.map +1 -1
  24. package/dist-tsc/state/hooks.js +428 -103
  25. package/dist-tsc/state/hooks.test.d.ts +2 -0
  26. package/dist-tsc/state/hooks.test.d.ts.map +1 -0
  27. package/dist-tsc/state/hooks.test.js +149 -0
  28. package/dist-tsc/state/spatial-reducers.d.ts +25 -0
  29. package/dist-tsc/state/spatial-reducers.d.ts.map +1 -0
  30. package/dist-tsc/state/spatial-reducers.js +251 -0
  31. package/dist-tsc/state/spatial-reducers.test.d.ts +2 -0
  32. package/dist-tsc/state/spatial-reducers.test.d.ts.map +1 -0
  33. package/dist-tsc/state/spatial-reducers.test.js +1382 -0
  34. package/package.json +5 -5
  35. package/src/data-hook-utils.js +19 -3
  36. package/src/data-hooks-multilevel-utils.js +571 -0
  37. package/src/data-hooks-multilevel-utils.test.js +110 -0
  38. package/src/data-hooks-multilevel.js +226 -0
  39. package/src/data-hooks.js +156 -6
  40. package/src/hooks.js +1 -1
  41. package/src/index.js +25 -0
  42. package/src/shared-mui/components.js +9 -4
  43. package/src/state/hooks.js +485 -104
  44. package/src/state/hooks.test.js +182 -0
  45. package/src/state/spatial-reducers.js +291 -0
  46. package/src/state/spatial-reducers.test.js +1432 -0
@@ -16,6 +16,7 @@ export function warn(error: AbstractLoaderError, setWarning: any): void;
16
16
  * keys are coordination type names and keys are values.
17
17
  */
18
18
  export function initCoordinationSpace(values: object, setters: object, initialValues: object): void;
19
+ export function dataQueryFn(ctx: any): Promise<any>;
19
20
  /**
20
21
  * Get data from a cells data type loader,
21
22
  * updating "ready" and URL state appropriately.
@@ -1 +1 @@
1
- {"version":3,"file":"data-hook-utils.d.ts","sourceRoot":"","sources":["../src/data-hook-utils.js"],"names":[],"mappings":"AAcA;;;;GAIG;AACH,4BAFW,mBAAmB,yBAS7B;AAED;;;;;;;;;;GAUG;AACH,8CATW,MAAM,WAGN,MAAM,iBAGN,MAAM,QAiBhB;AA4BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,oDAhBW,MAAM,WAEN,MAAM,cAEN,OAAO,uBAEP,MAAM,6BAGN,MAAM,uBA+ChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,yDAhBW,MAAM,WAEN,MAAM,cAEN,OAAO,uBAEP,MAAM,6BAGN,MAAM,0BAkEhB;AAED,+FAGC"}
1
+ {"version":3,"file":"data-hook-utils.d.ts","sourceRoot":"","sources":["../src/data-hook-utils.js"],"names":[],"mappings":"AAcA;;;;GAIG;AACH,4BAFW,mBAAmB,yBAS7B;AAED;;;;;;;;;;GAUG;AACH,8CATW,MAAM,WAGN,MAAM,iBAGN,MAAM,QAiBhB;AAGD,oDAuBC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,oDAhBW,MAAM,WAEN,MAAM,cAEN,OAAO,uBAEP,MAAM,6BAGN,MAAM,uBAkDhB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,yDAhBW,MAAM,WAEN,MAAM,cAEN,OAAO,uBAEP,MAAM,6BAGN,MAAM,0BA+EhB;AAED,+FAGC"}
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useMemo } from 'react';
2
+ import { useQuery, useQueries } from '@tanstack/react-query';
2
3
  import { capitalize, fromEntries } from '@vitessce/utils';
3
4
  import { STATUS } from '@vitessce/constants-internal';
4
- import { useQuery, useQueries } from '@tanstack/react-query';
5
5
  import { getMatchingLoader, useMatchingLoader, useSetWarning, } from './state/hooks.js';
6
6
  import { AbstractLoaderError, LoaderNotFoundError, } from './errors/index.js';
7
7
  /**
@@ -43,7 +43,7 @@ export function initCoordinationSpace(values, setters, initialValues) {
43
43
  }
44
44
  });
45
45
  }
46
- async function dataQueryFn(ctx) {
46
+ export async function dataQueryFn(ctx) {
47
47
  const { placeholderObject, loaders } = ctx.meta;
48
48
  // This ordering of the queryKey must match.
49
49
  const [dataset, dataType, matchOn, isRequired] = ctx.queryKey;
@@ -99,6 +99,9 @@ export function useDataType(dataType, loaders, dataset, isRequired, coordination
99
99
  placeholderData: placeholderObject,
100
100
  // Include the hook name in the queryKey to prevent the case in which an identical queryKey
101
101
  // in a different hook would cause an accidental cache hit.
102
+ // Note: same key structure/suffix as
103
+ // useDataTypeMulti() and getQueryKeyScopeTuplesAux()
104
+ // for shared caching.
102
105
  queryKey: [dataset, dataType, matchOn, isRequired, 'useDataType'],
103
106
  // Query function should return an object
104
107
  // { data, dataKey } where dataKey is the loaded gene selection.
@@ -152,6 +155,9 @@ export function useDataTypeMulti(dataType, loaders, dataset, isRequired, coordin
152
155
  placeholderData: placeholderObject,
153
156
  // Query key should match useDataType for shared cacheing
154
157
  // and correctness of dataQueryFn.
158
+ // Note: same key structure/suffix as
159
+ // useDataType() and getQueryKeyScopeTuplesAux()
160
+ // for shared caching.
155
161
  queryKey: [dataset, dataType, matchOn, isRequired, 'useDataType'],
156
162
  // Query function should return an object
157
163
  // { data, dataKey } where dataKey is the loaded gene selection.
@@ -182,8 +188,10 @@ export function useDataTypeMulti(dataType, loaders, dataset, isRequired, coordin
182
188
  // eslint-disable-next-line react-hooks/exhaustive-deps
183
189
  }, [anyError, setWarning]);
184
190
  // Convert data to object keyed by scopeKey.
185
- const data = useMemo(() => fromEntries(matchOnEntries.map(([scopeKey], i) => ([scopeKey, dataQueries[i].data?.data]))), [dataQueries]);
186
- return [data, dataStatus];
191
+ const data = useMemo(() => fromEntries(matchOnEntries.map(([scopeKey], i) => ([scopeKey, dataQueries[i].data?.data]))), [matchOnObj, dataStatus]);
192
+ // Convert data to object keyed by scopeKey.
193
+ const urls = useMemo(() => fromEntries(matchOnEntries.map(([scopeKey], i) => ([scopeKey, dataQueries[i].data?.urls]))), [dataQueries]);
194
+ return [data, dataStatus, urls];
187
195
  }
188
196
  export function useHasLoader(loaders, dataset, dataType, matchOn) {
189
197
  const loader = useMatchingLoader(loaders, dataset, dataType, matchOn);
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Get a flat list of tuples like (queryKey, scopeInfo)
3
+ * where scopeInfo is an object like { levelScopes, featureIndex, numFeatures }.
4
+ * Selections and matchOnObj are assumed to be objects with the same keys,
5
+ * both nested to the specified depth. For example, if depth is 2,
6
+ * the first level of keys might be for image layer scopes,
7
+ * and the second level of keys might be for channel scopes.
8
+ * @param {object} selections
9
+ * @param {object} matchOnObj
10
+ * @param {number} depth
11
+ * @param {string} dataset
12
+ * @param {string} dataType
13
+ * @returns
14
+ */
15
+ export function getFeatureSelectionQueryKeyScopeTuples(selections: object, matchOnObj: object, depth: number, dataset: string, dataType: string, isRequired: any): any;
16
+ /**
17
+ * Get a flat list of tuples like (queryKey, scopeInfo)
18
+ * where scopeInfo is an object like { levelScopes, featureIndex, numFeatures }.
19
+ * Selections and matchOnObj are assumed to be objects with the same keys,
20
+ * both nested to the specified depth. For example, if depth is 2,
21
+ * the first level of keys might be for image layer scopes,
22
+ * and the second level of keys might be for channel scopes.
23
+ * @param {object} matchOnObj
24
+ * @param {number} depth
25
+ * @param {string} dataset
26
+ * @param {string} dataType
27
+ * @returns
28
+ */
29
+ export function getMatrixIndicesQueryKeyScopeTuples(matchOnObj: object, depth: number, dataset: string, dataType: string, isRequired: any): any;
30
+ /**
31
+ * Get a flat list of tuples like (queryKey, scopeInfo)
32
+ * where scopeInfo is an object like { levelScopes, featureIndex, numFeatures }.
33
+ * Selections and matchOnObj are assumed to be objects with the same keys,
34
+ * both nested to the specified depth. For example, if depth is 2,
35
+ * the first level of keys might be for image layer scopes,
36
+ * and the second level of keys might be for channel scopes.
37
+ * @param {object} matchOnObj
38
+ * @param {number} depth
39
+ * @param {string} dataset
40
+ * @param {string} dataType
41
+ * @returns
42
+ */
43
+ export function getQueryKeyScopeTuples(matchOnObj: object, depth: number, dataset: string, dataType: string, isRequired: any): any;
44
+ /**
45
+ * For a list of paths into a nested object,
46
+ * initialize the object if the object keys do not yet exist.
47
+ * For the first level, the object is initialized to the return
48
+ * value of getBaseValue. For example, this allows initializing
49
+ * to an empty array (without reusing the same array object reference).
50
+ * @param {string[]} levelScopes
51
+ * @param {object} currObj
52
+ * @param {function} getBaseValue
53
+ * @returns The value at the end of the path specified by `levelScopes`.
54
+ */
55
+ export function initializeNestedObject(levelScopes: string[], currObj: object, getBaseValue: Function): any;
56
+ /**
57
+ * Nest query results.
58
+ * @param {array} queryKeyScopeTuples
59
+ * @param {array} flatQueryResults Return value of useQueries,
60
+ * after .map() to get inner data elements.
61
+ * @returns The nested object.
62
+ */
63
+ export function nestFeatureSelectionQueryResults(queryKeyScopeTuples: array, flatQueryResults: array): {};
64
+ /**
65
+ * Nest query results.
66
+ * @param {array} queryKeyScopeTuples
67
+ * @param {array} flatQueryResults Return value of useQueries,
68
+ * after .map() to get inner data elements.
69
+ * @returns The nested object.
70
+ */
71
+ export function nestQueryResults(queryKeyScopeTuples: array, flatQueryResults: array): {};
72
+ export function useFeatureSelectionMultiLevel(loaders: any, dataset: any, isRequired: any, matchOnObj: any, selections: any, depth: any): any[];
73
+ export function useObsFeatureMatrixIndicesMultiLevel(loaders: any, dataset: any, isRequired: any, matchOnObj: any, depth: any): any[];
74
+ export function useObsLocationsMultiLevel(loaders: any, dataset: any, isRequired: any, matchOnObj: any, depth: any): any[];
75
+ export function useObsSetsMultiLevel(loaders: any, dataset: any, isRequired: any, matchOnObj: any, depth: any): any[];
76
+ export function useObsLabelsMultiLevel(loaders: any, dataset: any, isRequired: any, matchOnObj: any, depth: any): any[];
77
+ //# sourceMappingURL=data-hooks-multilevel-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-hooks-multilevel-utils.d.ts","sourceRoot":"","sources":["../src/data-hooks-multilevel-utils.js"],"names":[],"mappings":"AA+CA;;;;;;;;;;;;;GAaG;AACH,mEAPW,MAAM,cACN,MAAM,SACN,MAAM,WACN,MAAM,YACN,MAAM,wBAmBhB;AA8BD;;;;;;;;;;;;GAYG;AACH,gEANW,MAAM,SACN,MAAM,WACN,MAAM,YACN,MAAM,wBAiBhB;AA+BD;;;;;;;;;;;;GAYG;AACH,mDANW,MAAM,SACN,MAAM,WACN,MAAM,YACN,MAAM,wBAiBhB;AAuBD;;;;;;;;;;GAUG;AACH,oDALW,MAAM,EAAE,WACR,MAAM,+BAOhB;AAED;;;;;;GAMG;AACH,0GAUC;AAED;;;;;;GAMG;AACH,0FAYC;AA+DD,gJA6DC;AAuCD,sIAqDC;AAyDD,2HAQC;AAED,sHAQC;AAED,wHAQC"}
@@ -0,0 +1,418 @@
1
+ import { useEffect, useMemo } from 'react';
2
+ import { useQueries } from '@tanstack/react-query';
3
+ import { extent } from 'd3-array';
4
+ import { DataType, STATUS } from '@vitessce/constants-internal';
5
+ import { getMatchingLoader, useSetWarning, } from './state/hooks.js';
6
+ import { LoaderNotFoundError, } from './errors/index.js';
7
+ import { warn, dataQueryFn, } from './data-hook-utils.js';
8
+ function getFeatureSelectionQueryKeyScopeTuplesAux(allSelections, allMatchOnObj, depth, dataset, dataType, isRequired, prevLevelScopes, currSelection, currMatchOn) {
9
+ // Base case
10
+ if (depth === 0) {
11
+ return currSelection
12
+ ?.map((featureId, featureIndex) => ([
13
+ // queryKey:
14
+ // TODO: use same key suffix as useFeatureSelection for shared caching?
15
+ [dataset, dataType, currMatchOn, featureId, isRequired, 'useFeatureSelectionMultiLevel'],
16
+ // scope info (for rolling up later)
17
+ { levelScopes: prevLevelScopes, featureIndex, numFeatures: currSelection.length },
18
+ ])) || [];
19
+ }
20
+ // Recursive case
21
+ return Object.entries(currSelection)
22
+ ?.flatMap(([levelScope, levelSelections]) => getFeatureSelectionQueryKeyScopeTuplesAux(allSelections, allMatchOnObj, depth - 1, dataset, dataType, isRequired, [...prevLevelScopes, levelScope], levelSelections, currMatchOn?.[levelScope])) || [];
23
+ }
24
+ /**
25
+ * Get a flat list of tuples like (queryKey, scopeInfo)
26
+ * where scopeInfo is an object like { levelScopes, featureIndex, numFeatures }.
27
+ * Selections and matchOnObj are assumed to be objects with the same keys,
28
+ * both nested to the specified depth. For example, if depth is 2,
29
+ * the first level of keys might be for image layer scopes,
30
+ * and the second level of keys might be for channel scopes.
31
+ * @param {object} selections
32
+ * @param {object} matchOnObj
33
+ * @param {number} depth
34
+ * @param {string} dataset
35
+ * @param {string} dataType
36
+ * @returns
37
+ */
38
+ export function getFeatureSelectionQueryKeyScopeTuples(selections, matchOnObj, depth, dataset, dataType, isRequired) {
39
+ // Begin recursion.
40
+ return getFeatureSelectionQueryKeyScopeTuplesAux(selections, matchOnObj, depth, dataset, dataType, isRequired, [], selections, matchOnObj);
41
+ }
42
+ function getMatrixIndicesQueryKeyScopeTuplesAux(allMatchOnObj, depth, dataset, dataType, isRequired, prevLevelScopes, currMatchOn) {
43
+ // Base case
44
+ if (depth === 0) {
45
+ return ([[
46
+ // queryKey:
47
+ // Note: this uses the same key structure/suffix as
48
+ // useObsFeatureMatrixIndices for shared caching.
49
+ [dataset, dataType, currMatchOn, isRequired, 'useObsFeatureMatrixIndices'],
50
+ // scope info (for rolling up later)
51
+ { levelScopes: prevLevelScopes },
52
+ ]]);
53
+ }
54
+ // Recursive case
55
+ return Object.entries(currMatchOn)
56
+ ?.flatMap(([levelScope, levelMatchOn]) => getMatrixIndicesQueryKeyScopeTuplesAux(allMatchOnObj, depth - 1, dataset, dataType, isRequired, [...prevLevelScopes, levelScope], levelMatchOn)) || [];
57
+ }
58
+ /**
59
+ * Get a flat list of tuples like (queryKey, scopeInfo)
60
+ * where scopeInfo is an object like { levelScopes, featureIndex, numFeatures }.
61
+ * Selections and matchOnObj are assumed to be objects with the same keys,
62
+ * both nested to the specified depth. For example, if depth is 2,
63
+ * the first level of keys might be for image layer scopes,
64
+ * and the second level of keys might be for channel scopes.
65
+ * @param {object} matchOnObj
66
+ * @param {number} depth
67
+ * @param {string} dataset
68
+ * @param {string} dataType
69
+ * @returns
70
+ */
71
+ export function getMatrixIndicesQueryKeyScopeTuples(matchOnObj, depth, dataset, dataType, isRequired) {
72
+ // Begin recursion.
73
+ return getMatrixIndicesQueryKeyScopeTuplesAux(matchOnObj, depth, dataset, dataType, isRequired, [], matchOnObj);
74
+ }
75
+ function getQueryKeyScopeTuplesAux(allMatchOnObj, depth, dataset, dataType, isRequired, prevLevelScopes, currMatchOn) {
76
+ // Base case
77
+ if (depth === 0) {
78
+ return ([[
79
+ // queryKey:
80
+ // Note: same key structure/suffix as
81
+ // useDataType() and useDataTypeMulti()
82
+ // for shared caching.
83
+ [dataset, dataType, currMatchOn, isRequired, 'useDataType'],
84
+ // scope info (for rolling up later)
85
+ { levelScopes: prevLevelScopes },
86
+ ]]);
87
+ }
88
+ // Recursive case
89
+ return Object.entries(currMatchOn)
90
+ ?.flatMap(([levelScope, levelMatchOn]) => getQueryKeyScopeTuplesAux(allMatchOnObj, depth - 1, dataset, dataType, isRequired, [...prevLevelScopes, levelScope], levelMatchOn)) || [];
91
+ }
92
+ /**
93
+ * Get a flat list of tuples like (queryKey, scopeInfo)
94
+ * where scopeInfo is an object like { levelScopes, featureIndex, numFeatures }.
95
+ * Selections and matchOnObj are assumed to be objects with the same keys,
96
+ * both nested to the specified depth. For example, if depth is 2,
97
+ * the first level of keys might be for image layer scopes,
98
+ * and the second level of keys might be for channel scopes.
99
+ * @param {object} matchOnObj
100
+ * @param {number} depth
101
+ * @param {string} dataset
102
+ * @param {string} dataType
103
+ * @returns
104
+ */
105
+ export function getQueryKeyScopeTuples(matchOnObj, depth, dataset, dataType, isRequired) {
106
+ // Begin recursion.
107
+ return getQueryKeyScopeTuplesAux(matchOnObj, depth, dataset, dataType, isRequired, [], matchOnObj);
108
+ }
109
+ function initializeNestedObjectAux(levelScopes, currObj, getBaseValue, currLevel) {
110
+ const currScope = levelScopes[currLevel];
111
+ const depthRemaining = levelScopes.length - currLevel;
112
+ // Base case.
113
+ if (depthRemaining === 0) {
114
+ return currObj;
115
+ }
116
+ // Recursive case.
117
+ if (!currObj[currScope]) {
118
+ if (depthRemaining === 1) {
119
+ // eslint-disable-next-line no-param-reassign
120
+ currObj[currScope] = getBaseValue();
121
+ }
122
+ else {
123
+ // eslint-disable-next-line no-param-reassign
124
+ currObj[currScope] = {};
125
+ }
126
+ }
127
+ return initializeNestedObjectAux(levelScopes, currObj[currScope], getBaseValue, currLevel + 1);
128
+ }
129
+ /**
130
+ * For a list of paths into a nested object,
131
+ * initialize the object if the object keys do not yet exist.
132
+ * For the first level, the object is initialized to the return
133
+ * value of getBaseValue. For example, this allows initializing
134
+ * to an empty array (without reusing the same array object reference).
135
+ * @param {string[]} levelScopes
136
+ * @param {object} currObj
137
+ * @param {function} getBaseValue
138
+ * @returns The value at the end of the path specified by `levelScopes`.
139
+ */
140
+ export function initializeNestedObject(levelScopes, currObj, getBaseValue) {
141
+ // Begin recursion.
142
+ return initializeNestedObjectAux(levelScopes, currObj, getBaseValue, 0);
143
+ }
144
+ /**
145
+ * Nest query results.
146
+ * @param {array} queryKeyScopeTuples
147
+ * @param {array} flatQueryResults Return value of useQueries,
148
+ * after .map() to get inner data elements.
149
+ * @returns The nested object.
150
+ */
151
+ export function nestFeatureSelectionQueryResults(queryKeyScopeTuples, flatQueryResults) {
152
+ const nestedData = {};
153
+ // eslint-disable-next-line no-unused-vars
154
+ queryKeyScopeTuples.forEach(([queryKey, { levelScopes, featureIndex, numFeatures }], i) => {
155
+ const getBaseValue = () => new Array(numFeatures);
156
+ const subObj = initializeNestedObject(levelScopes, nestedData, getBaseValue);
157
+ subObj[featureIndex] = flatQueryResults?.[i];
158
+ });
159
+ return nestedData;
160
+ }
161
+ /**
162
+ * Nest query results.
163
+ * @param {array} queryKeyScopeTuples
164
+ * @param {array} flatQueryResults Return value of useQueries,
165
+ * after .map() to get inner data elements.
166
+ * @returns The nested object.
167
+ */
168
+ export function nestQueryResults(queryKeyScopeTuples, flatQueryResults) {
169
+ const nestedData = {};
170
+ // eslint-disable-next-line no-unused-vars
171
+ queryKeyScopeTuples.forEach(([queryKey, { levelScopes }], i) => {
172
+ const getBaseValue = () => ({});
173
+ const subObj = initializeNestedObject(levelScopes, nestedData, getBaseValue);
174
+ Object.entries(flatQueryResults?.[i] || {}).forEach(([key, value]) => {
175
+ subObj[key] = value;
176
+ });
177
+ });
178
+ return nestedData;
179
+ }
180
+ async function featureSelectionQueryFn(ctx) {
181
+ // (copied from useFeatureSelection queryFn)
182
+ const { loaders } = ctx.meta;
183
+ const [dataset, dataType, matchOn, featureId, isRequired] = ctx.queryKey;
184
+ const loader = getMatchingLoader(loaders, dataset, dataType, matchOn);
185
+ if (loader) {
186
+ const implementsGeneSelection = typeof loader.loadGeneSelection === 'function';
187
+ if (implementsGeneSelection) {
188
+ const payload = await loader.loadGeneSelection({ selection: [featureId] });
189
+ if (!payload)
190
+ return null;
191
+ const { data } = payload;
192
+ const expressionData = data[0];
193
+ // Compute [min, max] extent of the data, and also a normalized version.
194
+ const dataExtent = extent(expressionData);
195
+ const [min, max] = dataExtent;
196
+ const ratio = 255 / (max - min);
197
+ const normData = new Uint8Array(expressionData.map(j => Math.floor((j - min) * ratio)));
198
+ return {
199
+ data: expressionData,
200
+ normData,
201
+ dataExtent,
202
+ dataKey: featureId,
203
+ };
204
+ }
205
+ // Loader does not implement loadGeneSelection.
206
+ const payload = await loader.load();
207
+ if (!payload)
208
+ return null;
209
+ const { data } = payload;
210
+ const { obsIndex, featureIndex, obsFeatureMatrix } = data;
211
+ // Compute expressionData
212
+ const geneIndex = featureIndex.indexOf(featureId);
213
+ const numGenes = featureIndex.length;
214
+ const numCells = obsIndex.length;
215
+ const expressionData = new Float32Array(numCells);
216
+ for (let cellIndex = 0; cellIndex < numCells; cellIndex += 1) {
217
+ expressionData[cellIndex] = obsFeatureMatrix.data[cellIndex * numGenes + geneIndex];
218
+ }
219
+ // Compute [min, max] extent of the data, and also a normalized version.
220
+ const dataExtent = extent(expressionData);
221
+ const [min, max] = dataExtent;
222
+ const ratio = 255 / (max - min);
223
+ const normData = new Uint8Array(expressionData.map(j => Math.floor((j - min) * ratio)));
224
+ return {
225
+ data: expressionData,
226
+ normData,
227
+ dataExtent,
228
+ dataKey: featureId,
229
+ };
230
+ }
231
+ // No loader was found.
232
+ if (isRequired) {
233
+ throw new LoaderNotFoundError(loaders, dataset, dataType, matchOn);
234
+ }
235
+ else {
236
+ return { data: null, dataKey: null };
237
+ }
238
+ }
239
+ export function useFeatureSelectionMultiLevel(loaders, dataset, isRequired, matchOnObj, selections, depth) {
240
+ const setWarning = useSetWarning();
241
+ // Create a flat list of tuples (queryKey, scopeInfo).
242
+ const queryKeyScopeTuples = useMemo(() => getFeatureSelectionQueryKeyScopeTuples(selections, matchOnObj, depth, dataset, DataType.OBS_FEATURE_MATRIX, isRequired), [selections, matchOnObj, depth, dataset]);
243
+ const featureQueries = useQueries({
244
+ queries: queryKeyScopeTuples.map(([queryKey]) => ({
245
+ structuralSharing: false,
246
+ placeholderData: null,
247
+ queryKey,
248
+ queryFn: featureSelectionQueryFn,
249
+ meta: { loaders },
250
+ })),
251
+ });
252
+ const anyLoading = featureQueries.some(q => q.isFetching);
253
+ const anyError = featureQueries.some(q => q.isError);
254
+ // eslint-disable-next-line no-nested-ternary
255
+ const dataStatus = anyLoading ? STATUS.LOADING : (anyError ? STATUS.ERROR : STATUS.SUCCESS);
256
+ const flatGeneData = featureQueries.map(q => q.data?.data || null);
257
+ const flatLoadedGeneName = featureQueries.map(q => q.data?.dataKey || null);
258
+ const flatExtents = featureQueries.map(q => q.data?.dataExtent || null);
259
+ const flatNormData = featureQueries.map(q => q.data?.normData || null);
260
+ useEffect(() => {
261
+ featureQueries
262
+ .map(q => q.error)
263
+ .filter(e => Boolean(e))
264
+ .forEach((error) => {
265
+ warn(error, setWarning);
266
+ });
267
+ // Deliberate dependency omissions: use indirect dependencies for efficiency.
268
+ // eslint-disable-next-line react-hooks/exhaustive-deps
269
+ }, [anyError, setWarning]);
270
+ // Need to re-nest the geneData and the loadedGeneName info.
271
+ const [geneData, loadedGeneName, extents, normData] = useMemo(() => {
272
+ const nestedGeneData = nestFeatureSelectionQueryResults(queryKeyScopeTuples, flatGeneData);
273
+ const nestedLoadedGeneName = nestFeatureSelectionQueryResults(queryKeyScopeTuples, flatLoadedGeneName);
274
+ const nestedExtents = nestFeatureSelectionQueryResults(queryKeyScopeTuples, flatExtents);
275
+ const nestedNormData = nestFeatureSelectionQueryResults(queryKeyScopeTuples, flatNormData);
276
+ return [nestedGeneData, nestedLoadedGeneName, nestedExtents, nestedNormData];
277
+ // We do not want this useMemo to execute on every re-render, only when the
278
+ // featureQueries results change. Unfortunately, the featureQueries array
279
+ // reference is not stable on each re-render, so we use dataUpdatedAt instead.
280
+ // We use .reduce to ensure the number of dependencies is stable
281
+ // (i.e., a single number, despite possibly different numbers of queries).
282
+ // Reference: https://github.com/TanStack/query/issues/3049#issuecomment-1253201068
283
+ // eslint-disable-next-line react-hooks/exhaustive-deps
284
+ }, [featureQueries.reduce((a, h) => a + h.dataUpdatedAt, 0)]);
285
+ return [geneData, loadedGeneName, extents, normData, dataStatus];
286
+ }
287
+ async function matrixIndicesQueryFn(ctx) {
288
+ // (copied from useFeatureSelection queryFn)
289
+ const { loaders } = ctx.meta;
290
+ const [dataset, dataType, matchOn, isRequired] = ctx.queryKey;
291
+ const loader = getMatchingLoader(loaders, dataset, dataType, matchOn);
292
+ if (loader) {
293
+ const implementsLoadAttrs = typeof loader.loadAttrs === 'function';
294
+ if (implementsLoadAttrs) {
295
+ const payload = await loader.loadAttrs();
296
+ if (!payload)
297
+ return null;
298
+ const { data: payloadData } = payload;
299
+ return {
300
+ data: {
301
+ obsIndex: payloadData.rows,
302
+ featureIndex: payloadData.cols,
303
+ },
304
+ };
305
+ }
306
+ // Loader does not implement loadAttrs.
307
+ const payload = await loader.load();
308
+ if (!payload)
309
+ return null;
310
+ const { data: payloadData } = payload;
311
+ return {
312
+ data: {
313
+ obsIndex: payloadData.obsIndex,
314
+ featureIndex: payloadData.featureIndex,
315
+ },
316
+ };
317
+ }
318
+ // No loader was found.
319
+ if (isRequired) {
320
+ throw new LoaderNotFoundError(loaders, dataset, dataType, matchOn);
321
+ }
322
+ else {
323
+ return { data: null, dataKey: null };
324
+ }
325
+ }
326
+ export function useObsFeatureMatrixIndicesMultiLevel(loaders, dataset, isRequired, matchOnObj, depth) {
327
+ const setWarning = useSetWarning();
328
+ // Create a flat list of tuples (queryKey, scopeInfo).
329
+ const queryKeyScopeTuples = useMemo(() => getMatrixIndicesQueryKeyScopeTuples(matchOnObj, depth, dataset, DataType.OBS_FEATURE_MATRIX, isRequired), [matchOnObj, depth, dataset]);
330
+ const indicesQueries = useQueries({
331
+ queries: queryKeyScopeTuples.map(([queryKey]) => ({
332
+ structuralSharing: false,
333
+ placeholderData: null,
334
+ queryKey,
335
+ queryFn: matrixIndicesQueryFn,
336
+ meta: { loaders },
337
+ })),
338
+ });
339
+ const anyLoading = indicesQueries.some(q => q.isFetching);
340
+ const anyError = indicesQueries.some(q => q.isError);
341
+ // eslint-disable-next-line no-nested-ternary
342
+ const dataStatus = anyLoading ? STATUS.LOADING : (anyError ? STATUS.ERROR : STATUS.SUCCESS);
343
+ const flatIndicesData = indicesQueries.map(q => q.data?.data || null);
344
+ useEffect(() => {
345
+ indicesQueries
346
+ .map(q => q.error)
347
+ .filter(e => Boolean(e))
348
+ .forEach((error) => {
349
+ warn(error, setWarning);
350
+ });
351
+ // Deliberate dependency omissions: use indirect dependencies for efficiency.
352
+ // eslint-disable-next-line react-hooks/exhaustive-deps
353
+ }, [anyError, setWarning]);
354
+ // Need to re-nest the geneData and the loadedGeneName info.
355
+ const indicesData = useMemo(() => {
356
+ const nestedIndicesData = nestQueryResults(queryKeyScopeTuples, flatIndicesData);
357
+ return nestedIndicesData;
358
+ // We do not want this useMemo to execute on every re-render, only when the
359
+ // featureQueries results change. Unfortunately, the featureQueries array
360
+ // reference is not stable on each re-render, so we use dataUpdatedAt instead.
361
+ // We use .reduce to ensure the number of dependencies is stable
362
+ // (i.e., a single number, despite possibly different numbers of queries).
363
+ // Reference: https://github.com/TanStack/query/issues/3049#issuecomment-1253201068
364
+ // eslint-disable-next-line react-hooks/exhaustive-deps
365
+ }, [indicesQueries.reduce((a, h) => a + h.dataUpdatedAt, 0)]);
366
+ return [indicesData, dataStatus];
367
+ }
368
+ function useDataTypeMultiLevel(loaders, dataset, isRequired, matchOnObj, depth, dataType) {
369
+ const setWarning = useSetWarning();
370
+ // Create a flat list of tuples (queryKey, scopeInfo).
371
+ const queryKeyScopeTuples = useMemo(() => getQueryKeyScopeTuples(matchOnObj, depth, dataset, dataType, isRequired), [matchOnObj, depth, dataset]);
372
+ const locationsQueries = useQueries({
373
+ queries: queryKeyScopeTuples.map(([queryKey]) => ({
374
+ structuralSharing: false,
375
+ placeholderData: null,
376
+ queryKey,
377
+ queryFn: dataQueryFn,
378
+ meta: { loaders },
379
+ })),
380
+ });
381
+ const anyLoading = locationsQueries.some(q => q.isFetching);
382
+ const anyError = locationsQueries.some(q => q.isError);
383
+ // eslint-disable-next-line no-nested-ternary
384
+ const dataStatus = anyLoading ? STATUS.LOADING : (anyError ? STATUS.ERROR : STATUS.SUCCESS);
385
+ const flatIndicesData = locationsQueries.map(q => q.data?.data || null);
386
+ useEffect(() => {
387
+ locationsQueries
388
+ .map(q => q.error)
389
+ .filter(e => Boolean(e))
390
+ .forEach((error) => {
391
+ warn(error, setWarning);
392
+ });
393
+ // Deliberate dependency omissions: use indirect dependencies for efficiency.
394
+ // eslint-disable-next-line react-hooks/exhaustive-deps
395
+ }, [anyError, setWarning]);
396
+ // Need to re-nest the geneData and the loadedGeneName info.
397
+ const locationsData = useMemo(() => {
398
+ const nestedIndicesData = nestQueryResults(queryKeyScopeTuples, flatIndicesData);
399
+ return nestedIndicesData;
400
+ // We do not want this useMemo to execute on every re-render, only when the
401
+ // featureQueries results change. Unfortunately, the featureQueries array
402
+ // reference is not stable on each re-render, so we use dataUpdatedAt instead.
403
+ // We use .reduce to ensure the number of dependencies is stable
404
+ // (i.e., a single number, despite possibly different numbers of queries).
405
+ // Reference: https://github.com/TanStack/query/issues/3049#issuecomment-1253201068
406
+ // eslint-disable-next-line react-hooks/exhaustive-deps
407
+ }, [locationsQueries.reduce((a, h) => a + h.dataUpdatedAt, 0)]);
408
+ return [locationsData, dataStatus];
409
+ }
410
+ export function useObsLocationsMultiLevel(loaders, dataset, isRequired, matchOnObj, depth) {
411
+ return useDataTypeMultiLevel(loaders, dataset, isRequired, matchOnObj, depth, DataType.OBS_LOCATIONS);
412
+ }
413
+ export function useObsSetsMultiLevel(loaders, dataset, isRequired, matchOnObj, depth) {
414
+ return useDataTypeMultiLevel(loaders, dataset, isRequired, matchOnObj, depth, DataType.OBS_SETS);
415
+ }
416
+ export function useObsLabelsMultiLevel(loaders, dataset, isRequired, matchOnObj, depth) {
417
+ return useDataTypeMultiLevel(loaders, dataset, isRequired, matchOnObj, depth, DataType.OBS_LABELS);
418
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data-hooks-multilevel-utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-hooks-multilevel-utils.test.d.ts","sourceRoot":"","sources":["../src/data-hooks-multilevel-utils.test.js"],"names":[],"mappings":""}