@vitessce/vit-s 2.0.3 → 3.0.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 (215) hide show
  1. package/dist/index.js +44072 -0
  2. package/dist-tsc/CallbackPublisher.d.ts +18 -0
  3. package/dist-tsc/CallbackPublisher.d.ts.map +1 -0
  4. package/dist-tsc/CallbackPublisher.js +62 -0
  5. package/dist-tsc/LoadingIndicator.d.ts +2 -0
  6. package/dist-tsc/LoadingIndicator.d.ts.map +1 -0
  7. package/dist-tsc/LoadingIndicator.js +26 -0
  8. package/dist-tsc/TitleInfo.d.ts +2 -0
  9. package/dist-tsc/TitleInfo.d.ts.map +1 -0
  10. package/dist-tsc/TitleInfo.js +74 -0
  11. package/dist-tsc/VitS.d.ts +50 -0
  12. package/dist-tsc/VitS.d.ts.map +1 -0
  13. package/dist-tsc/VitS.js +139 -0
  14. package/dist-tsc/VitessceGrid.d.ts +23 -0
  15. package/dist-tsc/VitessceGrid.d.ts.map +1 -0
  16. package/dist-tsc/VitessceGrid.js +63 -0
  17. package/dist-tsc/VitessceGrid.test.d.ts +2 -0
  18. package/dist-tsc/VitessceGrid.test.d.ts.map +1 -0
  19. package/dist-tsc/VitessceGrid.test.js +43 -0
  20. package/dist-tsc/Warning.d.ts +2 -0
  21. package/dist-tsc/Warning.d.ts.map +1 -0
  22. package/dist-tsc/Warning.js +45 -0
  23. package/dist-tsc/classNames.d.ts +8 -0
  24. package/dist-tsc/classNames.d.ts.map +1 -0
  25. package/dist-tsc/classNames.js +8 -0
  26. package/dist-tsc/data/AbstractLoader.d.ts +22 -0
  27. package/dist-tsc/data/AbstractLoader.d.ts.map +1 -0
  28. package/dist-tsc/data/AbstractLoader.js +18 -0
  29. package/dist-tsc/data/AbstractTwoStepLoader.d.ts +6 -0
  30. package/dist-tsc/data/AbstractTwoStepLoader.d.ts.map +1 -0
  31. package/dist-tsc/data/AbstractTwoStepLoader.js +7 -0
  32. package/dist-tsc/data/LoaderResult.d.ts +12 -0
  33. package/dist-tsc/data/LoaderResult.d.ts.map +1 -0
  34. package/dist-tsc/data/LoaderResult.js +12 -0
  35. package/dist-tsc/data/index.d.ts +4 -0
  36. package/dist-tsc/data/index.d.ts.map +1 -0
  37. package/dist-tsc/data/index.js +3 -0
  38. package/dist-tsc/data/loader-registry.d.ts +9 -0
  39. package/dist-tsc/data/loader-registry.d.ts.map +1 -0
  40. package/dist-tsc/data/loader-registry.js +27 -0
  41. package/dist-tsc/data-hook-utils.d.ts +65 -0
  42. package/dist-tsc/data-hook-utils.d.ts.map +1 -0
  43. package/dist-tsc/data-hook-utils.js +191 -0
  44. package/dist-tsc/data-hooks.d.ts +73 -0
  45. package/dist-tsc/data-hooks.d.ts.map +1 -0
  46. package/dist-tsc/data-hooks.js +254 -0
  47. package/dist-tsc/errors/AbstractLoaderError.d.ts +10 -0
  48. package/dist-tsc/errors/AbstractLoaderError.d.ts.map +1 -0
  49. package/dist-tsc/errors/AbstractLoaderError.js +13 -0
  50. package/dist-tsc/errors/DataSourceFetchError.d.ts +8 -0
  51. package/dist-tsc/errors/DataSourceFetchError.d.ts.map +1 -0
  52. package/dist-tsc/errors/DataSourceFetchError.js +13 -0
  53. package/dist-tsc/errors/DatasetNotFoundError.d.ts +6 -0
  54. package/dist-tsc/errors/DatasetNotFoundError.d.ts.map +1 -0
  55. package/dist-tsc/errors/DatasetNotFoundError.js +17 -0
  56. package/dist-tsc/errors/LoaderNotFoundError.d.ts +10 -0
  57. package/dist-tsc/errors/LoaderNotFoundError.d.ts.map +1 -0
  58. package/dist-tsc/errors/LoaderNotFoundError.js +17 -0
  59. package/dist-tsc/errors/LoaderValidationError.d.ts +10 -0
  60. package/dist-tsc/errors/LoaderValidationError.d.ts.map +1 -0
  61. package/dist-tsc/errors/LoaderValidationError.js +15 -0
  62. package/dist-tsc/errors/index.d.ts +6 -0
  63. package/dist-tsc/errors/index.d.ts.map +1 -0
  64. package/dist-tsc/errors/index.js +5 -0
  65. package/dist-tsc/hooks.d.ts +76 -0
  66. package/dist-tsc/hooks.d.ts.map +1 -0
  67. package/dist-tsc/hooks.js +262 -0
  68. package/dist-tsc/index.d.ts +12 -0
  69. package/dist-tsc/index.d.ts.map +1 -0
  70. package/dist-tsc/index.js +11 -17
  71. package/dist-tsc/mui-utils.d.ts +2 -0
  72. package/dist-tsc/mui-utils.d.ts.map +1 -0
  73. package/dist-tsc/mui-utils.js +44 -0
  74. package/dist-tsc/plugins.test.fixtures.d.ts +61 -0
  75. package/dist-tsc/plugins.test.fixtures.d.ts.map +1 -0
  76. package/dist-tsc/plugins.test.fixtures.js +76 -0
  77. package/dist-tsc/shared-mui/components.d.ts +2 -0
  78. package/dist-tsc/shared-mui/components.d.ts.map +1 -0
  79. package/dist-tsc/shared-mui/components.js +29 -0
  80. package/dist-tsc/shared-mui/container.d.ts +2 -0
  81. package/dist-tsc/shared-mui/container.d.ts.map +1 -0
  82. package/dist-tsc/shared-mui/container.js +211 -0
  83. package/dist-tsc/shared-mui/styles.d.ts +5 -0
  84. package/dist-tsc/shared-mui/styles.d.ts.map +1 -0
  85. package/dist-tsc/shared-mui/styles.js +109 -0
  86. package/dist-tsc/shared-plot-options/CellColorEncodingOption.d.ts +2 -0
  87. package/dist-tsc/shared-plot-options/CellColorEncodingOption.d.ts.map +1 -0
  88. package/dist-tsc/shared-plot-options/CellColorEncodingOption.js +17 -0
  89. package/dist-tsc/shared-plot-options/OptionSelect.d.ts +2 -0
  90. package/dist-tsc/shared-plot-options/OptionSelect.d.ts.map +1 -0
  91. package/dist-tsc/shared-plot-options/OptionSelect.js +12 -0
  92. package/dist-tsc/shared-plot-options/OptionsContainer.d.ts +2 -0
  93. package/dist-tsc/shared-plot-options/OptionsContainer.d.ts.map +1 -0
  94. package/dist-tsc/shared-plot-options/OptionsContainer.js +9 -0
  95. package/dist-tsc/shared-plot-options/index.d.ts +5 -0
  96. package/dist-tsc/shared-plot-options/index.d.ts.map +1 -0
  97. package/dist-tsc/shared-plot-options/index.js +4 -0
  98. package/dist-tsc/shared-plot-options/styles.d.ts +2 -0
  99. package/dist-tsc/shared-plot-options/styles.d.ts.map +1 -0
  100. package/dist-tsc/shared-plot-options/styles.js +48 -0
  101. package/dist-tsc/state/hooks.d.ts +242 -0
  102. package/dist-tsc/state/hooks.d.ts.map +1 -0
  103. package/dist-tsc/state/hooks.js +615 -0
  104. package/dist-tsc/title-styles.d.ts +2 -0
  105. package/dist-tsc/title-styles.d.ts.map +1 -0
  106. package/dist-tsc/title-styles.js +72 -0
  107. package/dist-tsc/view-config-utils.d.ts +31 -0
  108. package/dist-tsc/view-config-utils.d.ts.map +1 -0
  109. package/dist-tsc/view-config-utils.js +240 -0
  110. package/dist-tsc/view-config-utils.test.d.ts +2 -0
  111. package/dist-tsc/view-config-utils.test.d.ts.map +1 -0
  112. package/dist-tsc/view-config-utils.test.fixtures.d.ts +1049 -0
  113. package/dist-tsc/view-config-utils.test.fixtures.d.ts.map +1 -0
  114. package/dist-tsc/view-config-utils.test.fixtures.js +883 -0
  115. package/dist-tsc/view-config-utils.test.js +210 -0
  116. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.d.ts +8 -0
  117. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.d.ts.map +1 -0
  118. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.js +114 -0
  119. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.test.d.ts +2 -0
  120. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.test.d.ts.map +1 -0
  121. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.test.js +48 -0
  122. package/dist-tsc/vitessce-grid-layout/index.d.ts +2 -0
  123. package/dist-tsc/vitessce-grid-layout/index.d.ts.map +1 -0
  124. package/dist-tsc/vitessce-grid-layout/index.js +1 -0
  125. package/dist-tsc/vitessce-grid-layout/layout-utils.d.ts +15 -0
  126. package/dist-tsc/vitessce-grid-layout/layout-utils.d.ts.map +1 -0
  127. package/dist-tsc/vitessce-grid-layout/layout-utils.js +61 -0
  128. package/dist-tsc/vitessce-grid-layout/layout-utils.test.d.ts +2 -0
  129. package/dist-tsc/vitessce-grid-layout/layout-utils.test.d.ts.map +1 -0
  130. package/dist-tsc/vitessce-grid-layout/layout-utils.test.js +149 -0
  131. package/dist-tsc/vitessce-grid-utils.d.ts +13 -0
  132. package/dist-tsc/vitessce-grid-utils.d.ts.map +1 -0
  133. package/dist-tsc/vitessce-grid-utils.js +141 -0
  134. package/package.json +25 -11
  135. package/src/CallbackPublisher.js +7 -11
  136. package/src/LoadingIndicator.js +1 -2
  137. package/src/TitleInfo.js +12 -85
  138. package/src/VitS.js +143 -85
  139. package/src/VitessceGrid.js +35 -21
  140. package/src/VitessceGrid.test.jsx +23 -8
  141. package/src/Warning.js +4 -4
  142. package/src/data/AbstractLoader.js +2 -25
  143. package/src/data/AbstractTwoStepLoader.js +1 -1
  144. package/src/data/LoaderResult.js +5 -0
  145. package/src/data/index.js +3 -3
  146. package/src/data/loader-registry.js +22 -13
  147. package/src/data-hook-utils.js +119 -86
  148. package/src/data-hooks.js +150 -131
  149. package/src/errors/DataSourceFetchError.js +2 -2
  150. package/src/errors/DatasetNotFoundError.js +1 -1
  151. package/src/errors/LoaderNotFoundError.js +1 -1
  152. package/src/errors/LoaderValidationError.js +1 -1
  153. package/src/errors/index.js +5 -6
  154. package/src/hooks.js +81 -26
  155. package/src/index.js +23 -46
  156. package/src/mui-utils.js +52 -0
  157. package/src/shared-mui/components.js +10 -8
  158. package/src/shared-mui/container.js +1 -1
  159. package/src/shared-mui/styles.js +5 -2
  160. package/src/shared-plot-options/CellColorEncodingOption.js +3 -4
  161. package/src/shared-plot-options/OptionSelect.js +3 -3
  162. package/src/shared-plot-options/OptionsContainer.js +2 -5
  163. package/src/shared-plot-options/index.js +4 -4
  164. package/src/shared-plot-options/styles.js +2 -2
  165. package/src/state/hooks.js +75 -23
  166. package/src/title-styles.js +73 -0
  167. package/src/view-config-utils.js +54 -98
  168. package/src/view-config-utils.test.jsx +223 -0
  169. package/src/vitessce-grid-layout/VitessceGridLayout.js +51 -30
  170. package/src/vitessce-grid-layout/VitessceGridLayout.test.jsx +17 -9
  171. package/src/vitessce-grid-layout/index.js +1 -1
  172. package/src/vitessce-grid-layout/layout-utils.js +1 -1
  173. package/src/vitessce-grid-layout/layout-utils.test.js +2 -1
  174. package/src/vitessce-grid-utils.js +27 -15
  175. package/dist/index.mjs +0 -84801
  176. package/src/component-registry.js +0 -21
  177. package/src/errors/OptionsValidationError.js +0 -25
  178. package/src/file-options-schemas-legacy.js +0 -139
  179. package/src/file-options-schemas-legacy.test.js +0 -138
  180. package/src/file-options-schemas.js +0 -327
  181. package/src/file-options-schemas.test.js +0 -26
  182. package/src/joint-file-types-legacy.js +0 -298
  183. package/src/joint-file-types-legacy.test.js +0 -412
  184. package/src/joint-file-types.js +0 -171
  185. package/src/joint-file-types.test.js +0 -144
  186. package/src/json-schemas.js +0 -9
  187. package/src/plugins.js +0 -186
  188. package/src/plugins.test.js +0 -42
  189. package/src/schemas/config-0.1.0.schema.json +0 -85
  190. package/src/schemas/config-1.0.0.schema.json +0 -733
  191. package/src/schemas/config-1.0.1.schema.json +0 -909
  192. package/src/schemas/config-1.0.10.schema.json +0 -969
  193. package/src/schemas/config-1.0.11.schema.json +0 -990
  194. package/src/schemas/config-1.0.12.schema.json +0 -997
  195. package/src/schemas/config-1.0.13.schema.json +0 -1013
  196. package/src/schemas/config-1.0.14.schema.json +0 -1048
  197. package/src/schemas/config-1.0.15.schema.json +0 -1048
  198. package/src/schemas/config-1.0.16.schema.json +0 -1069
  199. package/src/schemas/config-1.0.16.schema.test.js +0 -32
  200. package/src/schemas/config-1.0.2.schema.json +0 -911
  201. package/src/schemas/config-1.0.3.schema.json +0 -911
  202. package/src/schemas/config-1.0.4.schema.json +0 -949
  203. package/src/schemas/config-1.0.5.schema.json +0 -949
  204. package/src/schemas/config-1.0.6.schema.json +0 -950
  205. package/src/schemas/config-1.0.7.schema.json +0 -950
  206. package/src/schemas/config-1.0.8.schema.json +0 -966
  207. package/src/schemas/config-1.0.9.schema.json +0 -965
  208. package/src/schemas/obsSets.schema.js +0 -195
  209. package/src/schemas/obsSetsTabular.schema.js +0 -39
  210. package/src/schemas/raster.schema.js +0 -121
  211. package/src/schemas/schema-schema.sh +0 -34
  212. package/src/schemas/schema.test-old.js +0 -49
  213. package/src/view-config-upgraders.js +0 -628
  214. package/src/view-config-utils.test.js +0 -95
  215. package/src/view-config-versions.js +0 -68
