Skip to content

Commit

Permalink
feat(dataviz): add Linear components (#757)
Browse files Browse the repository at this point in the history
  • Loading branch information
WilliamKelley authored Jun 18, 2024
1 parent 612c499 commit e358979
Show file tree
Hide file tree
Showing 18 changed files with 536 additions and 0 deletions.
80 changes: 80 additions & 0 deletions packages/dataviz/src/LinearScale/LinearScale.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { LinearScale } from './LinearScale';
import { LinearUnits } from '../LinearUnits';
import { LinearUnitLabel } from '../LinearUnitLabel';

const meta: Meta<typeof LinearScale> = {
title: 'LinearScale',
component: LinearScale,
decorators: (Story) => (
<svg viewBox="0 0 200 200" width="200" height="200">
<Story />
</svg>
),
argTypes: {
valueMin: {
control: {
type: 'range',
min: 0,
max: 100,
step: 1,
},
},
valueMax: {
control: {
type: 'range',
min: 0,
max: 100,
step: 1,
},
},
lengthMin: {
control: {
type: 'range',
min: 0,
max: 200,
step: 1,
},
},
lengthMax: {
control: {
type: 'range',
min: 0,
max: 200,
step: 1,
},
},
},
args: {
valueMin: 0,
valueMax: 100,
lengthMin: 0,
lengthMax: 200,
children: (
<LinearUnits>
<LinearUnitLabel at={0}>Label</LinearUnitLabel>
<LinearUnitLabel at={50}>Label</LinearUnitLabel>
<LinearUnitLabel at={100}>Label</LinearUnitLabel>
</LinearUnits>
),
},
};

export default meta;

type Story = StoryObj<typeof LinearScale>;

export const LengthMaxPartial: Story = {
name: 'lengthMax=100 (partial)',
args: {
lengthMax: 100,
},
};

export const LengthMaxMax: Story = {
name: 'lengthMax=200 (max)',
args: {
lengthMax: 200,
},
};
25 changes: 25 additions & 0 deletions packages/dataviz/src/LinearScale/LinearScale.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { LinearScaleChildProps, LinearScaleProps } from './LinearScaleProps';

export const LinearScale = (props: LinearScaleProps) => {
const { children, lengthMax, lengthMin, valueMax, valueMin } = props;

const ctx = {
linearScale: {
lengthMax,
lengthMin,
valueMax,
valueMin,
},
};

return React.Children.map(children, (child) => {
if (React.isValidElement<LinearScaleChildProps>(child)) {
return React.cloneElement(child, {
ctx: child.props.ctx ?? ctx,
});
}

return child;
});
};
9 changes: 9 additions & 0 deletions packages/dataviz/src/LinearScale/LinearScaleProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import { LinearScaleParams } from '../utils/linear-scale';
import { LinearCtx, WithCtx } from '../utils';

export interface LinearScaleProps extends LinearScaleParams {
children?: React.ReactNode;
}

export type LinearScaleChildProps = WithCtx<Pick<LinearCtx, 'linearScale'>>;
2 changes: 2 additions & 0 deletions packages/dataviz/src/LinearScale/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './LinearScale';
export * from './LinearScaleProps';
90 changes: 90 additions & 0 deletions packages/dataviz/src/LinearUnitLabel/LinearUnitLabel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { LinearUnitLabel } from './LinearUnitLabel';

const meta: Meta<typeof LinearUnitLabel> = {
title: 'LinearUnitLabel',
component: LinearUnitLabel,
decorators: (Story) => (
<svg viewBox="0 0 200 200" width="200" height="200">
<Story />
</svg>
),
argTypes: {
at: {
control: {
type: 'range',
min: 0,
max: 100,
step: 1,
},
},
dominantBaseline: {
control: { type: 'select' },
options: [
'text-before-edge',
'text-after-edge',
'middle',
'hanging',
'alphabetic',
'auto',
],
},
offset: {
control: { type: 'range', min: -20, max: 20, step: 1 },
},
},
args: {
children: <>Label</>,
ctx: {
linearScale: {
valueMin: 0,
valueMax: 100,
lengthMin: 0,
lengthMax: 200,
},
linearUnits: {},
},
},
};

export default meta;

type Story = StoryObj<typeof LinearUnitLabel>;

export const AtMin: Story = {
name: 'at=(min)',
args: {
at: 0,
},
};

export const AtMid: Story = {
name: 'at=(mid)',
args: {
at: 50,
},
};

export const AtMax: Story = {
name: 'at=(max)',
args: {
at: 100,
},
};

export const Offset4: Story = {
name: 'offset=4 at=..',
args: {
at: 0,
offset: 4,
},
};

export const OffsetNeg4: Story = {
name: 'offset=-4 at=..',
args: {
at: 0,
offset: -4,
},
};
49 changes: 49 additions & 0 deletions packages/dataviz/src/LinearUnitLabel/LinearUnitLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import {
LinearUnitLabelElement,
LinearUnitLabelProps,
} from './LinearUnitLabelProps';
import { drawLinearUnitLabel, mergeCtxOverrides } from '../utils';

export const LinearUnitLabel = React.forwardRef<
LinearUnitLabelElement,
LinearUnitLabelProps
>(function LinearUnitLabel(props, ref) {
const {
at,
dominantBaseline: dominantBaselineProp,
offset,
ctx: ctxProp,
overrides,
...other
} = props;

if (ctxProp === undefined) {
throw Error(
'Oops! `LinearUnits` received `ctx: undefined`. Did you mean to either (1) render as a child of `LinearScale`? or (2) specify `ctx` explicitly?'
);
}

const ctx = mergeCtxOverrides(ctxProp, overrides);

const { x, y, textAnchor, dominantBaseline } = drawLinearUnitLabel({
scale: ctx.linearScale,
units: ctx.linearUnits,
unitLabel: {
at,
dominantBaseline: dominantBaselineProp,
offset,
},
});

return (
<text
ref={ref}
x={x}
y={y}
textAnchor={textAnchor}
dominantBaseline={dominantBaseline}
{...other}
/>
);
});
14 changes: 14 additions & 0 deletions packages/dataviz/src/LinearUnitLabel/LinearUnitLabelProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { LinearCtx, LinearUnitLabelParams, WithOverridableCtx } from '../utils';

export interface LinearUnitLabelElement extends SVGTextElement {}

export interface LinearUnitLabelProps
extends LinearUnitLabelParams,
WithOverridableCtx<Pick<LinearCtx, 'linearScale' | 'linearUnits'>>,
Omit<
React.SVGTextElementAttributes<LinearUnitLabelElement>,
'offset' | 'dominantBaseline'
> {
children?: React.ReactNode;
}
2 changes: 2 additions & 0 deletions packages/dataviz/src/LinearUnitLabel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './LinearUnitLabel';
export * from './LinearUnitLabelProps';
63 changes: 63 additions & 0 deletions packages/dataviz/src/LinearUnits/LinearUnits.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { LinearUnitLabel } from '../LinearUnitLabel';
import { LinearUnits } from './LinearUnits';

const meta: Meta<typeof LinearUnits> = {
title: 'LinearUnits',
component: LinearUnits,
decorators: (Story) => (
<svg viewBox="0 0 200 200" width="200" height="200">
<Story />
</svg>
),
argTypes: {
offset: {
control: { type: 'range', min: -20, max: 20, step: 1 },
},
},
args: {
children: [
<LinearUnitLabel key="0" at={0}>
Label
</LinearUnitLabel>,
<LinearUnitLabel key="1" at={50}>
Label
</LinearUnitLabel>,
<LinearUnitLabel key="2" at={100}>
Label
</LinearUnitLabel>,
],
ctx: {
linearScale: {
valueMin: 0,
valueMax: 100,
lengthMin: 0,
lengthMax: 200,
},
},
},
};

export default meta;

type Story = StoryObj<typeof LinearUnits>;

export const Default: Story = {
name: '(default)',
args: {},
};

export const Offset4: Story = {
name: 'offset=4',
args: {
offset: 4,
},
};

export const OffsetNeg4: Story = {
name: 'offset=-4',
args: {
offset: -4,
},
};
41 changes: 41 additions & 0 deletions packages/dataviz/src/LinearUnits/LinearUnits.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { LinearUnitsChildProps, LinearUnitsProps } from './LinearUnitsProps';
import { mergeCtxOverrides } from '../utils';

export const LinearUnits = (props: LinearUnitsProps) => {
const {
children,
dy,
dominantBaseline,
offset,
ctx: ctxProp,
overrides,
} = props;

if (ctxProp === undefined) {
throw Error(
'Oops! `LinearUnits` received `ctx: undefined`. Did you mean to either (1) render as a child of `LinearScale`? or (2) specify `ctx` explicitly?'
);
}

const ctxWithOverrides = mergeCtxOverrides(ctxProp, overrides);

const ctx = {
...ctxWithOverrides,
linearUnits: {
dy,
dominantBaseline,
offset,
},
};

return React.Children.map(children, (child) => {
if (React.isValidElement<LinearUnitsChildProps>(child)) {
return React.cloneElement(child, {
ctx: child.props.ctx ?? ctx,
});
}

return child;
});
};
Loading

0 comments on commit e358979

Please sign in to comment.