diff --git a/packages/app/src/vis-packs/core/utils.ts b/packages/app/src/vis-packs/core/utils.ts index 276ba12b5..a69d8babc 100644 --- a/packages/app/src/vis-packs/core/utils.ts +++ b/packages/app/src/vis-packs/core/utils.ts @@ -4,7 +4,9 @@ import { type InteractionInfo, } from '@h5web/lib'; import { + isBigIntArray, isBigIntTypedArray, + isBoolArray, isIntegerType, isNumericType, } from '@h5web/shared/guards'; @@ -88,14 +90,6 @@ export function getImageInteractions(keepRatio: boolean): InteractionInfo[] { return keepRatio ? BASE_INTERACTIONS : INTERACTIONS_WITH_AXIAL_ZOOM; } -function isBigIntArray(val: ArrayValue): val is bigint[] { - return Array.isArray(val) && typeof val[0] === 'bigint'; -} - -function isBoolArray(val: ArrayValue): val is boolean[] { - return Array.isArray(val) && typeof val[0] === 'boolean'; -} - export function toNumArray | undefined>( arr: T, ): T extends ArrayValue ? NumArray : undefined; diff --git a/packages/app/src/vis-packs/nexus/NxValuesFetcher.tsx b/packages/app/src/vis-packs/nexus/NxValuesFetcher.tsx index 31715e9d4..fba5955bf 100644 --- a/packages/app/src/vis-packs/nexus/NxValuesFetcher.tsx +++ b/packages/app/src/vis-packs/nexus/NxValuesFetcher.tsx @@ -1,3 +1,8 @@ +import { + assertDefined, + isBigIntArray, + isBigIntTypedArray, +} from '@h5web/shared/guards'; import { type ComplexType, type NumericLikeType, @@ -43,7 +48,21 @@ function NxValuesFetcher( const errors = useDatasetValue(signalDef.errorDataset, selection); const auxValues = useDatasetsValues(auxDatasets, selection); const auxErrors = useDatasetsValues(auxErrorDatasets, selection); - const axisValues = useDatasetsValues(axisDatasets); + const rawAxisValues = useDatasetsValues(axisDatasets); + + const axisValues = rawAxisValues.map((value, i) => { + if (!value || isBigIntArray(value) || isBigIntTypedArray(value)) { + return value; + } + + assertDefined(axisDefs[i]); + const { scalingFactor, offset } = axisDefs[i]; + if (scalingFactor === undefined || offset === undefined) { + return value; + } + + return value.map((v) => scalingFactor * v + offset); + }); return render({ title, signal, errors, auxValues, auxErrors, axisValues }); } diff --git a/packages/app/src/vis-packs/nexus/hooks.ts b/packages/app/src/vis-packs/nexus/hooks.ts index a42dc873a..5fbc01cbd 100644 --- a/packages/app/src/vis-packs/nexus/hooks.ts +++ b/packages/app/src/vis-packs/nexus/hooks.ts @@ -19,13 +19,14 @@ import { findTitleDataset, getDatasetInfo, getDefaultSlice, + getScalingInfo, getSilxStyle, } from './utils'; export const useDefaultSlice = createMemo(getDefaultSlice); export function useNxData(group: GroupWithChildren): NxData { - const { attrValuesStore } = useDataContext(); + const { attrValuesStore, valuesStore } = useDataContext(); assertNxDataGroup(group, attrValuesStore); const signalDataset = findSignalDataset(group, attrValuesStore); @@ -46,7 +47,10 @@ export function useNxData(group: GroupWithChildren): NxData { })), axisDefs: axisDatasets.map( (dataset) => - dataset && { dataset, ...getDatasetInfo(dataset, attrValuesStore) }, + dataset && { + dataset, + ...getAxisDef(group, dataset, attrValuesStore, valuesStore), + }, ), defaultSlice: useDefaultSlice(group, signalDataset.shape, attrValuesStore), silxStyle: getSilxStyle(group, attrValuesStore), diff --git a/packages/app/src/vis-packs/nexus/models.ts b/packages/app/src/vis-packs/nexus/models.ts index a537de64b..65abc4321 100644 --- a/packages/app/src/vis-packs/nexus/models.ts +++ b/packages/app/src/vis-packs/nexus/models.ts @@ -28,6 +28,11 @@ export interface DatasetInfo { unit: string | undefined; } +export interface ScalingInfo { + scalingFactor: number | undefined; + offset: number | undefined; +} + export interface DatasetDef< T extends NumericLikeType | ComplexType = NumericLikeType | ComplexType, > extends DatasetInfo { @@ -38,7 +43,7 @@ type WithError = T & { errorDataset?: Dataset; }; -export type AxisDef = DatasetDef; +export type AxisDef = DatasetDef & ScalingInfo; export type DefaultSlice = (number | '.')[]; export interface SilxStyle { diff --git a/packages/app/src/vis-packs/nexus/utils.test.ts b/packages/app/src/vis-packs/nexus/utils.test.ts index 2a76eca1f..753857be5 100644 --- a/packages/app/src/vis-packs/nexus/utils.test.ts +++ b/packages/app/src/vis-packs/nexus/utils.test.ts @@ -5,9 +5,27 @@ import { describe, expect, it } from 'vitest'; import { applyDefaultSlice, guessKeepRatio } from './utils'; const axisDataset = dataset('foo', intType(), [5]); -const axisDefNoUnit = { label: 'foo', unit: undefined, dataset: axisDataset }; -const axisDefUnitX = { label: 'foo', unit: 'mm', dataset: axisDataset }; -const axisDefUnitY = { label: 'foo', unit: 'degrees', dataset: axisDataset }; +const axisDefNoUnit = { + label: 'foo', + unit: undefined, + dataset: axisDataset, + scalingFactor: undefined, + offset: undefined, +}; +const axisDefUnitX = { + label: 'foo', + unit: 'mm', + dataset: axisDataset, + scalingFactor: undefined, + offset: undefined, +}; +const axisDefUnitY = { + label: 'foo', + unit: 'degrees', + dataset: axisDataset, + scalingFactor: undefined, + offset: undefined, +}; describe('guessKeepRatio', () => { it('should return `cover` if units of both axes are provided and equal', () => { diff --git a/packages/app/src/vis-packs/nexus/utils.ts b/packages/app/src/vis-packs/nexus/utils.ts index 3390cf6e3..42daf1028 100644 --- a/packages/app/src/vis-packs/nexus/utils.ts +++ b/packages/app/src/vis-packs/nexus/utils.ts @@ -4,6 +4,7 @@ import { assertArrayShape, assertDataset, assertDefined, + assertNum, assertNumericLikeOrComplexType, assertNumericType, assertScalarShape, @@ -26,12 +27,13 @@ import { } from '@h5web/shared/hdf5-models'; import { getChildEntity } from '@h5web/shared/hdf5-utils'; -import { type AttrValuesStore } from '../../providers/models'; +import { type AttrValuesStore, type ValuesStore } from '../../providers/models'; import { hasAttribute } from '../../utils'; import { type AxisDef, type DatasetInfo, type DefaultSlice, + type ScalingInfo, type SilxStyle, } from './models'; @@ -130,6 +132,38 @@ export function findAuxErrorDataset( return dataset; } +export function findScalingFactorDataset( + group: GroupWithChildren, + datasetName: string, +): Dataset | undefined { + const dataset = getChildEntity(group, `${datasetName}_scaling_factor`); + + if (!dataset) { + return undefined; + } + + assertDataset(dataset); + assertScalarShape(dataset); + assertNumericType(dataset); + return dataset; +} + +export function findOffsetDataset( + group: GroupWithChildren, + datasetName: string, +): Dataset | undefined { + const dataset = getChildEntity(group, `${datasetName}_offset`); + + if (!dataset) { + return undefined; + } + + assertDataset(dataset); + assertScalarShape(dataset); + assertNumericType(dataset); + return dataset; +} + export function findAssociatedDatasets( group: GroupWithChildren, type: 'axes' | 'auxiliary_signals', @@ -312,6 +346,41 @@ export function getSilxStyle( } } +function getNumericValue( + dataset: Dataset | undefined, + valuesStore: ValuesStore, +): number | undefined { + if (dataset === undefined) { + return undefined; + } + + const value = valuesStore.get({ dataset }); + assertNum(value); + return value; +} + +export function getAxisDef( + group: GroupWithChildren, + dataset: Dataset, + attrValuesStore: AttrValuesStore, + valuesStore: ValuesStore, +): ScalingInfo { + const scalingFactor = getNumericValue( + findScalingFactorDataset(group, dataset.name), + valuesStore, + ); + const offset = getNumericValue( + findOffsetDataset(group, dataset.name), + valuesStore, + ); + + return { + scalingFactor, + offset, + ...getDatasetInfo(dataset, attrValuesStore), + }; +} + export function getDatasetInfo( dataset: Dataset, attrValuesStore: AttrValuesStore, diff --git a/packages/shared/src/guards.ts b/packages/shared/src/guards.ts index 7b430890c..764b5db8f 100644 --- a/packages/shared/src/guards.ts +++ b/packages/shared/src/guards.ts @@ -148,6 +148,14 @@ export function isComplexArray(val: unknown): val is H5WebComplex[] { return Array.isArray(val) && isComplex(val[0]); } +export function isBigIntArray(val: unknown): val is bigint[] { + return Array.isArray(val) && typeof val[0] === 'bigint'; +} + +export function isBoolArray(val: unknown): val is boolean[] { + return Array.isArray(val) && typeof val[0] === 'boolean'; +} + export function isTypedArray(val: unknown): val is TypedArray { return ( val instanceof Int8Array ||