package/src/data-hooks.js CHANGED
@@ -1,15 +1,20 @@
1
1
  import { useState, useEffect, useMemo } from 'react';
2
2
  import { CoordinationType, DataType, STATUS } from '@vitessce/constants-internal';
3
3
  import { fromEntries } from '@vitessce/utils';
4
- import { useMatchingLoader, useMultiCoordinationValues, useSetWarning } from './state/hooks';
4
+ import { useQuery, useQueries } from '@tanstack/react-query';
5
+ import {
6
+ getMatchingLoader,
7
+ useMultiCoordinationValues,
8
+ useSetWarning,
9
+ } from './state/hooks.js';
5
10
  import {
6
11
  LoaderNotFoundError,
7
- } from './errors/index';
12
+ } from './errors/index.js';
8
13
  import {
9
14
  warn,
10
15
  useDataType,
11
16
  useDataTypeMulti,
12
- } from './data-hook-utils';
17
+ } from './data-hook-utils.js';
13
18
 
14
19
  /**
15
20
  * Get the dataset description string.
@@ -48,8 +53,6 @@ export function useDescription(loaders, dataset) {
48
53
  * datasets and data types to loader instances.
49
54
  * @param {string} dataset The key for a dataset,
50
55
  * used to identify which loader to use.
51
- * @param {function} addUrl A function to call to update
52
- * the URL list.
53
56
  * @param {boolean} isRequired Should a warning be thrown if
54
57
  * loading is unsuccessful?
55
58
  * @param {object} coordinationSetters Object where
@@ -63,111 +66,111 @@ export function useDescription(loaders, dataset) {
63
66
  * number of items in the cells object.
64
67
  */
