@vitessce/vit-s 3.5.6 → 3.5.8

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 (53) hide show
  1. package/dist/index.js +768 -111
  2. package/dist-tsc/CallbackPublisher.js +2 -1
  3. package/dist-tsc/DebugWindow.d.ts +4 -0
  4. package/dist-tsc/DebugWindow.d.ts.map +1 -0
  5. package/dist-tsc/DebugWindow.js +9 -0
  6. package/dist-tsc/FallbackForView.d.ts +2 -0
  7. package/dist-tsc/FallbackForView.d.ts.map +1 -0
  8. package/dist-tsc/FallbackForView.js +13 -0
  9. package/dist-tsc/FallbackForVitS.d.ts +2 -0
  10. package/dist-tsc/FallbackForVitS.d.ts.map +1 -0
  11. package/dist-tsc/FallbackForVitS.js +12 -0
  12. package/dist-tsc/VitS.d.ts +4 -0
  13. package/dist-tsc/VitS.d.ts.map +1 -1
  14. package/dist-tsc/VitS.js +21 -2
  15. package/dist-tsc/VitSContainer.d.ts +9 -0
  16. package/dist-tsc/VitSContainer.d.ts.map +1 -0
  17. package/dist-tsc/VitSContainer.js +15 -0
  18. package/dist-tsc/Warning.d.ts.map +1 -1
  19. package/dist-tsc/Warning.js +1 -37
  20. package/dist-tsc/data-hook-utils.d.ts.map +1 -1
  21. package/dist-tsc/data-hook-utils.js +3 -2
  22. package/dist-tsc/data-hooks.d.ts +22 -0
  23. package/dist-tsc/data-hooks.d.ts.map +1 -1
  24. package/dist-tsc/data-hooks.js +90 -0
  25. package/dist-tsc/hooks.d.ts +9 -0
  26. package/dist-tsc/hooks.d.ts.map +1 -1
  27. package/dist-tsc/hooks.js +29 -1
  28. package/dist-tsc/index.d.ts +3 -2
  29. package/dist-tsc/index.js +3 -2
  30. package/dist-tsc/shared-warning-styles.d.ts +2 -0
  31. package/dist-tsc/shared-warning-styles.d.ts.map +1 -0
  32. package/dist-tsc/shared-warning-styles.js +37 -0
  33. package/dist-tsc/view-config-utils.d.ts.map +1 -1
  34. package/dist-tsc/view-config-utils.js +7 -4
  35. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.d.ts.map +1 -1
  36. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.js +8 -6
  37. package/dist-tsc/vitessce-grid-utils.js +2 -1
  38. package/package.json +11 -9
  39. package/src/CallbackPublisher.js +2 -2
  40. package/src/DebugWindow.js +35 -0
  41. package/src/FallbackForView.js +23 -0
  42. package/src/FallbackForVitS.js +21 -0
  43. package/src/VitS.js +31 -2
  44. package/src/VitSContainer.js +19 -0
  45. package/src/Warning.js +1 -38
  46. package/src/data-hook-utils.js +3 -2
  47. package/src/data-hooks.js +122 -0
  48. package/src/hooks.js +32 -1
  49. package/src/index.js +7 -0
  50. package/src/shared-warning-styles.js +38 -0
  51. package/src/view-config-utils.js +7 -4
  52. package/src/vitessce-grid-layout/VitessceGridLayout.js +32 -26
  53. package/src/vitessce-grid-utils.js +2 -2
@@ -2,8 +2,10 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import React, { useState, useMemo, useCallback } from 'react';
3
3
  import { Responsive, WidthProvider } from 'react-grid-layout-with-lodash';
4
4
  import { isEqual } from 'lodash-es';
5
+ import { ErrorBoundary } from 'react-error-boundary';
5
6
  import { getMaxRows, resolveLayout } from './layout-utils.js';
6
7
  import { PageModeViewContext } from '../contexts.js';
8
+ import { FallbackForView } from '../FallbackForView.js';
7
9
  const ResponsiveGridLayout = WidthProvider(Responsive);
