Skip to content

Commit

Permalink
Allow descending values in abscissas and ordinates of Heatmap and Lin…
Browse files Browse the repository at this point in the history
…eVis
  • Loading branch information
loichuder committed Oct 28, 2021
1 parent 01376a5 commit d2bbd89
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 15 deletions.
18 changes: 18 additions & 0 deletions apps/storybook/src/HeatmapVis.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ AxisValues.args = {
},
};

export const DescendingAxisValues = Template.bind({});
DescendingAxisValues.args = {
dataArray,
domain,
abscissaParams: {
value: Array.from(
{ length: dataArray.shape[1] }, // works even when right edge of last pixel is not provided
(_, i) => -100 - 10 * i
),
},
ordinateParams: {
value: Array.from(
{ length: dataArray.shape[0] + 1 },
(_, i) => (5 - 0.5 * i) / 100
),
},
};

export const Alpha = Template.bind({});
Alpha.args = {
dataArray,
Expand Down
16 changes: 15 additions & 1 deletion apps/storybook/src/LineVis.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const combinedDomain = getCombinedDomain(
getDomains([primaryArray, secondaryArray, tertiaryArray])
);

const abscissas = Array.from(
{ length: dataArray.size },
(_, i) => -10 + 0.5 * i
);
const errorsArray = ndarray(
Array.from({ length: dataArray.size }, (_, i) => Math.abs(10 - 0.5 * i)),
dataArray.shape
Expand Down Expand Up @@ -51,7 +55,17 @@ Abscissas.args = {
dataArray,
domain,
abscissaParams: {
value: Array.from({ length: dataArray.size }, (_, i) => -10 + 0.5 * i),
value: abscissas,
},
};

export const DescendingAbscissas = Template.bind({});

DescendingAbscissas.args = {
dataArray,
domain,
abscissaParams: {
value: [...abscissas].reverse(),
},
};

Expand Down
6 changes: 3 additions & 3 deletions packages/lib/src/vis/heatmap/HeatmapVis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { assertDefined, formatTooltipVal, ScaleType } from '@h5web/shared';
import type { NdArray } from 'ndarray';
import type { ReactElement, ReactNode } from 'react';

import { useDomain, useValueToIndexScale } from '../hooks';
import { useAxisDomain, useValueToIndexScale } from '../hooks';
import type { VisScaleType, AxisParams } from '../models';
import PanZoomMesh from '../shared/PanZoomMesh';
import TooltipMesh from '../shared/TooltipMesh';
Expand Down Expand Up @@ -59,11 +59,11 @@ function HeatmapVis(props: Props) {
const { rows, cols } = getDims(dataArray);

const abscissas = useAxisValues(abscissaValue, cols);
const abscissaDomain = useDomain(abscissas);
const abscissaDomain = useAxisDomain(abscissas);
assertDefined(abscissaDomain, 'Abscissas have undefined domain');

const ordinates = useAxisValues(ordinateValue, rows);
const ordinateDomain = useDomain(ordinates);
const ordinateDomain = useAxisDomain(ordinates);
assertDefined(ordinateDomain, 'Ordinates have undefined domain');

const abscissaToIndex = useValueToIndexScale(abscissas);
Expand Down
16 changes: 15 additions & 1 deletion packages/lib/src/vis/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ScaleType, getBounds, getValidDomainForScale } from '@h5web/shared';
import type { Domain } from '@h5web/shared';
import { useEventListener } from '@react-hookz/web';
import { useFrame, useThree } from '@react-three/fiber';
import type { NdArray } from 'ndarray';
Expand All @@ -7,7 +8,7 @@ import type { RefCallback } from 'react';
import { createMemo } from 'react-use';

import type { Size } from './models';
import { getCombinedDomain, getValueToIndexScale } from './utils';
import { getCombinedDomain, getValueToIndexScale, isDescending } from './utils';

const useBounds = createMemo(getBounds);
const useValidDomainForScale = createMemo(getValidDomainForScale);
Expand All @@ -21,6 +22,19 @@ export function useDomain(
return useValidDomainForScale(bounds, scaleType);
}

export function useAxisDomain(
axisValues: number[],
scaleType: ScaleType = ScaleType.Linear
): Domain | undefined {
const domain = useDomain(axisValues, scaleType);

if (!domain) {
return undefined;
}

return isDescending(axisValues) ? [domain[1], domain[0]] : domain;
}

export function useFrameRendering(): void {
const [, setNum] = useState(0);

Expand Down
17 changes: 14 additions & 3 deletions packages/lib/src/vis/line/LineVis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import type { AxisParams } from '../models';
import PanZoomMesh from '../shared/PanZoomMesh';
import TooltipMesh from '../shared/TooltipMesh';
import VisCanvas from '../shared/VisCanvas';
import { getDomain, extendDomain, DEFAULT_DOMAIN } from '../utils';
import {
getDomain,
extendDomain,
DEFAULT_DOMAIN,
isDescending,
} from '../utils';
import DataCurve from './DataCurve';
import styles from './LineVis.module.css';
import type { TooltipData } from './models';
Expand Down Expand Up @@ -75,9 +80,15 @@ function LineVis(props: Props) {

const abscissaToIndex = useValueToIndexScale(abscissas, true);

const abscissaDomain = useMemo(() => {
const abscissaDomain: Domain | undefined = useMemo(() => {
const rawDomain = getDomain(abscissas, abscissaScaleType);
return rawDomain && extendDomain(rawDomain, 0.01, abscissaScaleType);
if (!rawDomain) {
return undefined;
}
const extendedDomain = extendDomain(rawDomain, 0.01, abscissaScaleType);
return isDescending(abscissas)
? [extendedDomain[1], extendedDomain[0]]
: extendedDomain;
}, [abscissas, abscissaScaleType]);

assertDefined(abscissaDomain, 'Abscissas have undefined domain');
Expand Down
24 changes: 24 additions & 0 deletions packages/lib/src/vis/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,30 @@ describe('getValueToIndexScale', () => {
expect(scale(25)).toEqual(2);
expect(scale(100)).toEqual(2);
});

it('should create threshold scale from descending values to indices', () => {
const scale = getValueToIndexScale([30, 20, 10]);

expect(scale(100)).toEqual(0);
expect(scale(20)).toEqual(0);
expect(scale(19.9)).toEqual(1);
expect(scale(10)).toEqual(1);
expect(scale(9.9)).toEqual(2);
expect(scale(0)).toEqual(2);
});

it('should allow scale with descending values to switch at midpoints', () => {
const scale = getValueToIndexScale([30, 20, 10], true);

expect(scale(100)).toEqual(0);
expect(scale(30)).toEqual(0);
expect(scale(25)).toEqual(0);
expect(scale(24.9)).toEqual(1);
expect(scale(20)).toEqual(1);
expect(scale(15)).toEqual(1);
expect(scale(14.9)).toEqual(2);
expect(scale(0)).toEqual(2);
});
});

describe('getIntegerTicks', () => {
Expand Down
24 changes: 17 additions & 7 deletions packages/lib/src/vis/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,17 +157,23 @@ export function getValueToIndexScale(
values: number[],
switchAtMidpoints?: boolean
): ScaleThreshold<number, number> {
const indices = range(values.length);

const thresholds = switchAtMidpoints
const rawThresholds = switchAtMidpoints
? values.map((_, i) => values[i - 1] + (values[i] - values[i - 1]) / 2) // Shift the thresholds for the switch from i-1 to i to happen between values[i-1] and values[i]
: values; // Else, the switch from i-1 to i will happen at values[i]

// First threshold (going from 0 to 1) should be for the second value. Scaling the first value should return at 0.
return scaleThreshold<number, number>({
domain: thresholds.slice(1),
range: indices,
});
const thresholds = rawThresholds.slice(1);
const indices = range(values.length);

// ScaleThreshold only works with ascending values so the scale is reversed for descending values
return scaleThreshold<number, number>(
isDescending(thresholds)
? {
domain: [...thresholds].reverse(),
range: [...indices].reverse(),
}
: { domain: thresholds, range: indices }
);
}

export function getCanvasScale(
Expand Down Expand Up @@ -262,3 +268,7 @@ export function getAxisOffsets(
top: hasLabel.top ? horizontal : fallback,
};
}

export function isDescending(array: number[]): boolean {
return array[array.length - 1] - array[0] < 0;
}

0 comments on commit d2bbd89

Please sign in to comment.