65
68
  export function useObsEmbeddingData(
66
- loaders, dataset, addUrl, isRequired,
69
+ loaders, dataset, isRequired,
67
70
  coordinationSetters, initialCoordinationValues, matchOn,
68
71
  ) {
69
72
  return useDataType(
70
73
  DataType.OBS_EMBEDDING,
71
- loaders, dataset, addUrl, isRequired,
74
+ loaders, dataset, isRequired,
72
75
  coordinationSetters, initialCoordinationValues, matchOn,
73
76
  );
74
77
  }
75
78
 
76
79
  export function useObsLocationsData(
77
- loaders, dataset, addUrl, isRequired,
80
+ loaders, dataset, isRequired,
78
81
  coordinationSetters, initialCoordinationValues, matchOn,
79
82
  ) {
80
83
  return useDataType(
81
84
  DataType.OBS_LOCATIONS,
82
- loaders, dataset, addUrl, isRequired,
85
+ loaders, dataset, isRequired,
83
86
  coordinationSetters, initialCoordinationValues, matchOn,
84
87
  );
85
88
  }
86
89
 
87
90
  export function useObsLabelsData(
88
- loaders, dataset, addUrl, isRequired,
91
+ loaders, dataset, isRequired,
89
92
  coordinationSetters, initialCoordinationValues, matchOn,
90
93
  ) {
91
94
  return useDataType(
92
95
  DataType.OBS_LABELS,
93
- loaders, dataset, addUrl, isRequired,
96
+ loaders, dataset, isRequired,
94
97
  coordinationSetters, initialCoordinationValues, matchOn,
95
98
  );
96
99
  }
