Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove duplicate hooks #979

Merged
merged 2 commits into from
Feb 23, 2022
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
17 changes: 9 additions & 8 deletions apps/storybook/src/Utilities.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ getLinearGradient(interpolator: D3Interpolator, direction: 'top' | 'bottom' | 'r

#### getVisDomain

Determine the domain to be used for the visualization based on a user-selected custom domain. If a bound of the custom domain is `null`, it falls back to the corresponding bound of the data domain.
Determine the domain to be used for the visualization based on a custom domain selected by the user. If a bound of the custom domain is `null`, it falls back to the corresponding bound of the data domain.

```ts
getVisDomain(customDomain: CustomDomain, dataDomain: Domain): Domain
Expand All @@ -86,17 +86,18 @@ const visDomain2 = getVisDomain([null, 20], [0, 100]); // [0, 20]

#### getSafeDomain

Determine a domain that is safe for the visualization. This is typically called with a user-defined `customDomain`, or with a `visDomain` as returned by `getVisDomain()`.
If the domain is determined to be unsafe, a safe domain based on `fallbackDomain` is returned along with an error object. Note that `fallbackDomain` is assumed to be safe.
The domain is considered unsafe if it's inverted (`min > max`), or if the scale is log and at least one of the bounds is negative.
Make a domain safe in a given scale. This is typically called with a custom domain selected by the user (either directly or after calling `getVisDomain()`).
If `domain` is determined to be unsafe, a safe domain based on `fallbackDomain` is returned along with an errors object. Note that `fallbackDomain` is assumed to be safe in the given scale.
The domain is considered unsafe if it's inverted (`min > max`), or if the min and/or max bound is not suppoted by the scale.

```ts
getSafeDomain(domain: Domain, fallbackDomain: Domain, scaleType: ScaleType): [Domain, DomainErrors]

const safeDomain1 = getSafeDomain([-10, 50], [1, 100], ScaleType.Linear]); // [0, 50]
const safeDomain2 = getSafeDomain([-10, 50], [1, 100], ScaleType.Log]); // [1, 50]
const safeDomain3 = getSafeDomain([-50, -10], [1, 100], ScaleType.Log]); // [1, 100]
const safeDomain4 = getSafeDomain([-10, 50], [80, 100], ScaleType.Log]); // [50, 50] => log-safe min (80) is greater than max (50)
const res1 = getSafeDomain([-10, 5], [1, 2], ScaleType.Linear); // [[-10, 5], {}]
const res2 = getSafeDomain([5, -10], [1, 2], ScaleType.Linear); // [[1, 2], { minGreater: true }]
const res3 = getSafeDomain([-1, 5], [1, 2], ScaleType.Sqrt); // [[1, 5], { minError: DomainError.InvalidMinWithScale }]
const res4 = getSafeDomain([-2, 0], [1, 2], ScaleType.Log); // [[1, 2], { minError: DomainError.InvalidMinWithScale, maxError: DomainError.InvalidMaxWithScale }]
const res5 = getSafeDomain([-5, 5], [10, 20], ScaleType.Log); // [[5, 5], { minError: DomainError.CustomMaxFallback }]
```

#### scaleGamma
Expand Down
3 changes: 1 addition & 2 deletions packages/app/src/vis-packs/core/complex/MappedComplexVis.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { HeatmapVis } from '@h5web/lib';
import { HeatmapVis, useSafeDomain, useVisDomain } from '@h5web/lib';
import type { H5WebComplex, ScaleType } from '@h5web/shared';
import { createPortal } from 'react-dom';
import shallow from 'zustand/shallow';

import type { DimensionMapping } from '../../../dimension-mapper/models';
import { useHeatmapConfig } from '../heatmap/config';
import { useSafeDomain, useVisDomain } from '../heatmap/hooks';
import { useMappedArray, useSlicedDimsAndMapping } from '../hooks';
import type { AxisMapping } from '../models';
import { DEFAULT_DOMAIN } from '../utils';
Expand Down
5 changes: 2 additions & 3 deletions packages/app/src/vis-packs/core/heatmap/MappedHeatmapVis.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HeatmapVis } from '@h5web/lib';
import { HeatmapVis, useDomain, useSafeDomain, useVisDomain } from '@h5web/lib';
import type {
ArrayShape,
Dataset,
Expand All @@ -10,12 +10,11 @@ import { createPortal } from 'react-dom';
import shallow from 'zustand/shallow';

import type { DimensionMapping } from '../../../dimension-mapper/models';
import { useDomain, useMappedArray, useSlicedDimsAndMapping } from '../hooks';
import { useMappedArray, useSlicedDimsAndMapping } from '../hooks';
import type { AxisMapping } from '../models';
import { DEFAULT_DOMAIN } from '../utils';
import HeatmapToolbar from './HeatmapToolbar';
import { useHeatmapConfig } from './config';
import { useSafeDomain, useVisDomain } from './hooks';

interface Props {
dataset: Dataset<ArrayShape, NumericType>;
Expand Down
5 changes: 0 additions & 5 deletions packages/app/src/vis-packs/core/heatmap/hooks.ts

This file was deleted.

41 changes: 1 addition & 40 deletions packages/app/src/vis-packs/core/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import { getCombinedDomain } from '@h5web/lib';
import type {
AnyNumArray,
ArrayShape,
Dataset,
Domain,
NumArray,
ScalarShape,
Value,
} from '@h5web/shared';
import {
isDefined,
ScaleType,
getBounds,
getValidDomainForScale,
assertDatasetValue,
} from '@h5web/shared';
import { isDefined, assertDatasetValue } from '@h5web/shared';
import type { NdArray, TypedArray } from 'ndarray';
import { useContext, useMemo } from 'react';
import { createMemo } from 'react-use';

import type { DimensionMapping } from '../../dimension-mapper/models';
import { isAxis } from '../../dimension-mapper/utils';
Expand Down Expand Up @@ -77,35 +67,6 @@ export function useDatasetValues<D extends Dataset<ArrayShape | ScalarShape>>(
);
}

const useBounds = createMemo(getBounds);
const useValidDomainForScale = createMemo(getValidDomainForScale);

export function useDomain(
valuesArray: AnyNumArray,
scaleType: ScaleType = ScaleType.Linear,
errorArray?: AnyNumArray
): Domain | undefined {
// Distinct memoized calls allows for bounds to not be recomputed when scale type changes
const bounds = useBounds(valuesArray, errorArray);
return useValidDomainForScale(bounds, scaleType);
}

export function useDomains(
valuesArrays: AnyNumArray[],
scaleType: ScaleType = ScaleType.Linear
): (Domain | undefined)[] {
const allBounds = useMemo(() => {
return valuesArrays.map((arr) => getBounds(arr));
}, [valuesArrays]);

return useMemo(
() => allBounds.map((bounds) => getValidDomainForScale(bounds, scaleType)),
[allBounds, scaleType]
);
}

export const useCombinedDomain = createMemo(getCombinedDomain);

export function useBaseArray<T, U extends T[] | TypedArray | undefined>(
value: U,
rawDims: number[]
Expand Down
5 changes: 1 addition & 4 deletions packages/app/src/vis-packs/core/line/MappedLineVis.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LineVis } from '@h5web/lib';
import { LineVis, useCombinedDomain, useDomain, useDomains } from '@h5web/lib';
import type {
ArrayShape,
Dataset,
Expand All @@ -11,11 +11,8 @@ import shallow from 'zustand/shallow';

import type { DimensionMapping } from '../../../dimension-mapper/models';
import {
useCombinedDomain,
useMappedArrays,
useMappedArray,
useDomain,
useDomains,
useSlicedDimsAndMapping,
} from '../hooks';
import type { AxisMapping } from '../models';
Expand Down
49 changes: 48 additions & 1 deletion packages/lib/src/vis/heatmap/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,58 @@
import { ScaleType } from '@h5web/shared';
import { interpolateGreys } from 'd3-scale-chromatic';

import { getLinearGradient } from './utils';
import { DomainError } from '../models';
import { getLinearGradient, getSafeDomain } from './utils';

const white = 'rgb(255, 255, 255)';
const black = 'rgb(0, 0, 0)';
const gradientRegex = /^linear-gradient\(to top, (.+)\)$/;

describe('getSafeDomain', () => {
it('should return given domain if safe in given scale', () => {
const [domain, errors] = getSafeDomain([-10, 5], [1, 2], ScaleType.Linear);
expect(domain).toEqual([-10, 5]);
expect(errors).toEqual({});
});

it('should return fallback domain if min > max', () => {
const [domain, errors] = getSafeDomain([5, -10], [1, 2], ScaleType.Linear);
expect(domain).toEqual([1, 2]);
expect(errors).toEqual({ minGreater: true });
});

it('should use fallback min if not supported by given scale', () => {
const logResult = getSafeDomain([0, 5], [1, 2], ScaleType.Log);
const sqrtResult = getSafeDomain([-1, 5], [1, 2], ScaleType.Sqrt);

const expectedDomain = [1, 5];
const expectedErrors = { minError: DomainError.InvalidMinWithScale };

expect(logResult).toEqual([expectedDomain, expectedErrors]);
expect(sqrtResult).toEqual([expectedDomain, expectedErrors]);
});

it('should return fallback domain if neither min nor max are supported by given scale', () => {
const logResult = getSafeDomain([-2, 0], [1, 2], ScaleType.Log);
const sqrtResult = getSafeDomain([-10, -5], [1, 2], ScaleType.Sqrt);

const expectedDomain = [1, 2];
const expectedErrors = {
minError: DomainError.InvalidMinWithScale,
maxError: DomainError.InvalidMaxWithScale,
};

expect(logResult).toEqual([expectedDomain, expectedErrors]);
expect(sqrtResult).toEqual([expectedDomain, expectedErrors]);
});

it('should return empty [max, max] domain if fallback min is greater than max', () => {
const [domain, errors] = getSafeDomain([-5, 5], [10, 20], ScaleType.Log);
expect(domain).toEqual([5, 5]);
expect(errors).toEqual({ minError: DomainError.CustomMaxFallback });
});
});

describe('getLinearGradient', () => {
it('should generate linear gradient from first to last color', () => {
const gradient = getLinearGradient(interpolateGreys, 'top');
Expand Down
3 changes: 1 addition & 2 deletions packages/lib/src/vis/heatmap/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,9 @@ export function getSafeDomain(
if (domain[0] > domain[1]) {
return [fallbackDomain, { minGreater: true }];
}
const h5webScale = H5WEB_SCALES[scaleType];

const [min, max] = domain;
const { validMin } = h5webScale;
const { validMin } = H5WEB_SCALES[scaleType];

const isMinSupported = min >= validMin;
const isMaxSupported = max >= validMin;
Expand Down
69 changes: 34 additions & 35 deletions packages/lib/src/vis/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ScaleType, getBounds, getValidDomainForScale } from '@h5web/shared';
import type { Domain, AnyNumArray } from '@h5web/shared';
import { useEventListener } from '@react-hookz/web';
import { useFrame, useThree } from '@react-three/fiber';
import type { NdArray } from 'ndarray';
import { useCallback, useMemo, useState } from 'react';
import type { RefCallback } from 'react';
import { createMemo } from 'react-use';
Expand All @@ -18,29 +17,25 @@ import {

const useBounds = createMemo(getBounds);
const useValidDomainForScale = createMemo(getValidDomainForScale);

export const useCombinedDomain = createMemo(getCombinedDomain);
export const useValueToIndexScale = createMemo(getValueToIndexScale);
export const useAxisDomain = createMemo(getAxisDomain);

export function useDomain(
valuesArray: AnyNumArray,
scaleType: ScaleType = ScaleType.Linear,
errorArray?: AnyNumArray
) {
): Domain | undefined {
// Distinct memoized calls allows for bounds to not be recomputed when scale type changes
const bounds = useBounds(valuesArray, errorArray);
return useValidDomainForScale(bounds, scaleType);
}

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

useFrame(() => {
setNum(Math.random());
});
}

loichuder marked this conversation as resolved.
Show resolved Hide resolved
export function useDomains(
valuesArrays: (NdArray<number[]> | number[])[],
valuesArrays: AnyNumArray[],
loichuder marked this conversation as resolved.
Show resolved Hide resolved
scaleType: ScaleType = ScaleType.Linear
) {
): (Domain | undefined)[] {
const allBounds = useMemo(() => {
return valuesArrays.map((arr) => getBounds(arr));
}, [valuesArrays]);
Expand All @@ -51,6 +46,33 @@ export function useDomains(
);
}

export function useVisibleDomains(): {
xVisibleDomain: Domain;
yVisibleDomain: Domain;
} {
const { worldToData } = useAxisSystemContext();
const camera = useThree((state) => state.camera);

const worldBottomLeft = CAMERA_BOTTOM_LEFT.clone().unproject(camera);
const worldTopRight = CAMERA_TOP_RIGHT.clone().unproject(camera);

const dataBottomLeft = worldToData(worldBottomLeft);
const dataTopRight = worldToData(worldTopRight);

return {
xVisibleDomain: [dataBottomLeft.x, dataTopRight.x],
yVisibleDomain: [dataBottomLeft.y, dataTopRight.y],
};
}

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

useFrame(() => {
setNum(Math.random());
});
}

function onWheel(evt: WheelEvent) {
evt.preventDefault();
}
Expand All @@ -63,10 +85,6 @@ export function useWheelCapture() {
useEventListener(domElement, 'wheel', onWheel, { passive: false });
}

export const useCombinedDomain = createMemo(getCombinedDomain);

export const useValueToIndexScale = createMemo(getValueToIndexScale);

export function useCSSCustomProperties(...names: string[]): {
colors: string[];
refCallback: RefCallback<HTMLElement>;
Expand All @@ -86,22 +104,3 @@ export function useCSSCustomProperties(...names: string[]): {
refCallback,
};
}

export function useVisibleDomains(): {
xVisibleDomain: Domain;
yVisibleDomain: Domain;
} {
const { worldToData } = useAxisSystemContext();
const camera = useThree((state) => state.camera);

const worldBottomLeft = CAMERA_BOTTOM_LEFT.clone().unproject(camera);
const worldTopRight = CAMERA_TOP_RIGHT.clone().unproject(camera);

const dataBottomLeft = worldToData(worldBottomLeft);
const dataTopRight = worldToData(worldTopRight);

return {
xVisibleDomain: [dataBottomLeft.x, dataTopRight.x],
yVisibleDomain: [dataBottomLeft.y, dataTopRight.y],
};
}