Skip to content

Commit

Permalink
Implement useGeometry hook to ease creation/update of geometries
Browse files Browse the repository at this point in the history
  • Loading branch information
axelboc committed Oct 26, 2023
1 parent 4917b6b commit 9a14804
Show file tree
Hide file tree
Showing 28 changed files with 971 additions and 405 deletions.
9 changes: 2 additions & 7 deletions apps/storybook/src/AxialSelectToZoom.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
assertDefined,
AxialSelectToZoom,
DataCurve,
HeatmapMesh,
Line,
mockValues,
Pan,
ResetZoomButton,
Expand Down Expand Up @@ -57,12 +57,7 @@ const Default = {
<AxialSelectToZoom {...args} />
<ResetZoomButton />

<DataCurve
abscissas={range(oneD.length)}
ordinates={oneD}
color="blue"
showErrors
/>
<Line abscissas={range(oneD.length)} ordinates={oneD} color="blue" />
</VisCanvas>
);
},
Expand Down
82 changes: 82 additions & 0 deletions apps/storybook/src/ErrorBars.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
DefaultInteractions,
ErrorBars,
useDomain,
VisCanvas,
} from '@h5web/lib';
import { assertDefined, mockValues, ScaleType } from '@h5web/shared';
import type { Meta, StoryObj } from '@storybook/react';
import { range } from 'lodash';

import FillHeight from './decorators/FillHeight';

const meta = {
title: 'Building Blocks/ErrorBars',
component: ErrorBars,
decorators: [FillHeight],
parameters: {
layout: 'fullscreen',
controls: { sort: 'requiredFirst' },
},
args: {
abscissas: range(0, mockValues.oneD.length),
ordinates: mockValues.oneD,
errors: mockValues.oneD.map(() => 10),
color: 'blue',
visible: true,
},
argTypes: {
abscissas: { control: false },
ordinates: { control: false },
errors: { control: false },
color: { control: { type: 'color' } },
},
} satisfies Meta<typeof ErrorBars>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default = {
render: (args) => {
const { abscissas, ordinates, errors, ignoreValue } = args;

const abscissaDomain = useDomain(abscissas);
const ordinateDomain = useDomain(
ordinates,
ScaleType.Log,
errors,
ignoreValue,
);

assertDefined(abscissaDomain);
assertDefined(ordinateDomain);

return (
<VisCanvas
abscissaConfig={{ visDomain: abscissaDomain, showGrid: true }}
ordinateConfig={{
visDomain: ordinateDomain,
scaleType: ScaleType.Log,
showGrid: true,
}}
>
<DefaultInteractions />
<ErrorBars {...args} />
</VisCanvas>
);
},
} satisfies Story;

export const Color = {
...Default,
args: {
color: 'red',
},
} satisfies Story;

export const IgnoreValue = {
...Default,
args: {
ignoreValue: (val) => val % 5 === 0,
},
} satisfies Story;
97 changes: 97 additions & 0 deletions apps/storybook/src/Glyphs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
DefaultInteractions,
Glyphs,
GlyphType as GlyphTypeEnum,
useDomain,
VisCanvas,
} from '@h5web/lib';
import { assertDefined, mockValues, ScaleType } from '@h5web/shared';
import type { Meta, StoryObj } from '@storybook/react';
import { range } from 'lodash';

import FillHeight from './decorators/FillHeight';

const meta = {
title: 'Building Blocks/Glyphs',
component: Glyphs,
decorators: [FillHeight],
parameters: {
layout: 'fullscreen',
controls: { sort: 'requiredFirst' },
},
args: {
abscissas: range(0, mockValues.oneD.length),
ordinates: mockValues.oneD,
glyphType: GlyphTypeEnum.Cross,
color: 'blue',
size: 6,
visible: true,
},
argTypes: {
abscissas: { control: false },
ordinates: { control: false },
glyphType: {
control: { type: 'select' },
options: Object.keys(GlyphTypeEnum),
},
color: { control: { type: 'color' } },
},
} satisfies Meta<typeof Glyphs>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default = {
render: (args) => {
const { abscissas, ordinates, ignoreValue } = args;

const abscissaDomain = useDomain(abscissas);
const ordinateDomain = useDomain(
ordinates,
ScaleType.Linear,
undefined,
ignoreValue,
);

assertDefined(abscissaDomain);
assertDefined(ordinateDomain);

return (
<VisCanvas
abscissaConfig={{ visDomain: abscissaDomain, showGrid: true }}
ordinateConfig={{ visDomain: ordinateDomain, showGrid: true }}
>
<DefaultInteractions />
<Glyphs {...args} />
</VisCanvas>
);
},
} satisfies Story;

export const Color = {
...Default,
args: {
color: 'red',
},
} satisfies Story;

export const GlyphType = {
...Default,
args: {
glyphType: GlyphTypeEnum.Square,
},
} satisfies Story;

export const Size = {
...Default,
args: {
size: 12,
},
} satisfies Story;

export const IgnoreValue = {
...Default,
args: {
ignoreValue: (val) => val % 5 === 0,
},
} satisfies Story;
71 changes: 71 additions & 0 deletions apps/storybook/src/Line.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { DefaultInteractions, Line, useDomain, VisCanvas } from '@h5web/lib';
import { assertDefined, mockValues, ScaleType } from '@h5web/shared';
import type { Meta, StoryObj } from '@storybook/react';
import { range } from 'lodash';