97
100
 
98
101
  export function useObsSegmentationsData(
99
- loaders, dataset, addUrl, isRequired,
102
+ loaders, dataset, isRequired,
100
103
  coordinationSetters, initialCoordinationValues, matchOn,
101
104
  ) {
102
105
  return useDataType(
103
106
  DataType.OBS_SEGMENTATIONS,
104
- loaders, dataset, addUrl, isRequired,
107
+ loaders, dataset, isRequired,
105
108
  coordinationSetters, initialCoordinationValues, matchOn,
106
109
  );
107
110
  }
108
111
 
109
112
  export function useObsSetsData(
110
- loaders, dataset, addUrl, isRequired,
113
+ loaders, dataset, isRequired,
111
114
  coordinationSetters, initialCoordinationValues, matchOn,
112
115
  ) {
113
116
  return useDataType(
114
117
  DataType.OBS_SETS,
115
- loaders, dataset, addUrl, isRequired,
118
+ loaders, dataset, isRequired,
116
119
  coordinationSetters, initialCoordinationValues, matchOn,
117
120
  );
118
121
  }
119
122
 
120
123
  export function useObsFeatureMatrixData(
121
- loaders, dataset, addUrl, isRequired,
124
+ loaders, dataset, isRequired,
122
125
  coordinationSetters, initialCoordinationValues, matchOn,
123
126
  ) {
124
127
  return useDataType(
125
128
  DataType.OBS_FEATURE_MATRIX,
126
- loaders, dataset, addUrl, isRequired,
129
+ loaders, dataset, isRequired,
127
130
  coordinationSetters, initialCoordinationValues, matchOn,
128
131
  );
129
132
  }
130
133
 
131
134
  export function useFeatureLabelsData(
132
- loaders, dataset, addUrl, isRequired,
135
+ loaders, dataset, isRequired,
133
136
  coordinationSetters, initialCoordinationValues, matchOn,
134
137
  ) {
135
138
  return useDataType(
136
139
  DataType.FEATURE_LABELS,
137
- loaders, dataset, addUrl, isRequired,
140
+ loaders, dataset, isRequired,
138
141
  coordinationSetters, initialCoordinationValues, matchOn,
139
142
  );
140
143
  }
141
144
 
142
145
  export function useImageData(
143
- loaders, dataset, addUrl, isRequired,
146
+ loaders, dataset, isRequired,
144
147
  coordinationSetters, initialCoordinationValues, matchOn,
145
148
  ) {
146
149
  return useDataType(
147
150
  DataType.IMAGE,
148
- loaders, dataset, addUrl, isRequired,
151
+ loaders, dataset, isRequired,
149
152
  coordinationSetters, initialCoordinationValues, matchOn,
150
153
  );
151
154
  }
152
155
 
153
156
  export function useGenomicProfilesData(
154
- loaders, dataset, addUrl, isRequired,
157
+ loaders, dataset, isRequired,
155
158
  coordinationSetters, initialCoordinationValues, matchOn,
156
159
  ) {
157
160
  return useDataType(
158
161
  DataType.GENOMIC_PROFILES,
159
- loaders, dataset, addUrl, isRequired,
162
+ loaders, dataset, isRequired,
160
163
  coordinationSetters, initialCoordinationValues, matchOn,
161
164
  );
162
165
  }
163
166
 
