Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/demo/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createRoot } from 'react-dom/client';

import DemoApp from './DemoApp';

enableBigIntSerialization(); // for `RawVis` and `MetadataViewer`
enableBigIntSerialization(); // for `ScalarVis` and `MetadataViewer`

const rootElem = document.querySelector('#root');
assertNonNull(rootElem);
Expand Down
4 changes: 2 additions & 2 deletions packages/app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ import { getFeedbackMailto } from '@h5web/app';

#### `enableBigIntSerialization: () => void`

Invoke this function before rendering your application to allow the _Raw_
Invoke this function before rendering your application to allow the _Scalar_
visualization and metadata inspector to serialize and display
[big integers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#bigint_type):

Expand All @@ -465,7 +465,7 @@ This is recommended if you work with a provider that supports 64-bit integers
i.e. one that may provide dataset and attribute values that include primitive
`bigint` numbers — currently only [`MockProvider`](#mockprovider).

The _Raw_ visualization and metadata inspector rely on `JSON.stringify()` to
The _Scalar_ visualization and metadata inspector rely on `JSON.stringify()` to
render dataset and attribute values. By default, `JSON.stringify()` does not
know how to serialize `bigint` numbers and throws an error if it encounters one.
`enableBigIntSerialization()` teaches `JSON.stringify()` to convert big integers
Expand Down
20 changes: 10 additions & 10 deletions packages/app/src/__tests__/CorePack.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { Vis } from '../vis-packs/core/visualizations';
test('visualize raw dataset', async () => {
const { selectExplorerNode } = await renderApp('/entities/raw');

expect(getVisTabs()).toEqual([Vis.Raw]);
expect(getSelectedVisTab()).toBe(Vis.Raw);
expect(getVisTabs()).toEqual([Vis.Scalar]);
expect(getSelectedVisTab()).toBe(Vis.Scalar);
expect(page.getByText('"int": 42')).toBeVisible();

await selectExplorerNode('raw_large');
Expand All @@ -18,8 +18,8 @@ test('visualize raw dataset', async () => {
test('visualize raw image dataset', async () => {
await renderApp('/entities/raw_png');

expect(getVisTabs()).toEqual([Vis.Raw]);
expect(getSelectedVisTab()).toBe(Vis.Raw);
expect(getVisTabs()).toEqual([Vis.Scalar]);
expect(getSelectedVisTab()).toBe(Vis.Scalar);
expect(page.getByAltText('raw_png')).toBeVisible();
});

Expand Down Expand Up @@ -99,16 +99,16 @@ test('visualize 1D compound dataset', async () => {
test('visualize 1D mixed compound dataset', async () => {
await renderApp('/nD_datasets/oneD_compound_mixed');

expect(getVisTabs()).toEqual([Vis.Raw]);
expect(getSelectedVisTab()).toBe(Vis.Raw);
expect(getVisTabs()).toEqual([Vis.Scalar]);
expect(getSelectedVisTab()).toBe(Vis.Scalar);
expect(page.getByText('"int": 42')).toBeVisible();
});

test('visualize 1D opaque dataset', async () => {
await renderApp('/nD_datasets/oneD_opaque');

expect(getVisTabs()).toEqual([Vis.Raw]);
expect(getSelectedVisTab()).toBe(Vis.Raw);
expect(getVisTabs()).toEqual([Vis.Scalar]);
expect(getSelectedVisTab()).toBe(Vis.Scalar);
expect(page.getByText('"foo"')).toBeVisible();
});

Expand Down Expand Up @@ -183,8 +183,8 @@ test('visualize 2D complex dataset', async () => {
test('visualize 2D opaque dataset', async () => {
await renderApp('/nD_datasets/twoD_opaque');

expect(getVisTabs()).toEqual([Vis.Raw]);
expect(getSelectedVisTab()).toBe(Vis.Raw);
expect(getVisTabs()).toEqual([Vis.Scalar]);
expect(getSelectedVisTab()).toBe(Vis.Scalar);
expect(page.getByText('Uint8Array [ 0,1 ]')).toBeVisible();
});

Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/vis-packs/core/configs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { RawConfigProvider } from './raw/config';
export { ScalarConfigProvider } from './scalar/config';
export { MatrixConfigProvider } from './matrix/config';
export { LineConfigProvider } from './line/config';
export { HeatmapConfigProvider } from './heatmap/config';
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/vis-packs/core/containers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { default as RawVisContainer } from './raw/RawVisContainer';
export { default as ScalarVisContainer } from './scalar/ScalarVisContainer';
export { default as MatrixVisContainer } from './matrix/MatrixVisContainer';
export { default as LineVisContainer } from './line/LineVisContainer';
Expand Down
57 changes: 0 additions & 57 deletions packages/app/src/vis-packs/core/raw/RawVisContainer.tsx

This file was deleted.

10 changes: 0 additions & 10 deletions packages/app/src/vis-packs/core/raw/utils.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RawImageVis, RawVis } from '@h5web/lib';
import { BinaryImageVis, ScalarVis } from '@h5web/lib';
import {
type ArrayShape,
type Dataset,
Expand All @@ -8,21 +8,22 @@ import { createPortal } from 'react-dom';

import visualizerStyles from '../../../visualizer/Visualizer.module.css';
import { useExportEntries } from '../hooks';
import { type RawConfig } from './config';
import RawToolbar from './RawToolbar';
import { isBinaryImage } from './utils';
import { type ScalarConfig } from './config';
import ScalarToolbar from './ScalarToolbar';
import { getFormatter, isBinaryImage } from './utils';

interface Props {
dataset: Dataset<ScalarShape | ArrayShape>;
value: unknown;
toolbarContainer?: HTMLDivElement | undefined;
config: RawConfig;
config: ScalarConfig;
}

function MappedRawVis(props: Props) {
function MappedScalarVis(props: Props) {
const { dataset, value, toolbarContainer, config } = props;

const isImage = value instanceof Uint8Array && isBinaryImage(value);
const formatter = getFormatter(dataset.type);

const exportEntries = useExportEntries(['json'], dataset, undefined, {
json: () => JSON.stringify(value, null, 2),
Expand All @@ -32,7 +33,7 @@ function MappedRawVis(props: Props) {
<>
{toolbarContainer &&
createPortal(
<RawToolbar
<ScalarToolbar
isImage={isImage}
config={config}
exportEntries={exportEntries}
Expand All @@ -41,17 +42,17 @@ function MappedRawVis(props: Props) {
)}

{isImage ? (
<RawImageVis
<BinaryImageVis
Copy link
Contributor Author

@axelboc axelboc Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed this component, since the term "Raw" no longer makes much sense.

className={visualizerStyles.vis}
value={value}
title={dataset.name}
fit={config.fitImage}
/>
) : (
<RawVis className={visualizerStyles.vis} value={value} />
<ScalarVis className={visualizerStyles.vis} value={formatter(value)} />
)}
</>
);
}

export default MappedRawVis;
export default MappedScalarVis;
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { ExportMenu, Separator, ToggleBtn, Toolbar } from '@h5web/lib';
import { type ExportEntry } from '@h5web/shared/vis-models';
import { MdOutlineFitScreen } from 'react-icons/md';

import { type RawConfig } from './config';
import { type ScalarConfig } from './config';

interface Props {
isImage: boolean;
config: RawConfig;
config: ScalarConfig;
exportEntries: ExportEntry[];
}

function RawToolbar(props: Props) {
function ScalarToolbar(props: Props) {
const { isImage, config, exportEntries } = props;
const { fitImage, setFitImage } = config;

Expand All @@ -34,4 +34,4 @@ function RawToolbar(props: Props) {
);
}

export default RawToolbar;
export default ScalarToolbar;
66 changes: 43 additions & 23 deletions packages/app/src/vis-packs/core/scalar/ScalarVisContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,56 @@
import { ScalarVis } from '@h5web/lib';
import {
assertDataset,
assertPrintableType,
assertScalarShape,
} from '@h5web/shared/guards';
import { DimensionMapper, getSliceSelection } from '@h5web/lib';
import { assertDataset, assertNonNullShape } from '@h5web/shared/guards';

import { useDimMappingState } from '../../../dim-mapping-store';
import { useValuesInCache } from '../../../hooks';
import visualizerStyles from '../../../visualizer/Visualizer.module.css';
import { type VisContainerProps } from '../../models';
import VisBoundary from '../../VisBoundary';
import ValueFetcher from '../ValueFetcher';
import { getFormatter } from './utils';
import { useScalarConfig } from './config';
import MappedScalarVis from './MappedScalarVis';
import ScalarFetcher from './ScalarFetcher';

function ScalarVisContainer(props: VisContainerProps) {
const { entity } = props;
const { entity, toolbarContainer } = props;
assertDataset(entity);
assertScalarShape(entity);
assertPrintableType(entity);
Comment on lines -17 to -18
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScalarVisContainer now accepts anything with a non-null shape.

assertNonNullShape(entity);

const formatter = getFormatter(entity);
const { dims } = entity.shape;
const [dimMapping, setDimMapping] = useDimMappingState({
dims,
axesCount: 0, // slicing only
});

const config = useScalarConfig();
const selection = getSliceSelection(dimMapping);
const canSliceFast = useValuesInCache(entity);

return (
<VisBoundary>
<ValueFetcher
dataset={entity}
render={(value) => (
<ScalarVis
className={visualizerStyles.vis}
value={formatter(value)}
/>
)}
/>
</VisBoundary>
<>
{dims.length > 0 && (
<DimensionMapper
className={visualizerStyles.dimMapper}
dims={dims}
dimMapping={dimMapping}
canSliceFast={canSliceFast}
onChange={setDimMapping}
/>
)}
<VisBoundary resetKey={dimMapping} isSlice={dims.length > 0}>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code comes from RawVisContainer with one subtle fix here caught by our Scalar vis tests: isSlice is now set dynamically to ensure that the correct loading message is shown ("Loading data" instead of "Loading slice" when fetching the value of a scalar dataset)

<ScalarFetcher
dataset={entity}
selection={selection}
render={(value) => (
<MappedScalarVis
dataset={entity}
value={value}
toolbarContainer={toolbarContainer}
config={config}
/>
)}
/>
</VisBoundary>
</>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,38 @@ import {
import { createStore, type StoreApi, useStore } from 'zustand';
import { persist } from 'zustand/middleware';

export interface RawConfig {
export interface ScalarConfig {
fitImage: boolean;
setFitImage: (fitImage: boolean) => void;
}

function createRawConfigStore() {
return createStore<RawConfig>()(
function createScalarConfigStore() {
return createStore<ScalarConfig>()(
persist(
(set): RawConfig => ({
(set): ScalarConfig => ({
fitImage: true,
setFitImage: (fitImage) => set({ fitImage }),
}),
{
name: 'h5web:raw',
name: 'h5web:scalar',
version: 1,
},
),
);
}

const StoreContext = createContext({} as StoreApi<RawConfig>);
const StoreContext = createContext({} as StoreApi<ScalarConfig>);

export function RawConfigProvider(props: PropsWithChildren<NoProps>) {
export function ScalarConfigProvider(props: PropsWithChildren<NoProps>) {
const { children } = props;

const [store] = useState(createRawConfigStore);
const [store] = useState(createScalarConfigStore);

return (
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
);
}

export function useRawConfig(): RawConfig {
export function useScalarConfig(): ScalarConfig {
return useStore(useContext(StoreContext));
}
Loading
Loading