import FillHeight from './decorators/FillHeight';

const meta = {
title: 'Building Blocks/Line',
component: Line,
decorators: [FillHeight],
parameters: {
layout: 'fullscreen',
controls: { sort: 'requiredFirst' },
},
args: {
abscissas: range(0, mockValues.oneD.length),
ordinates: mockValues.oneD,
color: 'blue',
visible: true,
},
argTypes: {
abscissas: { control: false },
ordinates: { control: false },
color: { control: { type: 'color' } },
},
} satisfies Meta<typeof Line>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default = {
render: (args) => {
const { abscissas, ordinates, ignoreValue } = args;

const abscissaDomain = useDomain(abscissas);
const ordinateDomain = useDomain(
ordinates,
ScaleType.Linear,
undefined,
ignoreValue,
);

assertDefined(abscissaDomain);
assertDefined(ordinateDomain);

return (
<VisCanvas
abscissaConfig={{ visDomain: abscissaDomain, showGrid: true }}
ordinateConfig={{ visDomain: ordinateDomain, showGrid: true }}
>
<DefaultInteractions />
<Line {...args} />
</VisCanvas>
);
},
} satisfies Story;

export const Color = {
...Default,
args: {
color: 'purple',
},
} satisfies Story;

export const IgnoreValue = {
...Default,
args: {
ignoreValue: (val) => val % 5 === 0,
},
} satisfies Story;
78 changes: 74 additions & 4 deletions apps/storybook/src/Utilities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ Return an array of axis values of the desired length.
```ts
getAxisValues(rawValues: NumArray | undefined, axisLength: number): number[]

getAxisValues(undefined, 3) // [0, 1, 2]
getAxisValues([-1, 0, 1], 3) // [-1, 0, 1]
getAxisValues(Float32Array([-1, 0, 1], 3) // [-1, 0, 1]
getAxisValues([0, 1], 3) // Throws error as 2 != 3.
getAxisValues(undefined, 3); // [0, 1, 2]
getAxisValues([-1, 0, 1], 3); // [-1, 0, 1]
getAxisValues(new Float32Array([-1, 0, 1]), 3); // [-1, 0, 1]
getAxisValues([0, 1], 3); // Throws error as 2 != 3
```

#### getAxisDomain
Expand All @@ -173,6 +173,38 @@ getAxisDomain([4, 2, 0, -2], ScaleType.Log); // [4, 2]);
getAxisDomain([4, 2, 0], ScaleType.Linear, 0.5); // [6, -2])
```

#### createBufferAttr

Create a Three.js `BufferAttribute` for passing vertex data to shaders. Typically called when
instanciating a new `H5WebGeometry` sub-class instance (e.g. `LineGeometry`).

```ts
function createBufferAttr(
dataLength: number,
itemSize = 3,
TypedArrayCtor: TypedArrayConstructor = Float32Array,
): BufferAttribute;

createBufferAttr(100);
createBufferAttr(100, 1, Uint16Array);
```

#### createIndex

Create an [index buffer attribute](https://threejs.org/docs/index.html?q=bufferge#api/en/core/BufferGeometry.index)
to allow vertices to be re-used across multiple triangles. Argument `maxValue` helps decide
whether to initialise the `BufferAttribute` with a `Uint16Array` or `Uint32Array`; it should
typically receive the number of vertices (i.e. the length of the `position` attribute).
For an example usage, see `SurfaceMeshGeometry#prepare`

```ts
createIndex(length: number, maxValue: number): BufferAttribute

createIndex(100 * 2 * 3, 100); // 2 triangles per vertex; 3 indices per triangle
```

---

### Hooks

- **`useDomain(...args): Domain | undefined`** - Memoised version of `getDomain`.
Expand Down Expand Up @@ -316,6 +348,44 @@ const { delta, isDragging, startDrag } = useDrag({
});
```

#### useGeometry

Initialise and update a buffer geometry. Requires passing the constructor of a class that
implements the `H5WebGeometry` abstract class, which itself inherits from Three's `BufferGeometry`.

```ts
useGeometry<
AttributeNames extends string,
Params extends object,
>(
Ctor: new (len: number) => H5WebGeometry<AttributeNames, Params>,
dataLength: number,
params: Params | false | undefined, // skip updates by passing `false` or `undefined`
): H5WebGeometry<AttributeNames, Params>

const lineGeometry = useGeometry(
LineGeometry,
ordinates.length,
visible && {
abscissas,
ordinates,
abscissaScale,
ordinateScale,
ignoreValue,
},
);
```

The following built-in geometry classes are available and can be used as examples for
writing your own geometries:

- `LineGeometry`, used in `Line`
- `GlyphsGeometry`, used in `Glyphs`
- `ErrorBarsGeometry`, used in `ErrorBars`
- `ErrorCapsGeometry`, used in `ErrorBars`
- `ScatterPointsGeometry`, used in [`ScatterPoints`](https://h5web-docs.panosc.eu/?path=/docs/visualizations-scattervis--docs) (undocumented)
- `SurfaceMeshGeometry`, used in [`SurfaceMesh`](https://h5web-docs.panosc.eu/?path=/docs/experimental-surfacevis--docs) (experimental)

### Mock data

The library exposes a utility function to retrieve a mock entity's metadata and a mock dataset's value as ndarray for testing purposes.
Expand Down
Loading

0 comments on commit 9a14804

Please sign in to comment.