164
167
  export function useNeighborhoodsData(
165
- loaders, dataset, addUrl, isRequired,
168
+ loaders, dataset, isRequired,
166
169
  coordinationSetters, initialCoordinationValues, matchOn,
167
170
  ) {
168
171
  return useDataType(
169
172
  DataType.NEIGHBORHOODS,
170
- loaders, dataset, addUrl, isRequired,
173
+ loaders, dataset, isRequired,
171
174
  coordinationSetters, initialCoordinationValues, matchOn,
172
175
  );
173
176
  }
@@ -193,73 +196,71 @@ export function useFeatureSelection(
193
196
  selection,
194
197
  matchOn,
195
198
  ) {
196
- const [geneData, setGeneData] = useState();
197
- const [status, setStatus] = useState(STATUS.LOADING);
198
- const [loadedGeneName, setLoadedGeneName] = useState(null);
199
-
200
199
  const setWarning = useSetWarning();
201
- const loader = useMatchingLoader(loaders, dataset, DataType.OBS_FEATURE_MATRIX, matchOn);
202
-
203
- useEffect(() => {
204
- if (!selection) {
205
- setGeneData(null);
206
- setLoadedGeneName(null);
207
- setStatus(STATUS.SUCCESS);
208
- return;
209
- }
210
- if (loader) {
211
- setGeneData(null);
212
- setLoadedGeneName(null);
213
- setStatus(STATUS.LOADING);
214
- const implementsGeneSelection = typeof loader.loadGeneSelection === 'function';
215
- if (implementsGeneSelection) {
216
- loader
217
- .loadGeneSelection({ selection })
218
- .catch(e => warn(e, setWarning))
219
- .then((payload) => {
220
- if (!payload) return;
200
+ const featureQueries = useQueries({
201
+ queries: selection?.map(featureId => ({
202
+ structuralSharing: false,
203
+ placeholderData: null,
204
+ queryKey: [dataset, DataType.OBS_FEATURE_MATRIX, matchOn, featureId, 'useFeatureSelection'],
205
+ // Query function should return an object
206
+ // { data, dataKey } where dataKey is the loaded gene selection.
207
+ queryFn: async (ctx) => {
208
+ const loader = getMatchingLoader(
209
+ ctx.meta.loaders, ctx.queryKey[0], ctx.queryKey[1], ctx.queryKey[2],
210
+ );
211
+ if (loader) {
212
+ const implementsGeneSelection = typeof loader.loadGeneSelection === 'function';
213
+ if (implementsGeneSelection) {
214
+ const payload = await loader.loadGeneSelection({ selection: [featureId] });
215
+ if (!payload) return null;
221
216
  const { data } = payload;
222
- setGeneData(data);
223
- setStatus(STATUS.SUCCESS);
224
- setLoadedGeneName(selection);
225
- });
226
- } else {
227
- loader.load().catch(e => warn(e, setWarning)).then((payload) => {
228
- if (!payload) return;
217
+ return { data: data[0], dataKey: selection };
218
+ }
219
+ // Loader does not implement loadGeneSelection.
220
+ const payload = await loader.load();
221
+ if (!payload) return null;
229
222
  const { data } = payload;
230
223
  const { obsIndex, featureIndex, obsFeatureMatrix } = data;
231
- const expressionDataForSelection = selection.map((sel) => {
232
- const geneIndex = featureIndex.indexOf(sel);
233
- const numGenes = featureIndex.length;
234
- const numCells = obsIndex.length;
235
- const expressionData = new Uint8Array(numCells);
236
- for (let cellIndex = 0; cellIndex < numCells; cellIndex += 1) {
237
- expressionData[cellIndex] = obsFeatureMatrix.data[cellIndex * numGenes + geneIndex];
238
- }
239
- return expressionData;
240
- });
241
- setGeneData(expressionDataForSelection);
242
- setStatus(STATUS.SUCCESS);
243
- setLoadedGeneName(selection);
244
- });
245
- }
246
- } else {
247
- setGeneData(null);
248
- setLoadedGeneName(null);
249
- if (isRequired) {
250
- warn(
251
- new LoaderNotFoundError(loaders, dataset, DataType.OBS_FEATURE_MATRIX, matchOn),
252
- setWarning,
253
- );
254
- setStatus(STATUS.ERROR);
255
- } else {
256
- setStatus(STATUS.SUCCESS);
257
- }
258
- }
224
+ // Compute expressionData
225
+ const geneIndex = featureIndex.indexOf(featureId);
226
+ const numGenes = featureIndex.length;
227
+ const numCells = obsIndex.length;
228
+ const expressionData = new Float32Array(numCells);
229
+ for (let cellIndex = 0; cellIndex < numCells; cellIndex += 1) {
230
+ expressionData[cellIndex] = obsFeatureMatrix.data[cellIndex * numGenes + geneIndex];
231
+ }
232
+ return { data: expressionData, dataKey: featureId };
233
+ }
234
+ // No loader was found.
235
+ if (isRequired) {
236
+ throw new LoaderNotFoundError(loaders, dataset, DataType.OBS_FEATURE_MATRIX, matchOn);
237
+ } else {
238
+ return { data: null, dataKey: null };
239
+ }
240
+ },
241
+ meta: { loaders },
242
+ })) || [],
243
+ });
244
+ const anyLoading = featureQueries.some(q => q.isFetching);
245
+ const anyError = featureQueries.some(q => q.isError);
246
+ // eslint-disable-next-line no-nested-ternary
247
+ const dataStatus = anyLoading ? STATUS.LOADING : (anyError ? STATUS.ERROR : STATUS.SUCCESS);
248
+ const isSuccess = dataStatus === STATUS.SUCCESS;
249
+ const geneData = isSuccess ? featureQueries.map(q => q.data?.data || null) : null;
250
+ const loadedGeneName = isSuccess ? featureQueries.map(q => q.data?.dataKey || null) : null;
251
+
252
+ useEffect(() => {
253
+ featureQueries
254
+ .map(q => q.error)
255
+ .filter(e => Boolean(e))
256
+ .forEach((error) => {
257
+ warn(error, setWarning);
258
+ });
259
+ // Deliberate dependency omissions: use indirect dependencies for efficiency.
259
260
  // eslint-disable-next-line react-hooks/exhaustive-deps
260
- }, [loader, selection]);
261
+ }, [anyError, setWarning]);
261
262
 
262
- return [geneData, loadedGeneName, status];
263
+ return [geneData, loadedGeneName, dataStatus];
263
264
  }