8
10
  class ResponsiveHeightGridLayout extends ResponsiveGridLayout {
9
11
  componentDidUpdate(prevProps) {
@@ -102,9 +104,9 @@ export function VitessceGridLayout(props) {
102
104
  const gridComponentsValues = Object.values(gridComponents);
103
105
  const contextValue = Object.fromEntries(gridComponentsValues.map((v) => {
104
106
  const Component = getComponent(v.component);
105
- return [v.uid, () => (_jsx("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: _jsx(Component, { ...v.props, uuid: v.uid, coordinationScopes: v.coordinationScopes, coordinationScopesBy: v.coordinationScopesBy, theme: theme, removeGridComponent: null,
106
- // Props used by LinkControllerSubscriber:
107
- viewTypes: viewTypes, fileTypes: fileTypes, coordinationTypes: coordinationTypes, stores: stores }) }, v.uid))];
107
+ return [v.uid, () => (_jsx("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: _jsx(ErrorBoundary, { FallbackComponent: FallbackForView, children: _jsx(Component, { ...v.props, uuid: v.uid, coordinationScopes: v.coordinationScopes, coordinationScopesBy: v.coordinationScopesBy, theme: theme, removeGridComponent: null,
108
+ // Props used by LinkControllerSubscriber:
109
+ viewTypes: viewTypes, fileTypes: fileTypes, coordinationTypes: coordinationTypes, stores: stores }) }) }, v.uid))];
108
110
  }));
109
111
  // Not pageMode, so no children to render.
110
112
  // We render the grid components based on the layout information.
@@ -113,9 +115,9 @@ export function VitessceGridLayout(props) {
113
115
  const removeGridComponent = () => {
114
116
  onRemoveComponent(v.uid);
115
117
  };
116
- return (_jsx("div", { children: _jsx(Component, { ...v.props, uuid: v.uid, coordinationScopes: v.coordinationScopes, coordinationScopesBy: v.coordinationScopesBy, theme: theme, removeGridComponent: removeGridComponent,
117
- // Props used by LinkControllerSubscriber:
118
- viewTypes: viewTypes, fileTypes: fileTypes, coordinationTypes: coordinationTypes, stores: stores }) }, v.uid));
118
+ return (_jsx("div", { children: _jsx(ErrorBoundary, { FallbackComponent: FallbackForView, children: _jsx(Component, { ...v.props, uuid: v.uid, coordinationScopes: v.coordinationScopes, coordinationScopesBy: v.coordinationScopesBy, theme: theme, removeGridComponent: removeGridComponent,
119
+ // Props used by LinkControllerSubscriber:
120
+ viewTypes: viewTypes, fileTypes: fileTypes, coordinationTypes: coordinationTypes, stores: stores }) }) }, v.uid));
119
121
  }), contextValue];
120
122
  }, [gridComponents, getComponent, onRemoveComponent, theme]);
