Skip to content

Commit

Permalink
Merge pull request #1762 from silx-kit/data-hooks
Browse files Browse the repository at this point in the history
Export and document context access hooks
  • Loading branch information
axelboc authored Feb 28, 2025
2 parents 027df8a + 67ff592 commit e9dfcd4
Show file tree
Hide file tree
Showing 20 changed files with 264 additions and 125 deletions.
56 changes: 56 additions & 0 deletions packages/app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,3 +479,59 @@ function MyApp() {
return <pre>{JSON.stringify(group, null, 2)}</pre>;
}
```

For convenience, the following hooks are available to access data through the
data context: `useEntity`, `useDatasetValue`, `useDatasetsValues`,
`usePrefetchValues`.

We also provide a large number of type guards and assertion functions to narrow
down the kind/shape/type of HDF5 entities returned by `useEntity`, as well as a
memoised hook called `useNdArray` to create ndarrays that can be passed to the
visualization components (available in `@h5web/lib`).

```tsx
function MyApp() {
const entity = useEntity('/nD_datasets/twoD'); // ProvidedEntity
assertDataset(entity); // Dataset
assertArrayShape(entity); // Dataset<ArrayShape>
assertFloatType(entity); // Dataset<ArrayShape, FloatType>

const value = useDatasetValue(entity); // number[] | TypedArray
const dataArray = useNdArray(value, entity.shape);
const domain = useDomain(dataArray);

return (
<HeatmapVis
style={{ width: '100vw', height: '100vh' }}
dataArray={dataArray}
domain={domain}
/>
);
}
```

When accessing the values of multiple datasets with multiple consecutive calls
to `useDatasetValue` (and/or `useDatasetsValues`), invoke `usePrefetchValues`
first to ensure that the values are requested in parallel rather than
sequentially:

```tsx
const axesDatasets = [abscissasDataset, ordinatesDataset];
usePrefetchValues([valuesDataset, ...axesDatasets]);

const values = useDatasetValue(valuesDataset);
const [abscissas, ordinates] = useDatasetsValues(axesDatasets);
```

All three hooks accept a `selection` parameter to request specific slices from
n-dimensional datasets:

```tsx
const selection = '0,:,:';
usePrefetchValues([valuesDataset], selection); // prefetch the first 2D slice
usePrefetchValues([abscissasDataset, ordinatesDataset]); // pretech in full (i.e. no selection)