264
265
 
265
266
  /**
@@ -272,68 +273,85 @@ export function useFeatureSelection(
272
273
  * datasets and data types to loader instances.
273
274
  * @param {string} dataset The key for a dataset,
274
275
  * used to identify which loader to use.
275
- * @param {function} addUrl A function to call to update
276
- * the URL list.
277
276
  * @param {boolean} isRequired Should a warning be thrown if
278
277
  * loading is unsuccessful?
279
278
  * @returns {object} [attrs] { rows, cols } object containing cell and gene names.
280
279
  */
281
280
  export function useObsFeatureMatrixIndices(
282
- loaders, dataset, addUrl, isRequired, matchOn,
281
+ loaders, dataset, isRequired, matchOn,
283
282
  ) {
284
- const [data, setData] = useState({});
285
- const [status, setStatus] = useState(STATUS.LOADING);
286
-
287
283
  const setWarning = useSetWarning();
288
- const loader = useMatchingLoader(loaders, dataset, DataType.OBS_FEATURE_MATRIX, matchOn);
289
-
290
- useEffect(() => {
291
- if (loader) {
292
- setStatus(STATUS.LOADING);
293
- const implementsLoadAttrs = typeof loader.loadAttrs === 'function';
294
- if (implementsLoadAttrs) {
295
- loader.loadAttrs().catch(e => warn(e, setWarning)).then((payload) => {
296
- if (!payload) return;
297
- const { data: payloadData, url } = payload;
298
- setData({
299
- obsIndex: payloadData.rows,
300
- featureIndex: payloadData.cols,
301
- });
302
- addUrl(url, DataType.OBS_FEATURE_MATRIX);
303
- setStatus(STATUS.SUCCESS);
304
- });
305
- } else {
306
- loader.load().catch(e => warn(e, setWarning)).then((payload) => {
307
- if (!payload) return;
284
+ const placeholderObject = useMemo(() => ({}), []);
285
+ const indicesQuery = useQuery({
286
+ // TODO: only enable once `loaders` is available?
287
+ structuralSharing: false,
288
+ placeholderData: placeholderObject,
289
+ // Include the hook name in the queryKey to prevent the case in which an identical queryKey
290
+ // in a different hook would cause an accidental cache hit.
291
+ queryKey: [dataset, DataType.OBS_FEATURE_MATRIX, matchOn, 'useObsFeatureMatrixIndices'],
292
+ // Query function should return an object
293
+ // { data, dataKey } where dataKey is the loaded gene selection.
294
+ // TODO: use TypeScript to type the return value?
295
+ queryFn: async (ctx) => {
296
+ const loader = getMatchingLoader(
297
+ ctx.meta.loaders, ctx.queryKey[0], ctx.queryKey[1], ctx.queryKey[2],
298
+ );
299
+ if (loader) {
300
+ const implementsLoadAttrs = typeof loader.loadAttrs === 'function';
301
+ if (implementsLoadAttrs) {
302
+ // Has loadAttrs function.
303
+ const payload = await loader.loadAttrs();
304
+ if (!payload) return placeholderObject;
308
305
  const { data: payloadData, url } = payload;
309
- setData({
306
+
307
+ return {
308
+ data: {
309
+ obsIndex: payloadData.rows,
310
+ featureIndex: payloadData.cols,
311
+ },
312
+ urls: [{ url, name: DataType.OBS_FEATURE_MATRIX }],
313
+ };
314
+ }
315
+ // No loadAttrs function.
316
+ const payload = await loader.load();
317
+ if (!payload) return placeholderObject;
318
+ const { data: payloadData, url } = payload;
319
+ return {
320
+ data: {
310
321
  obsIndex: payloadData.obsIndex,
311
322
  featureIndex: payloadData.featureIndex,
312
- });
313
- addUrl(url, DataType.OBS_FEATURE_MATRIX);
314
- setStatus(STATUS.SUCCESS);
315
- });
323
+ },
324
+ urls: [{ url, name: DataType.OBS_FEATURE_MATRIX }],
325
+ };
316
326
  }
317
- } else {
318
- setData({});
327
+ // No loader was found.
319
328
  if (isRequired) {
320
- warn(
321
- new LoaderNotFoundError(loaders, dataset, DataType.OBS_FEATURE_MATRIX, matchOn),
322
- setWarning,
323
- );
324
- setStatus(STATUS.ERROR);
329
+ // Status: error
330
+ throw new LoaderNotFoundError(loaders, dataset, DataType.OBS_FEATURE_MATRIX, matchOn);
325
331
  } else {
326
- setStatus(STATUS.SUCCESS);
332
+ // Status: success
333
+ return { data: placeholderObject, dataKey: null };
327
334
  }
335
+ },
336
+ meta: { loaders },
337
+ });
338
+ const { data, status, isFetching, error } = indicesQuery;
339
+ const loadedData = data?.data || placeholderObject;
340
+
341
+ const dataStatus = isFetching ? STATUS.LOADING : status;
342
+ const urls = data?.urls;
343
+
344
+ useEffect(() => {
345
+ if (error) {
346
+ setWarning(error.message);
328
347
  }
329
- // eslint-disable-next-line react-hooks/exhaustive-deps
330
- }, [loader]);
348
+ }, [error, setWarning]);
331
349
 
332
- return [data, status];
350
+ return [loadedData, dataStatus, urls];
333
351
  }
334
352
 
335
353
  export function useMultiObsLabels(
336
- coordinationScopes, obsType, loaders, dataset, addUrl,
354
+ coordinationScopes, obsType, loaders, dataset,
337
355
  ) {
338
356
  const obsLabelsTypes = useMultiCoordinationValues(
339
357
  CoordinationType.OBS_LABELS_TYPE,
@@ -347,8 +365,9 @@ export function useMultiObsLabels(
347
365
  ), [obsLabelsTypes, obsType]);
348
366
  const [obsLabelsData, obsLabelsDataStatus] = useDataTypeMulti(
349
367
  DataType.OBS_LABELS, loaders, dataset,
350
- addUrl, false, {}, {},
368
+ false, {}, {},
351
369
  obsLabelsMatchOnObj,
352
370
  );
353
- return [obsLabelsTypes, obsLabelsData, obsLabelsDataStatus];
371
+ const urls = null; // TODO?
372
+ return [obsLabelsTypes, obsLabelsData, obsLabelsDataStatus, urls];
354
373
  }
@@ -1,4 +1,4 @@
1
- import AbstractLoaderError from './AbstractLoaderError';
1
+ import AbstractLoaderError from './AbstractLoaderError.js';
2
2
 
3
3
  export default class DataSourceFetchError extends AbstractLoaderError {
4
4
  constructor(source, url, headers) {
@@ -10,6 +10,6 @@ export default class DataSourceFetchError extends AbstractLoaderError {
10
10
 
11
11
  warnInConsole() {
12
12
  const { source, url, headers } = this;
13
- console.warn(`${source} failed to fetch from ${url} with headers ${headers}`);
13
+ console.warn(`${source} failed to fetch from ${url} with headers ${JSON.stringify(headers)}`);
14
14
  }
15
15
  }
@@ -1,4 +1,4 @@
1
- import AbstractLoaderError from './AbstractLoaderError';
1
+ import AbstractLoaderError from './AbstractLoaderError.js';
2
2
 
3
3
  export default class DatasetNotFoundError extends AbstractLoaderError {
4
4
  constructor(datasetUid) {
@@ -1,4 +1,4 @@
1
- import AbstractLoaderError from './AbstractLoaderError';
1
+ import AbstractLoaderError from './AbstractLoaderError.js';
2
2
 
3
3
  export default class LoaderNotFoundError extends AbstractLoaderError {
4
4
  constructor(loaders, dataset, fileType, viewCoordinationValues) {
@@ -1,4 +1,4 @@
1
- import AbstractLoaderError from './AbstractLoaderError';
1
+ import AbstractLoaderError from './AbstractLoaderError.js';
2
2
 
3
3
  export default class LoaderValidationError extends AbstractLoaderError {
4
4
  constructor(datasetType, datasetFileType, datasetUrl, reason) {
@@ -1,6 +1,5 @@
1
- export { default as AbstractLoaderError } from './AbstractLoaderError';
2
- export { default as LoaderValidationError } from './LoaderValidationError';
3
- export { default as OptionsValidationError } from './OptionsValidationError';
4
- export { default as LoaderNotFoundError } from './LoaderNotFoundError';
5
- export { default as DatasetNotFoundError } from './DatasetNotFoundError';
6
- export { default as DataSourceFetchError } from './DataSourceFetchError';
1
+ export { default as AbstractLoaderError } from './AbstractLoaderError.js';
2
+ export { default as LoaderValidationError } from './LoaderValidationError.js';
3
+ export { default as LoaderNotFoundError } from './LoaderNotFoundError.js';
4
+ export { default as DatasetNotFoundError } from './DatasetNotFoundError.js';
5
+ export { default as DataSourceFetchError } from './DataSourceFetchError.js';
package/src/hooks.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  useRef, useState, useEffect, useCallback, useMemo,
3
3
  } from 'react';
4
- import debounce from 'lodash/debounce';
5
- import every from 'lodash/every';
4
+ import { debounce, every } from 'lodash-es';
5
+ import { extent } from 'd3-array';
6
6
  import { capitalize, fromEntries } from '@vitessce/utils';
7
- import { useGridResize, useEmitGridResize } from './state/hooks';
8
- import { VITESSCE_CONTAINER } from './classNames';
7
+ import { STATUS } from '@vitessce/constants-internal';
8
+ import { useGridResize, useEmitGridResize } from './state/hooks.js';
9
+ import { VITESSCE_CONTAINER } from './classNames.js';
9
10
 
10
11
  function getWindowDimensions() {
11
12
  const { innerWidth: width, innerHeight: height } = window;
@@ -137,34 +138,23 @@ export function useDeckCanvasSize() {
137
138
  */
138
139
  export function useReady(statusValues) {
139
140
  return useMemo(() => every(
140
- statusValues, val => val === 'success',
141
+ statusValues, val => val !== STATUS.LOADING,
141
142
  // eslint-disable-next-line react-hooks/exhaustive-deps
142
143
  ), statusValues);
143
144
  }
144
145
 
145
146
  /**
146
147
  * This hook helps manage a list of URLs.
147
- * @param {object} loaders The loaders dependency.
148
- * @param {string} dataset The dataset UID dependency.
149
- * @returns {array} An array
150
- * [urls, addUrl]
151
- * where urls is the array of URL objects,
152
- * and addUrl is a function for adding a URL to the array.
148
+ * @param {(null|object[])[]} urls Array of (null or array of { url, name }).
149
+ * @returns {array} An array of { url, name } objects (flattened from the input).
153
150
  */
154
- export function useUrls(loaders, dataset) {
155
- const [urls, setUrls] = useState([]);
156
-
157
- const addUrl = useCallback((url, name) => {
158
- if (url) {
159
- setUrls(prev => ([...prev, { url, name }]));
160
- }
161
- }, [setUrls]);
162
-
163
- useEffect(() => {
164
- setUrls([]);
165
- }, [loaders, dataset]);
166
-
167
- return [urls, addUrl];
151
+ export function useUrls(urls) {
152
+ const mergedUrls = useMemo(
153
+ () => urls.filter(a => Array.isArray(a)).flat(),
154
+ // eslint-disable-next-line react-hooks/exhaustive-deps
155
+ urls,
156
+ );
157
+ return mergedUrls;
168
158
  }
169
159
 
170
160
  /**
@@ -199,7 +189,63 @@ export function useClosestVitessceContainerSize(ref) {
199
189
  return [width, height];
200
190
  }
201
191
 
202
- export function useExpressionValueGetter({ instanceObsIndex, matrixObsIndex, expressionData }) {
192
+ /**
193
+ * Normalize an obsFeatureMatrix to a Uint8Array.
194
+ * @param {object} params
195
+ * @param {object} params.obsFeatureMatrix The obsFeatureMatrix
196
+ * returned by the useObsFeatureMatrix hook.
197
+ * @returns {array} A tuple [obsFeatureMatrix, dataExtent]
198
+ * where obsFeatureMatrix
199
+ * is a Uint8Array with the same shape as
200
+ * params.obsFeatureMatrix.data, and dataExtent is the
201
+ * [min, max] of the original data.
202
+ */
203
+ export function useUint8ObsFeatureMatrix({ obsFeatureMatrix }) {
204
+ return useMemo(() => {
205
+ if (obsFeatureMatrix && obsFeatureMatrix.data) {
206
+ const dataExtent = extent(obsFeatureMatrix.data);
207
+ const [min, max] = dataExtent;
208
+ const ratio = 255 / (max - min);
209
+ const data = new Uint8Array(
210
+ obsFeatureMatrix.data.map(i => Math.floor((i - min) * ratio)),
211
+ );
212
+ return [{ data }, dataExtent];
213
+ }
214
+ return [null, null];
215
+ }, [obsFeatureMatrix]);
216
+ }
217
+
218
+ /**
219
+ * Normalize a feature selection (data for selected
220
+ * columns of an obsFeatureMatrix) to a Uint8Array.
221
+ * @param {array|null} expressionData The expressionData
222
+ * returned by the useFeatureSelection hook,
223
+ * where each element corresponds to an
224
+ * array of values for a selected feature.
225
+ * @returns {array} A tuple [normData, extents] where
226
+ * normData is an array of Uint8Arrays (or null), and extents is
227
+ * an array of [min, max] values for each feature (or null).
228
+ */
229
+ export function useUint8FeatureSelection(expressionData) {
230
+ return useMemo(() => {
231
+ if (expressionData && expressionData[0]) {
232
+ const extents = expressionData.map(arr => extent(arr));
233
+ const normData = expressionData.map((arr, i) => {
234
+ const [min, max] = extents[i];
235
+ const ratio = 255 / (max - min);
236
+ return new Uint8Array(
237
+ arr.map(j => Math.floor((j - min) * ratio)),
238
+ );
239
+ });
240
+ return [normData, extents];
241
+ }
242
+ return [null, null];
243
+ }, [expressionData]);
244
+ }
245
+
246
+ export function useExpressionValueGetter(
247
+ { instanceObsIndex, matrixObsIndex, expressionData },
248
+ ) {
203
249
  // Get a mapping from cell ID to row index in the gene expression matrix.
204
250
  // Since the two obsIndices (instanceObsIndex = the obsIndex from obsEmbedding)
205
251
  // may be ordered differently (matrixObsIndex = the obsIndex from obsFeatureMatrix),
@@ -226,6 +272,15 @@ export function useExpressionValueGetter({ instanceObsIndex, matrixObsIndex, exp
226
272
  return getExpressionValue;
227
273
  }
228
274
 
275
+ export function useGetObsMembership(obsSetsMembership) {
276
+ return useCallback((obsId) => {
277
+ if (obsId) {
278
+ return obsSetsMembership?.get(obsId) || [];
279
+ }
280
+ return [];
281
+ }, [obsSetsMembership]);
282
+ }
283
+
229
284
  export function useGetObsInfo(obsType, obsLabelsTypes, obsLabelsData, obsSetsMembership) {
230
285
  return useCallback((obsId) => {
231
286
  if (obsId) {