121
123
  return (pageMode
@@ -3,6 +3,7 @@ import { InternMap } from 'internmap';
3
3
  import { isEqual, pick } from 'lodash-es';
4
4
  import { DATA_TYPE_COORDINATION_VALUE_USAGE } from '@vitessce/constants-internal';
5
5
  import { getSourceAndLoaderFromFileType, getDataTypeFromFileType } from '@vitessce/abstract';
6
+ import { log } from '@vitessce/globals';
6
7
  /**
7
8
  * Return the bottom coordinate of the layout.
8
9
  * https://github.com/STRML/react-grid-layout/blob/20dac73f91274526034c00968b5bedb9c2ed36b9/lib/utils.js#L82
@@ -90,7 +91,7 @@ function withDefaults(coordinationValues, dataType, fileType, datasetUid, defaul
90
91
  };
91
92
  if (!isEqual(coordinationValues, coordinationValuesWithDefaults)) {
92
93
  // eslint-disable-next-line max-len
93
- console.warn(`Using coordination value defaults for file type ${fileType} in dataset ${datasetUid}\nBefore: ${JSON.stringify(coordinationValues)}\nAfter: ${JSON.stringify(coordinationValuesWithDefaults)}`);
94
+ log.warn(`Using coordination value defaults for file type ${fileType} in dataset ${datasetUid}\nBefore: ${JSON.stringify(coordinationValues)}\nAfter: ${JSON.stringify(coordinationValuesWithDefaults)}`);
94
95
  }
95
96
  return coordinationValuesWithDefaults;
96
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/vit-s",
3
- "version": "3.5.6",
3
+ "version": "3.5.8",
4
4
  "author": "HIDIVE Lab at HMS",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -29,13 +29,15 @@
29
29
  "uuid": "^9.0.0",
30
30
  "zustand": "^3.5.10",
31
31
  "react-aria": "^3.28.0",
32
- "@vitessce/abstract": "3.5.6",
33
- "@vitessce/constants-internal": "3.5.6",
34
- "@vitessce/plugins": "3.5.6",
35
- "@vitessce/schemas": "3.5.6",
36
- "@vitessce/utils": "3.5.6",
37
- "@vitessce/sets-utils": "3.5.6",
38
- "@vitessce/config": "3.5.6"
32
+ "react-error-boundary": "^5.0.0",
33
+ "@vitessce/abstract": "3.5.8",
34
+ "@vitessce/constants-internal": "3.5.8",
35
+ "@vitessce/plugins": "3.5.8",
36
+ "@vitessce/schemas": "3.5.8",
37
+ "@vitessce/utils": "3.5.8",
38
+ "@vitessce/sets-utils": "3.5.8",
39
+ "@vitessce/config": "3.5.8",
40
+ "@vitessce/globals": "3.5.8"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@testing-library/jest-dom": "^5.16.4",
@@ -43,7 +45,7 @@
43
45
  "react": "^18.0.0",
44
46
  "vite": "^4.3.0",
45
47
  "vitest": "^0.32.2",
46
- "@vitessce/types": "3.5.6"
48
+ "@vitessce/types": "3.5.8"
47
49
  },
48
50
  "peerDependencies": {
49
51
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
@@ -1,14 +1,14 @@
1
1
  import { useEffect } from 'react';
2
+ import { log } from '@vitessce/globals';
2
3
  import { useViewConfigStoreApi, useLoaders, useWarning } from './state/hooks.js';
3
4
 
4
-
5
5
  function validateViewConfig(viewConfig, pluginSpecificConfigSchema) {
6
6
  // Need the try-catch here since Zustand will actually
7
7
  // just catch and ignore errors in its subscription callbacks.
8
8
  try {
9
9
  pluginSpecificConfigSchema.parse(viewConfig);
10
10
  } catch (e) {
11
- console.error(e);
11
+ log.error(e);
12
12
  }
13
13
  // Do nothing if successful.
14
14
  }
@@ -0,0 +1,35 @@
1
+ import clsx from 'clsx';
2
+ import { useStyles } from './shared-warning-styles.js';
3
+ import { VITESSCE_CONTAINER } from './classNames.js';
4
+
5
+ export function DebugWindow({ debugErrors }) {
6
+ const classes = useStyles();
7
+ return (
8
+ <div className={VITESSCE_CONTAINER}>
9
+ <div className={clsx(classes.warningLayout, classes.containerFluid)}>
10
+ <div className={classes.row}>
11
+ <div className={classes.warningCard}>
12
+ {debugErrors.map((error, index) => (
13
+ <div key={error.message || index}>
14
+ {index === 0 && (
15
+ <div>
16
+ <h1>Error Type: {error.name}</h1>
17
+ {Object.keys(error).map(
18
+ key => key !== 'name'
19
+ && key !== 'message' && (
20
+ <p key={key}>
21
+ {key.charAt(0).toUpperCase() + key.slice(1)}: {error[key]}
22
+ </p>
23
+ ),
24
+ )}
25
+ </div>
26
+ )}
27
+ <p>Error: {error.message}</p>
28
+ </div>
29
+ ))}
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { getDebugMode } from '@vitessce/globals';
3
+
4
+ // TODO: Improve styling - perhaps style similar to ./TitleInfo.js
5
+ // TODO: Display different info depending on the type of error
6
+
7
+ // Reference: https://github.com/bvaughn/react-error-boundary?tab=readme-ov-file#errorboundary-with-fallbackcomponent-prop
8
+ export function FallbackForView(props) {
9
+ const {
10
+ error,
11
+ // Call resetErrorBoundary() to reset the error boundary and retry the render.
12
+ // resetErrorBoundary,
13
+ } = props;
14
+
15
+ return (
16
+ <div>
17
+ <p>A view-level error occurred.</p>
18
+ {getDebugMode() ? (
19
+ <pre style={{ color: 'red' }}>{error.message}</pre>
20
+ ) : null}
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+
3
+ // TODO: Improve styling - see ./DebugWindow.js
4
+ // TODO: Display different info depending on the type of error
5
+
6
+ // Reference: https://github.com/bvaughn/react-error-boundary?tab=readme-ov-file#errorboundary-with-fallbackcomponent-prop
7
+ export function FallbackForVitS(props) {
8
+ const {
9
+ error,
10
+ // Call resetErrorBoundary() to reset the error boundary and retry the render.
11
+ // resetErrorBoundary,
12
+ } = props;
13
+
14
+
15
+ return (
16
+ <div>
17
+ <p>Error:</p>
18
+ <pre style={{ color: 'red' }}>{error.message}</pre>
19
+ </div>
20
+ );
21
+ }
package/src/VitS.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useMemo, useCallback } from 'react';
1
+ import React, { useState, useEffect, useMemo, useCallback, useLayoutEffect } from 'react';
2
2
  import {
3
3
  ThemeProvider,
4
4
  StylesProvider,
@@ -9,6 +9,10 @@ import {
9
9
  } from '@tanstack/react-query';
10
10
  import { isEqual } from 'lodash-es';
11
11
  import { buildConfigSchema, latestConfigSchema } from '@vitessce/schemas';
12
+ import {
13
+ setLogLevel, setDebugMode,
14
+ DEFAULT_LOG_LEVEL, DEFAULT_DEBUG_MODE,
15
+ } from '@vitessce/globals';
12
16
  import { muiTheme } from './shared-mui/styles.js';
13
17
  import {
14
18
  ViewConfigProvider,
@@ -19,6 +23,7 @@ import {
19
23
 
20
24
  import VitessceGrid from './VitessceGrid.js';
21
25
  import { Warning } from './Warning.js';
26
+ import { DebugWindow } from './DebugWindow.js';
22
27
  import CallbackPublisher from './CallbackPublisher.js';
23
28
  import {
24
29
  initialize,
@@ -62,6 +67,8 @@ import { AsyncFunctionsContext } from './contexts.js';
62
67
  * @param {array} props.coordinationTypes Plugin coordination types.
63
68
  * @param {null|object} props.warning A warning to render within the Vitessce grid,
64
69
  * @param {boolean} props.pageMode Whether to render in page mode. By default, false.
70
+ * @param {boolean} props.debugMode Whether to display the debugWindow. By default, false.
71
+ * @param {null|string} props.logLevel To set the log level in the console.
65
72
  * provided by the parent.
66
73
  */
67
74
  export function VitS(props) {
@@ -87,8 +94,12 @@ export function VitS(props) {
87
94
  warning,
88
95
  pageMode = false,
89
96
  children,
97
+ debugMode = DEFAULT_DEBUG_MODE,
98
+ logLevel = DEFAULT_LOG_LEVEL,
90
99
  } = props;
91
100
 
101
+ // eslint-disable-next-line no-unused-vars
102
+ const [debugErrors, setDebugErrors] = useState([]);
92
103
  const viewTypes = useMemo(() => (viewTypesProp || []), [viewTypesProp]);
93
104
  const fileTypes = useMemo(() => (fileTypesProp || []), [fileTypesProp]);
94
105
  const jointFileTypes = useMemo(
@@ -99,9 +110,15 @@ export function VitS(props) {
99
110
  () => (coordinationTypesProp || []),
100
111
  [coordinationTypesProp],
101
112
  );
102
-
103
113
  const generateClassName = useMemo(() => createGenerateClassName(uid), [uid]);
104
114
 
115
+ // Set error handling-related globals.
116
+ useLayoutEffect(() => {
117
+ setLogLevel(logLevel);
118
+ }, [logLevel]);
119
+ useLayoutEffect(() => {
120
+ setDebugMode(debugMode);
121
+ }, [debugMode]);
105
122
  const configVersion = config?.version;
106
123
 
107
124
  // If config.uid exists, then use it for hook dependencies to detect changes
@@ -230,6 +247,18 @@ export function VitS(props) {
230
247
  // eslint-disable-next-line react-hooks/exhaustive-deps
231
248
  }, [success, configKey]);
232
249
 
250
+ // TODO: use in ErrorBoundary fallback.
251
+ // Will probably need to move a lot to a child of VitS
252
+ // so that when the child throws errors the parent can catch.
253
+ if (debugMode && debugErrors.length > 0) {
254
+ return (
255
+ <StylesProvider generateClassName={generateClassName}>
256
+ <ThemeProvider theme={muiTheme[theme]}>
257
+ <DebugWindow debugErrors={debugErrors} />
258
+ </ThemeProvider>
259
+ </StylesProvider>
260
+ );
261
+ }
233
262
  return success ? (
234
263
  <StylesProvider generateClassName={generateClassName}>
235
264
  <ThemeProvider theme={muiTheme[theme]}>
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { ErrorBoundary } from 'react-error-boundary';
3
+ import { FallbackForVitS } from './FallbackForVitS.js';
4
+ import { VitS } from './VitS.js';
5
+
6
+ /**
7
+ * This serves as a way to wrap VitS in a React ErrorBoundary.
8
+ * Since there is a lot of logic within the VitS component,
9
+ * we want the ErrorBoundary to be a parent (as opposed to a child)
10
+ * of the VitS component.
11
+ * @param {*} props All props will be passed down to VitS.
12
+ */
13
+ export function VitSContainer(props) {
14
+ return (
15
+ <ErrorBoundary FallbackComponent={FallbackForVitS}>
16
+ <VitS {...props} />
17
+ </ErrorBoundary>
18
+ );
19
+ }
package/src/Warning.js CHANGED
@@ -1,44 +1,7 @@
1
1
  import clsx from 'clsx';
2
- import { makeStyles } from '@material-ui/core';
2
+ import { useStyles } from './shared-warning-styles.js';
3
3
  import { VITESSCE_CONTAINER } from './classNames.js';
4
4
 
5
- const useStyles = makeStyles(theme => ({
6
- warningLayout: {
7
- backgroundColor: theme.palette.gridLayoutBackground,
8
- position: 'absolute',
9
- width: '100%',
10
- height: '100vh',
11
- },
12
- containerFluid: {
13
- width: '100%',
14
- padding: '15px',
15
- marginRight: 'auto',
16
- marginLeft: 'auto',
17
- boxSizing: 'border-box',
18
- display: 'flex',
19
- },
20
- row: {
21
- flexGrow: '1',
22
- },
23
- warningCard: {
24
- border: `1px solid ${theme.palette.cardBorder}`,
25
- flex: '1 1 auto',
26
- minHeight: '1px',
27
- padding: '12px',
28
- marginTop: '8px',
29
- marginBottom: '8px',
30
- position: 'relative',
31
- display: 'flex',
32
- flexDirection: 'column',
33
- minWidth: '0',
34
- wordWrap: 'break-word',
35
- backgroundClip: 'border-box',
36
- borderRadius: '4px',
37
- backgroundColor: theme.palette.primaryBackground,
38
- color: theme.palette.primaryForeground,
39
- },
40
- }));
41
-
42
5
  export function Warning(props) {
43
6
  const {
44
7
  title,
@@ -4,6 +4,7 @@ import {
4
4
  capitalize,
5
5
  getInitialCoordinationScopePrefix,
6
6
  } from '@vitessce/utils';
7
+ import { log } from '@vitessce/globals';
7
8
  import { STATUS } from '@vitessce/constants-internal';
8
9
  import {
9
10
  AbstractLoaderError,
@@ -22,8 +23,8 @@ import {
22
23
  */
23
24
  export function warn(error, setWarning) {
24
25
  setWarning(error.message);
25
- console.warn(error.message);
26
- console.error(error.stack);
26
+ log.warn(error.message);
27
+ log.error(error.stack);
27
28
  if (error instanceof AbstractLoaderError) {
28
29
  error.warnInConsole();
29
30
  }
package/src/data-hooks.js CHANGED
@@ -219,6 +219,17 @@ export function useNeighborhoodsData(
219
219
  );
220
220
  }
221
221
 
222
+ export function useComparisonMetadata(
223
+ loaders, dataset, isRequired,
224
+ coordinationSetters, initialCoordinationValues, matchOn,
225
+ ) {
226
+ return useDataType(
227
+ DataType.COMPARISON_METADATA,
228
+ loaders, dataset, isRequired,
229
+ coordinationSetters, initialCoordinationValues, matchOn,
230
+ );
231
+ }
232
+
222
233
  /**
223
234
  * Get data from the expression matrix data type loader for a given gene selection.
224
235
  * Throw warnings if the data is marked as required.
@@ -313,6 +324,117 @@ export function useFeatureSelection(
313
324
  return [geneData, loadedGeneName, dataStatus];
314
325
  }
315
326
 
327
+ /**
328
+ * Utility hook function which can be used to define
329
+ * dataType-specific data hooks which call a loader.loadMulti
330
+ * method to obtain multiple data frames resulting from
331
+ * multiple comparative analyses.
332
+ * @param {object} loaders The object mapping
333
+ * datasets and data types to loader instances.
334
+ * @param {string} dataset The key for a dataset,
335
+ * used to identify which loader to use.
336
+ * @param {boolean} isRequired Should a warning be thrown if
337
+ * loading is unsuccessful?
338
+ * @param {object} matchOn Coordination values used to obtain a matching loader.
339
+ * @param {object} volcanoOptions Options to pass to the loadMulti function of the loader.
340
+ * @param {string} dataType A data type.
341
+ * @returns {array} [{ df, metadata }] Array of dataframes and their metadata, where
342
+ * df is an object in which each column is stored as an array like { col1: arr1, col2: arr2 }.
343
+ */
344
+ export function useComparativeDataType(
345
+ loaders, dataset, isRequired, matchOn, volcanoOptions, dataType,
346
+ ) {
347
+ const setWarning = useSetWarning();
348
+ const placeholderObject = useMemo(() => ({}), []);
349
+ const statsQuery = useQuery({
350
+ // TODO: only enable once `loaders` is available?
351
+ structuralSharing: false,
352
+ placeholderData: placeholderObject,
353
+ // Include the hook name in the queryKey to prevent the case in which an identical queryKey
354
+ // in a different hook would cause an accidental cache hit.
355
+ queryKey: [dataset, dataType, matchOn, volcanoOptions, 'useComparativeData'],
356
+ // TODO: use TypeScript to type the return value?
357
+ queryFn: async (ctx) => {
358
+ const loader = getMatchingLoader(
359
+ ctx.meta.loaders, ctx.queryKey[0], ctx.queryKey[1], ctx.queryKey[2],
360
+ );
361
+ if (loader) {
362
+ // FeatureStats loader has a special loadMulti function
363
+ // that depends on coordination values such as sampleSetSelection.
364
+ const implementsLoadAttrs = typeof loader.loadMulti === 'function';
365
+ if (implementsLoadAttrs) {
366
+ // Has loadMulti function.
367
+ const payload = await loader.loadMulti(volcanoOptions);
368
+ if (!payload) return placeholderObject;
369
+ const { data: payloadData } = payload;
370
+
371
+ return {
372
+ data: payloadData,
373
+ urls: null,
374
+ };
375
+ }
376
+ // No loadAttrs function.
377
+ const payload = await loader.load();
378
+ if (!payload) return placeholderObject;
379
+ const { data: payloadData } = payload;
380
+ return {
381
+ data: payloadData,
382
+ urls: null,
383
+ };
384
+ }
385
+ // No loader was found.
386
+ if (isRequired) {
387
+ // Status: error
388
+ throw new LoaderNotFoundError(loaders, dataset, dataType, matchOn);
389
+ } else {
390
+ // Status: success
391
+ return { data: placeholderObject, dataKey: null };
392
+ }
393
+ },
394
+ meta: { loaders },
395
+ });
396
+ const { data, status, isFetching, error } = statsQuery;
397
+ const loadedData = data?.data || placeholderObject;
398
+
399
+ const dataStatus = isFetching ? STATUS.LOADING : status;
400
+ const urls = data?.urls;
401
+
402
+ useEffect(() => {
403
+ if (error) {
404
+ setWarning(error.message);
405
+ }
406
+ }, [error, setWarning]);
407
+
408
+ return [loadedData, dataStatus, urls];
409
+ }
410
+
411
+ export function useFeatureStatsData(
412
+ loaders, dataset, isRequired, matchOn, volcanoOptions,
413
+ ) {
414
+ return useComparativeDataType(
415
+ loaders, dataset, isRequired, matchOn, volcanoOptions,
416
+ DataType.FEATURE_STATS,
417
+ );
418
+ }
419
+
420
+ export function useFeatureSetStatsData(
421
+ loaders, dataset, isRequired, matchOn, volcanoOptions,
422
+ ) {
423
+ return useComparativeDataType(
424
+ loaders, dataset, isRequired, matchOn, volcanoOptions,
425
+ DataType.FEATURE_SET_STATS,
426
+ );
427
+ }
428
+
429
+ export function useObsSetStatsData(
430
+ loaders, dataset, isRequired, matchOn, volcanoOptions,
431
+ ) {
432
+ return useComparativeDataType(
433
+ loaders, dataset, isRequired, matchOn, volcanoOptions,
434
+ DataType.OBS_SET_STATS,
435
+ );
436
+ }
437
+
316
438
  /**
317
439
  * Get the attributes for the expression matrix data type loader,
318
440
  * i.e names of cells and genes.
package/src/hooks.js CHANGED
@@ -6,8 +6,8 @@ import { extent } from 'd3-array';
6
6
  import { useQuery } from '@tanstack/react-query';
7
7
  import { capitalize } from '@vitessce/utils';
8
8
  import { STATUS, AsyncFunctionType } from '@vitessce/constants-internal';
9
- import { useGridResize, useEmitGridResize } from './state/hooks.js';
10
9
  import { VITESSCE_CONTAINER } from './classNames.js';
10
+ import { useGridResize, useEmitGridResize } from './state/hooks.js';
11
11
  import { useAsyncFunction } from './contexts.js';
12
12
 
13
13
 
@@ -370,3 +370,34 @@ export function useExpandedFeatureLabelsMap(featureType, featureLabelsMap, optio
370
370
  );
371
371
  return [updatedFeatureLabelsMap, dataStatus];
372
372
  }
373
+
374
+ /**
375
+ * Using a loader object's options,
376
+ * return a mapping from group name to column name.
377
+ * @param {*} loader obsSets or sampleSets loader class
378
+ * @param {[boolean]} reverse Optionally, reverse
379
+ * (so, return column name to group name mapping).
380
+ * @returns {Record<string, string>} Mapping from group name to column name.
381
+ */
382
+ export function useColumnNameMapping(loader, reverse = false) {
383
+ return useMemo(() => {
384
+ const result = {};
385
+ if (loader?.options) {
386
+ const optionsArray = loader.options.obsSets
387
+ ? loader.options.obsSets
388
+ : loader.options.sampleSets;
389
+ optionsArray.forEach((optionObject) => {
390
+ const { name, path } = optionObject;
391
+ const columnName = path.split('/').at(-1);
392
+ result[name] = columnName;
393
+ });
394
+ }
395
+ if (reverse) {
396
+ return Object.fromEntries(
397
+ Object.entries(result)
398
+ .map(([k, v]) => ([v, k])),
399
+ );
400
+ }
401
+ return result;
402
+ }, [loader]);
403
+ }
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { VitS } from './VitS.js';
2
+ export { VitSContainer } from './VitSContainer.js';
2
3
  export { TitleInfo } from './TitleInfo.js';
3
4
  export { PopperMenu } from './shared-mui/components.js';
4
5
  // For plugin view types:
@@ -16,6 +17,7 @@ export {
16
17
  useWindowDimensions,
17
18
  useGridItemSize,
18
19
  useExpandedFeatureLabelsMap,
20
+ useColumnNameMapping,
19
21
  } from './hooks.js';
20
22
  export {
21
23
  useCoordinationScopes,
@@ -76,6 +78,11 @@ export {
76
78
  useObsFeatureMatrixData,
77
79
  useFeatureLabelsData,
78
80
  useGenomicProfilesData,
81
+
82
+ useComparisonMetadata,
83
+ useFeatureStatsData,
84
+ useFeatureSetStatsData,
85
+ useObsSetStatsData,
79
86
  } from './data-hooks.js';
80
87
  export {
81
88
  usePointMultiObsLabels,
@@ -0,0 +1,38 @@
1
+ import { makeStyles } from '@material-ui/core';
2
+
3
+ export const useStyles = makeStyles(theme => ({
4
+ warningLayout: {
5
+ backgroundColor: theme.palette.gridLayoutBackground,
6
+ position: 'absolute',
7
+ width: '100%',
8
+ height: '100vh',
9
+ },
10
+ containerFluid: {
11
+ width: '100%',
12
+ padding: '15px',
13
+ marginRight: 'auto',
14
+ marginLeft: 'auto',
15
+ boxSizing: 'border-box',
16
+ display: 'flex',
17
+ },
18
+ row: {
19
+ flexGrow: '1',
20
+ },
21
+ warningCard: {
22
+ border: `1px solid ${theme.palette.cardBorder}`,
23
+ flex: '1 1 auto',
24
+ minHeight: '1px',
25
+ padding: '12px',
26
+ marginTop: '8px',
27
+ marginBottom: '8px',
28
+ position: 'relative',
29
+ display: 'flex',
30
+ flexDirection: 'column',
31
+ minWidth: '0',
32
+ wordWrap: 'break-word',
33
+ backgroundClip: 'border-box',
34
+ borderRadius: '4px',
35
+ backgroundColor: theme.palette.primaryBackground,
36
+ color: theme.palette.primaryForeground,
37
+ },
38
+ }));
@@ -2,16 +2,19 @@
2
2
  /* eslint-disable camelcase */
3
3
  import { cloneDeep } from 'lodash-es';
4
4
  import { getNextScope } from '@vitessce/utils';
5
+ import { atLeastLogLevel, LogLevel } from '@vitessce/globals';
5
6
  import {
6
7
  AUTO_INDEPENDENT_COORDINATION_TYPES,
7
8
  META_VERSION,
8
9
  } from '@vitessce/constants-internal';
9
10
 
10
11
  export function logConfig(config, name) {
11
- console.groupCollapsed(`🚄 VitS (${META_VERSION.version}) ${name}`);
12
- console.info(`data:,${JSON.stringify(config)}`);
13
- console.info(JSON.stringify(config, null, 2));
14
- console.groupEnd();
12
+ if (atLeastLogLevel(LogLevel.INFO)) {
13
+ console.groupCollapsed(`🚄 VitS (${META_VERSION.version}) ${name}`);
14
+ console.info(`data:,${JSON.stringify(config)}`);
15
+ console.info(JSON.stringify(config, null, 2));
16
+ console.groupEnd();
17
+ }
15
18
  }
16
19
 
17
20
  /**