const values = useDatasetValue(valuesDataset, selection);
const abscissas = useDatasetValue(abscissasDataset);
const ordinates = useDatasetValue(ordinatesDataset);
```
21 changes: 21 additions & 0 deletions packages/app/src/dimension-mapper/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
type ArrayShape,
type Dataset,
type ScalarShape,
} from '@h5web/shared/hdf5-models';

import { useDataContext } from '../providers/DataProvider';
import { getSliceSelection } from '../vis-packs/core/utils';
import { type DimensionMapping } from './models';

export function useValuesInCache(
...datasets: (Dataset<ScalarShape | ArrayShape> | undefined)[]
): (dimMapping: DimensionMapping) => boolean {
const { valuesStore } = useDataContext();
return (dimMapping) => {
const selection = getSliceSelection(dimMapping);
return datasets.every(
(dataset) => !dataset || valuesStore.has({ dataset, selection }),
);
};
}
5 changes: 2 additions & 3 deletions packages/app/src/explorer/EntityList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { assertGroup } from '@h5web/shared/guards';
import { buildEntityPath } from '@h5web/shared/hdf5-utils';

import { useDataContext } from '../providers/DataProvider';
import { useEntity } from '../hooks';
import EntityItem from './EntityItem';
import styles from './Explorer.module.css';

Expand All @@ -15,8 +15,7 @@ interface Props {
function EntityList(props: Props) {
const { level, parentPath, selectedPath, onSelect } = props;

const { entitiesStore } = useDataContext();
const group = entitiesStore.get(parentPath);
const group = useEntity(parentPath);
assertGroup(group);

if (group.children.length === 0) {
Expand Down
79 changes: 79 additions & 0 deletions packages/app/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { assertDatasetValue, isDefined } from '@h5web/shared/guards';
import {
type ArrayShape,
type Dataset,
type ProvidedEntity,
type ScalarShape,
type Value,
} from '@h5web/shared/hdf5-models';

import { useDataContext } from './providers/DataProvider';

export function useEntity(path: string): ProvidedEntity {
const { entitiesStore } = useDataContext();
return entitiesStore.get(path);
}

export function usePrefetchValues(
datasets: (Dataset<ScalarShape | ArrayShape> | undefined)[],
selection?: string,
): void {
const { valuesStore } = useDataContext();
datasets.filter(isDefined).forEach((dataset) => {
valuesStore.prefetch({ dataset, selection });
});
}

export function useDatasetValue<D extends Dataset<ArrayShape | ScalarShape>>(
dataset: D,
selection?: string,
): Value<D>;

export function useDatasetValue<D extends Dataset<ArrayShape | ScalarShape>>(
dataset: D | undefined,
selection?: string,
): Value<D> | undefined;

export function useDatasetValue<D extends Dataset<ArrayShape | ScalarShape>>(
dataset: D | undefined,
selection?: string,
): Value<D> | undefined {
const { valuesStore } = useDataContext();

if (!dataset) {
return undefined;
}

// If `selection` is undefined, the entire dataset will be fetched
const value = valuesStore.get({ dataset, selection });

assertDatasetValue(value, dataset);
return value;
}

export function useDatasetsValues<D extends Dataset<ArrayShape | ScalarShape>>(
datasets: D[],
selection?: string,
): Value<D>[];

export function useDatasetsValues<D extends Dataset<ArrayShape | ScalarShape>>(
datasets: (D | undefined)[],
selection?: string,
): (Value<D> | undefined)[];

export function useDatasetsValues<D extends Dataset<ArrayShape | ScalarShape>>(
datasets: (D | undefined)[],
selection?: string,
): (Value<D> | undefined)[] {
const { valuesStore } = useDataContext();

return datasets.map((dataset) => {
if (!dataset) {
return undefined;
}

const value = valuesStore.get({ dataset, selection });
assertDatasetValue(value, dataset);
return value;
});
}
35 changes: 28 additions & 7 deletions packages/app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,23 @@ export type GetExportURL = NonNullable<DataProviderApi['getExportURL']>;
// Context
export { useDataContext } from './providers/DataProvider';
export type { DataContextValue } from './providers/DataProvider';
export type {
EntitiesStore,
ValuesStore,
ValuesStoreParams,
AttrValuesStore,
} from './providers/models';

// Undocumented (for @h5web/h5wasm)
export { default as DataProvider } from './providers/DataProvider';
export { DataProviderApi } from './providers/api';
export type { ValuesStoreParams } from './providers/models';
export { getValueOrError } from './providers/utils';
// Hooks
export {
useEntity,
useDatasetValue,
useDatasetsValues,
usePrefetchValues,
} from './hooks';
export { useBaseArray as useNdArray } from './vis-packs/core/hooks';

// Undocumented models
// Models
export { EntityKind } from '@h5web/shared/hdf5-models';

export type {
Expand Down Expand Up @@ -66,13 +75,14 @@ export type {
UnknownType,

// Value
Value,
ScalarValue,
ArrayValue,
AttributeValues,
H5WebComplex,
} from '@h5web/shared/hdf5-models';

// Undocumented guards and assertions
// Type guards and assertions
export {
isDefined,
isNonNull,
Expand Down Expand Up @@ -117,6 +127,8 @@ export {
isPrintableType,
isCompoundType,
hasStringType,
hasIntegerType,
hasFloatType,
hasNumericType,
hasBoolType,
hasEnumType,
Expand All @@ -125,11 +137,20 @@ export {
hasPrintableType,
hasCompoundType,
assertStringType,
assertIntegerType,
assertFloatType,
assertNumericType,
assertBoolType,
assertEnumType,
assertNumericLikeType,
assertComplexType,
assertPrintableType,
assertCompoundType,
assertScalarValue,
assertDatasetValue,
} from '@h5web/shared/guards';

// Undocumented (for @h5web/h5wasm)
export { default as DataProvider } from './providers/DataProvider';
export { DataProviderApi } from './providers/api';
export { getValueOrError } from './providers/utils';
6 changes: 2 additions & 4 deletions packages/app/src/metadata-viewer/MetadataViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { buildEntityPath } from '@h5web/shared/hdf5-utils';
import { memo, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

import { useDataContext } from '../providers/DataProvider';
import { useEntity } from '../hooks';
import AttrErrorFallback from './AttrErrorFallback';
import AttributesInfo from './AttributesInfo';
import AttrValueLoader from './AttrValueLoader';
Expand All @@ -21,9 +21,7 @@ interface Props {
function MetadataViewer(props: Props) {
const { path, onSelectPath } = props;

const { entitiesStore } = useDataContext();
const entity = entitiesStore.get(path);

const entity = useEntity(path);
const { kind, attributes } = entity;
const title = kind === EntityKind.Unresolved ? 'Entity' : kind;

Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/vis-packs/core/ValueFetcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from '@h5web/shared/hdf5-models';
import { type ReactNode } from 'react';

import { useDatasetValue } from './hooks';
import { useDatasetValue } from '../../hooks';

interface Props<D extends Dataset> {
dataset: D;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
} from '@h5web/shared/guards';

import DimensionMapper from '../../../dimension-mapper/DimensionMapper';
import { useValuesInCache } from '../../../dimension-mapper/hooks';
import { useDimMappingState } from '../../../dimension-mapper/store';
import { type VisContainerProps } from '../../models';
import VisBoundary from '../../VisBoundary';
import { useValuesInCache } from '../hooks';
import { useLineConfig } from '../line/config';
import { getSliceSelection } from '../utils';
import ValueFetcher from '../ValueFetcher';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import {
} from '@h5web/shared/guards';

import DimensionMapper from '../../../dimension-mapper/DimensionMapper';
import { useValuesInCache } from '../../../dimension-mapper/hooks';
import { useDimMappingState } from '../../../dimension-mapper/store';
import { type VisContainerProps } from '../../models';
import VisBoundary from '../../VisBoundary';
import { useHeatmapConfig } from '../heatmap/config';
import { useValuesInCache } from '../hooks';
import { getSliceSelection } from '../utils';
import ValueFetcher from '../ValueFetcher';
import { useComplexConfig } from './config';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
} from '@h5web/shared/guards';

import DimensionMapper from '../../../dimension-mapper/DimensionMapper';
import { useValuesInCache } from '../../../dimension-mapper/hooks';
import { useDimMappingState } from '../../../dimension-mapper/store';
import { type VisContainerProps } from '../../models';
import VisBoundary from '../../VisBoundary';
import { useValuesInCache } from '../hooks';
import { useMatrixConfig } from '../matrix/config';
import { getSliceSelection } from '../utils';
import ValueFetcher from '../ValueFetcher';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import {
} from '@h5web/shared/guards';

import DimensionMapper from '../../../dimension-mapper/DimensionMapper';
import { useValuesInCache } from '../../../dimension-mapper/hooks';
import { useDimMappingState } from '../../../dimension-mapper/store';
import { type VisContainerProps } from '../../models';
import VisBoundary from '../../VisBoundary';
import { useIgnoreFillValue, useValuesInCache } from '../hooks';
import { useIgnoreFillValue } from '../hooks';
import { getSliceSelection } from '../utils';
import ValueFetcher from '../ValueFetcher';
import { useHeatmapConfig } from './config';
Expand Down
Loading

0 comments on commit e9dfcd4

Please sign in to comment.