Skip to content

Commit 5664338

Browse files
committed
Add Array vis for non-printable array datasets
1 parent 8ccffe8 commit 5664338

File tree

10 files changed

+211
-31
lines changed

10 files changed

+211
-31
lines changed

packages/app/src/dimension-mapper/DimensionMapper.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
.dimSize {
4545
flex: 1 0;
46+
min-width: 2.25em; /* in case there are no axes */
4647
padding: 0 0.1875rem;
4748
text-align: center;
4849
}

packages/app/src/dimension-mapper/store.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ interface DimMappingState {
2323
) => void;
2424
}
2525

26-
function createLineConfigStore() {
26+
function createDimMappingStore() {
2727
return createStore<DimMappingState>((set) => ({
2828
dims: [],
2929
axesCount: 0,
@@ -41,7 +41,7 @@ interface Props {}
4141
export function DimMappingProvider(props: PropsWithChildren<Props>) {
4242
const { children } = props;
4343

44-
const [store] = useState(createLineConfigStore);
44+
const [store] = useState(createDimMappingStore);
4545

4646
return (
4747
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
@@ -62,7 +62,7 @@ export function useDimMappingState(
6262
const mapping = isStale
6363
? [
6464
...Array.from({ length: dims.length - axesCount }, () => 0),
65-
...(dims.length > 0
65+
...(dims.length > 0 && axesCount > 0
6666
? ['y' as const, 'x' as const].slice(-axesCount)
6767
: []),
6868
]

packages/app/src/hooks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function useDatasetValue<D extends Dataset<ArrayShape | ScalarShape>>(
4747
// If `selection` is undefined, the entire dataset will be fetched
4848
const value = valuesStore.get({ dataset, selection });
4949

50-
assertDatasetValue(value, dataset);
50+
assertDatasetValue(value, dataset, selection);
5151
return value;
5252
}
5353

@@ -73,7 +73,7 @@ export function useDatasetsValues<D extends Dataset<ArrayShape | ScalarShape>>(
7373
}
7474

7575
const value = valuesStore.get({ dataset, selection });
76-
assertDatasetValue(value, dataset);
76+
assertDatasetValue(value, dataset, selection);
7777
return value;
7878
});
7979
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { assertArrayShape, assertDataset } from '@h5web/shared/guards';
2+
3+
import DimensionMapper from '../../../dimension-mapper/DimensionMapper';
4+
import { useValuesInCache } from '../../../dimension-mapper/hooks';
5+
import { useDimMappingState } from '../../../dimension-mapper/store';
6+
import { type VisContainerProps } from '../../models';
7+
import VisBoundary from '../../VisBoundary';
8+
import { useRawConfig } from '../raw/config';
9+
import MappedRawVis from '../raw/MappedRawVis';
10+
import { getSliceSelection } from '../utils';
11+
import ValueFetcher from '../ValueFetcher';
12+
13+
function ArrayVisContainer(props: VisContainerProps) {
14+
const { entity, toolbarContainer } = props;
15+
assertDataset(entity);
16+
assertArrayShape(entity);
17+
18+
const { shape: dims } = entity;
19+
const [dimMapping, setDimMapping] = useDimMappingState(dims, 0); // no axes, slicing only
20+
21+
const config = useRawConfig();
22+
const selection = getSliceSelection(dimMapping);
23+
24+
return (
25+
<>
26+
<DimensionMapper
27+
dims={dims}
28+
dimMapping={dimMapping}
29+
isCached={useValuesInCache(entity)}
30+
onChange={setDimMapping}
31+
/>
32+
<VisBoundary resetKey={dimMapping} isSlice={selection !== undefined}>
33+
<ValueFetcher
34+
dataset={entity}
35+
selection={selection}
36+
render={(value) => (
37+
<MappedRawVis
38+
dataset={entity}
39+
value={value}
40+
toolbarContainer={toolbarContainer}
41+
config={config}
42+
/>
43+
)}
44+
/>
45+
</VisBoundary>
46+
</>
47+
);
48+
}
49+
50+
export default ArrayVisContainer;

packages/app/src/vis-packs/core/containers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { default as RawVisContainer } from './raw/RawVisContainer';
22
export { default as ScalarVisContainer } from './scalar/ScalarVisContainer';
3+
export { default as ArrayVisContainer } from './array/ArrayVisContainer';
34
export { default as MatrixVisContainer } from './matrix/MatrixVisContainer';
45
export { default as LineVisContainer } from './line/LineVisContainer';
56
export { default as HeatmapVisContainer } from './heatmap/HeatmapVisContainer';

packages/app/src/vis-packs/core/visualizations.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
FiMap,
2121
FiPackage,
2222
} from 'react-icons/fi';
23+
import { MdDataArray } from 'react-icons/md';
2324

2425
import { type AttrValuesStore } from '../../providers/models';
2526
import { type VisDef } from '../models';
@@ -33,6 +34,7 @@ import {
3334
RgbConfigProvider,
3435
} from './configs';
3536
import {
37+
ArrayVisContainer,
3638
ComplexLineVisContainer,
3739
ComplexVisContainer,
3840
CompoundVisContainer,
@@ -49,6 +51,7 @@ import SurfaceVisContainer from './surface/SurfaceVisContainer';
4951
export enum Vis {
5052
Raw = 'Raw',
5153
Scalar = 'Scalar',
54+
Array = 'Array',
5255
Matrix = 'Matrix',
5356
Line = 'Line',
5457
Heatmap = 'Heatmap',
@@ -72,7 +75,7 @@ export const CORE_VIS = {
7275
Icon: FiCpu,
7376
Container: RawVisContainer,
7477
ConfigProvider: RawConfigProvider,
75-
supportsDataset: hasNonNullShape,
78+
supportsDataset: hasScalarShape,
7679
},
7780

7881
[Vis.Scalar]: {
@@ -84,6 +87,14 @@ export const CORE_VIS = {
8487
},
8588
},
8689

90+
[Vis.Array]: {
91+
name: Vis.Array,
92+
Icon: MdDataArray,
93+
Container: ArrayVisContainer,
94+
ConfigProvider: RawConfigProvider,
95+
supportsDataset: hasArrayShape,
96+
},
97+
8798
[Vis.Matrix]: {
8899
name: Vis.Matrix,
89100
Icon: FiGrid,

packages/app/src/visualizer/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ function getNxDefaultPath(
8282
return getImplicitDefaultChild(entity.children, attrValueStore)?.path;
8383
}
8484

85+
const FALLBACK_VIS = new Set([Vis.Raw, Vis.Array]);
86+
8587
function getSupportedCoreVis(
8688
entity: ProvidedEntity,
8789
attrValueStore: AttrValuesStore,
@@ -91,7 +93,7 @@ function getSupportedCoreVis(
9193
);
9294

9395
return supportedVis.length > 1
94-
? supportedVis.filter((vis) => vis.name !== Vis.Raw)
96+
? supportedVis.filter((vis) => !FALLBACK_VIS.has(vis.name))
9597
: supportedVis;
9698
}
9799

packages/shared/src/guards.test.ts

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -133,27 +133,100 @@ describe('assertDatasetValue', () => {
133133
dataset('foo', intType(true, 64), [2]),
134134
),
135135
).not.toThrow();
136+
137+
expect(() =>
138+
assertDatasetValue(
139+
Float32Array.from([0, 1]), // big ints can be returned as any kind of numbers
140+
dataset('foo', intType(true, 64), [2]),
141+
),
142+
).not.toThrow();
136143
});
137144

138-
describe('assertDatasetValue', () => {
139-
it("should throw when value doesn't satisfy dataset type and shape", () => {
140-
expect(() =>
141-
assertDatasetValue(
142-
true,
143-
dataset('foo', enumType(intType(), { FOO: 0 }), []),
144-
),
145-
).toThrow('Expected number');
146-
147-
expect(() =>
148-
assertDatasetValue(['foo', 'bar'], dataset('foo', intType(), [2])),
149-
).toThrow('Expected number');
150-
151-
expect(() =>
152-
assertDatasetValue(
153-
BigInt64Array.from([0n, 1n]),
154-
dataset('foo', intType(), [2]),
155-
),
156-
).toThrow('Expected number');
157-
});
145+
it("should throw when value doesn't satisfy dataset type and shape", () => {
146+
expect(() =>
147+
assertDatasetValue(
148+
true,
149+
dataset('foo', enumType(intType(), { FOO: 0 }), []),
150+
),
151+
).toThrow('Expected number');
152+
153+
expect(() =>
154+
assertDatasetValue(['foo', 'bar'], dataset('foo', intType(), [2])),
155+
).toThrow('Expected number');
156+
157+
expect(() =>
158+
assertDatasetValue(
159+
BigInt64Array.from([0n, 1n]),
160+
dataset('foo', intType(), [2]),
161+
),
162+
).toThrow('Expected number');
163+
});
164+
165+
it('should not throw when value shape satisfies selection', () => {
166+
expect(() =>
167+
assertDatasetValue(
168+
0, // scalar => OK
169+
dataset('foo', intType(), [1]), // 1D dataset
170+
'0', // scalar selection (only in "Array" vis)
171+
),
172+
).not.toThrow();
173+
174+
expect(() =>
175+
assertDatasetValue(
176+
[0], // array => OK
177+
dataset('foo', intType(), [1]), // 1D dataset
178+
':', // entire array
179+
),
180+
).not.toThrow();
181+
182+
expect(() =>
183+
assertDatasetValue(
184+
0, // scalar => OK
185+
dataset('foo', intType(), [1, 1]), // 2D dataset
186+
'0,0', // scalar selection (only in "Array" vis)
187+
),
188+
).not.toThrow();
189+
190+
expect(() =>
191+
assertDatasetValue(
192+
[0], // array => OK
193+
dataset('foo', intType(), [1, 1]), // 2D dataset
194+
'0,:', // 1D slice selection
195+
),
196+
).not.toThrow();
197+
});
198+
199+
it("should throw when value shape doesn't satisfy selection", () => {
200+
expect(() =>
201+
assertDatasetValue(
202+
[0], // array => NOT OK
203+
dataset('foo', intType(), [1]), // 1D dataset
204+
'0', // scalar selection (only in "Array" vis)
205+
),
206+
).toThrow('Expected number');
207+
208+
expect(() =>
209+
assertDatasetValue(
210+
0, // scalar => NOT OK
211+
dataset('foo', intType(), [1]), // 1D dataset
212+
':', // entire array
213+
),
214+
).toThrow('Expected array or typed array');
215+
216+
expect(() =>
217+
assertDatasetValue(
218+
[0], // array => NOT OK
219+
dataset('foo', intType(), [1, 1]), // 2D dataset
220+
'0,0', // scalar selection (only in "Array" vis)
221+
),
222+
).toThrow('Expected number');
223+
224+
expect(() =>
225+
assertDatasetValue(
226+
0, // scalar => NOT OK
227+
dataset('foo', intType(), [1, 1]), // 2D dataset
228+
'0,:', // 1D slice
229+
),
230+
).toThrow('Expected array');
158231
});
159232
});

packages/shared/src/guards.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,10 @@ export function isArrayOrVlenType(type: DType): type is ArrayType | VLenType {
529529
return type.class === DTypeClass.Array || type.class === DTypeClass.VLen;
530530
}
531531

532+
export function isScalarSelection(selection: string | undefined): boolean {
533+
return selection !== undefined && /^\d+(?:,\d+)*$/u.test(selection);
534+
}
535+
532536
export function assertScalarValue(
533537
value: unknown,
534538
type: DType,
@@ -561,10 +565,11 @@ export function assertScalarValue(
561565
export function assertDatasetValue<D extends Dataset<ScalarShape | ArrayShape>>(
562566
value: unknown,
563567
dataset: D,
568+
selection?: string,
564569
): asserts value is Value<D> {
565570
const { type } = dataset;
566571

567-
if (hasScalarShape(dataset)) {
572+
if (hasScalarShape(dataset) || isScalarSelection(selection)) {
568573
assertScalarValue(value, type);
569574
} else {
570575
assertArrayOrTypedArray(value);

support/sample/create_h5_sample.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ def print_h5t_class(dataset):
122122
"byte_string",
123123
np.array([np.void(b"\x00"), np.void(b"\x11"), np.void(b"\x22")]),
124124
)
125+
add_array(
126+
h5,
127+
"byte_string",
128+
np.array(
129+
[[np.void(b"\x00"), np.void(b"\x11")], [np.void(b"\x00"), np.void(b"\x11")]]
130+
),
131+
)
125132
add_scalar(h5, "datetime64", np.void(np.datetime64("2019-09-22T17:38:30")))
126133
add_scalar(h5, "datetime64_not-a-time", np.void(np.datetime64("NaT")))
127134

@@ -215,6 +222,36 @@ def print_h5t_class(dataset):
215222
),
216223
)
217224

225+
add_array(
226+
h5,
227+
"compound_mixed",
228+
np.array(
229+
[
230+
[
231+
(
232+
True,
233+
np.array([0, 1], np.float32),
234+
),
235+
(
236+
False,
237+
np.array([2, 3], np.float32),
238+
),
239+
],
240+
[
241+
(
242+
False,
243+
np.array([4, 5], np.float32),
244+
),
245+
(
246+
True,
247+
np.array([6, 7], np.float32),
248+
),
249+
],
250+
],
251+
[("bool", np.bool_), ("arr", np.float32, (2,))],
252+
),
253+
)
254+
218255
# === H5T_REFERENCE ===
219256

220257
add_scalar(h5, "reference", for_ref.ref, h5py.ref_dtype)
@@ -282,9 +319,9 @@ def print_h5t_class(dataset):
282319
vlen_array = add_array(
283320
h5, "vlen_utf8", shape=(3,), dtype=h5py.vlen_dtype(h5py.string_dtype())
284321
)
285-
vlen_array[0] = ['a']
286-
vlen_array[1] = ['a', 'bc']
287-
vlen_array[2] = ['a', 'bc', 'def']
322+
vlen_array[0] = ["a"]
323+
vlen_array[1] = ["a", "bc"]
324+
vlen_array[2] = ["a", "bc", "def"]
288325

289326
# === H5T_ARRAY ===
290327

0 commit comments

Comments
 (0)