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

feat(shape): update typings and add factory functions #776

Merged
merged 15 commits into from
Aug 10, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ const keys = Object.keys(data[0]).filter(d => d !== 'date') as CityName[];
const getDate = (d: CityTemperature) => d.date;

// scales
const dateScale = scaleBand<string>({
const dateScale = scaleBand({
domain: data.map(getDate),
padding: 0.2,
});
const cityScale = scaleBand<string>({
const cityScale = scaleBand({
domain: keys,
padding: 0.1,
});
Expand Down Expand Up @@ -70,7 +70,7 @@ export default function Example({
<svg width={width} height={height}>
<rect x={0} y={0} width={width} height={height} fill={background} rx={14} />
<Group top={margin.top} left={margin.left}>
<BarGroupHorizontal<CityTemperature, CityName>
<BarGroupHorizontal
data={data}
keys={keys}
width={xMax}
Expand Down
2 changes: 1 addition & 1 deletion packages/vx-demo/src/sandboxes/vx-bargroup/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default function Example({
<svg width={width} height={height}>
<rect x={0} y={0} width={width} height={height} fill={background} rx={14} />
<Group top={margin.top} left={margin.left}>
<BarGroup<CityTemperature, string>
<BarGroup
data={data}
keys={keys}
height={yMax}
Expand Down
1 change: 1 addition & 0 deletions packages/vx-shape/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@types/react": "*",
"@vx/curve": "0.0.198",
"@vx/group": "0.0.198",
"@vx/scale": "0.0.198",
"classnames": "^2.2.5",
"d3-path": "^1.0.5",
"d3-shape": "^1.2.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/vx-shape/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ export { default as LinkVerticalStep, pathVerticalStep } from './shapes/link/ste
export { default as LinkRadialStep, pathRadialStep } from './shapes/link/step/LinkRadialStep';
export { default as Polygon, getPoints, getPoint } from './shapes/Polygon';
export { default as Circle } from './shapes/Circle';

// Export factory functions
export * from './types/D3ShapeConfig';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Also export the factories

export * from './util/D3ShapeFactories';
47 changes: 17 additions & 30 deletions packages/vx-shape/src/shapes/Arc.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/unbound-method */
import React from 'react';
import cx from 'classnames';
import { arc as d3Arc, Arc as ArcType } from 'd3-shape';
import setNumOrAccessor, { NumberAccessor } from '../util/setNumberOrNumberAccessor';
import { $TSFIXME } from '../types';
import { Arc as ArcType } from 'd3-shape';
import { $TSFIXME, AddSVGProps, ArcPathConfig } from '../types';
import { arc } from '../util/D3ShapeFactories';

export type ArcProps<Datum> = {
/** className applied to path element. */
Expand All @@ -14,21 +13,7 @@ export type ArcProps<Datum> = {
children?: (args: { path: ArcType<$TSFIXME, Datum> }) => React.ReactNode;
/** React ref to the path element. */
innerRef?: React.Ref<SVGPathElement>;
/** Number or accessor function which returns a number, which defines the arc innerRadius. */
innerRadius?: NumberAccessor<Datum> | number;
/** Number or accessor function which returns a number, which defines the arc outerRadius. */
outerRadius?: NumberAccessor<Datum> | number;
/** Number or accessor function which returns a number, which defines the arc cornerRadius. */
cornerRadius?: NumberAccessor<Datum> | number;
/** Number or accessor function which returns a number, which defines the arc startAngle. */
startAngle?: NumberAccessor<Datum> | number;
/** Number or accessor function which returns a number, which defines the arc endAngle. */
endAngle?: NumberAccessor<Datum> | number;
/** Number or accessor function which returns a number, which defines the arc padAngle. */
padAngle?: NumberAccessor<Datum> | number;
/** Number or accessor function which returns a number, which defines the arc padRadius. */
padRadius?: NumberAccessor<Datum> | number;
};
} & ArcPathConfig<Datum>;

export default function Arc<Datum>({
className,
Expand All @@ -43,20 +28,22 @@ export default function Arc<Datum>({
children,
innerRef,
...restProps
}: ArcProps<Datum> & Omit<React.SVGProps<SVGPathElement>, keyof ArcProps<Datum>>) {
const arc = d3Arc<Datum>();
if (innerRadius != null) setNumOrAccessor(arc.innerRadius, innerRadius);
if (outerRadius != null) setNumOrAccessor(arc.outerRadius, outerRadius);
if (cornerRadius != null) setNumOrAccessor(arc.cornerRadius, cornerRadius);
if (startAngle != null) setNumOrAccessor(arc.startAngle, startAngle);
if (endAngle != null) setNumOrAccessor(arc.endAngle, endAngle);
if (padAngle != null) setNumOrAccessor(arc.padAngle, padAngle);
if (padRadius != null) setNumOrAccessor(arc.padRadius, padRadius);
}: AddSVGProps<ArcProps<Datum>, SVGPathElement>) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Use helper type

const path = arc({
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Use the factory to create path

innerRadius,
outerRadius,
cornerRadius,
startAngle,
endAngle,
padAngle,
padRadius,
});

if (children) return <>{children({ path: arc })}</>;
// eslint-disable-next-line react/jsx-no-useless-fragment
if (children) return <>{children({ path })}</>;
if (!data) return null;

return (
<path ref={innerRef} className={cx('vx-arc', className)} d={arc(data) || ''} {...restProps} />
<path ref={innerRef} className={cx('vx-arc', className)} d={path(data) || ''} {...restProps} />
);
}
46 changes: 6 additions & 40 deletions packages/vx-shape/src/shapes/Area.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,9 @@
import React from 'react';
import cx from 'classnames';
import { area, Area as AreaType, CurveFactory } from 'd3-shape';
import setNumOrAccessor from '../util/setNumberOrNumberAccessor';
import { AddSVGProps, BaseAreaProps } from '../types';
import { area } from '../util/D3ShapeFactories';

type NumberAccessor<Datum> = (datum: Datum, index: number, data: Datum[]) => number;

export type AreaProps<Datum> = {
/** Override render function which is passed the configured area generator as input. */
children?: (args: { path: AreaType<Datum> }) => React.ReactNode;
/** Classname applied to path element. */
className?: string;
/** Array of data for which to generate an area shape. */
data?: Datum[];
/** The defined accessor for the shape. The final area shape includes all points for which this function returns true. By default all points are defined. */
defined?: (datum: Datum, index: number, data: Datum[]) => boolean;
/** Sets the curve factory (from @vx/curve or d3-curve) for the area generator. Defaults to curveLinear. */
curve?: CurveFactory;
/** React RefObject passed to the path element. */
innerRef?: React.Ref<SVGPathElement>;
/** Sets the x0 accessor function, and sets x1 to null. */
x?: NumberAccessor<Datum> | number;
/** Specifies the x0 accessor function which defaults to d => d[0]. */
x0?: NumberAccessor<Datum> | number;
/** Specifies the x1 accessor function which defaults to null. */
x1?: NumberAccessor<Datum> | number;
/** Sets the y0 accessor function, and sets y1 to null. */
y?: NumberAccessor<Datum> | number;
/** Specifies the y0 accessor function which defaults to d => 0. */
y0?: NumberAccessor<Datum> | number;
/** Specifies the y1 accessor function which defaults to d => d[1]. */
y1?: NumberAccessor<Datum> | number;
};
export type AreaProps<Datum> = BaseAreaProps<Datum>;

export default function Area<Datum>({
children,
Expand All @@ -46,16 +19,9 @@ export default function Area<Datum>({
curve,
innerRef,
...restProps
}: AreaProps<Datum> & Omit<React.SVGProps<SVGPathElement>, keyof AreaProps<Datum>>) {
const path = area<Datum>();
if (x) setNumOrAccessor(path.x, x);
if (x0) setNumOrAccessor(path.x0, x0);
if (x1) setNumOrAccessor(path.x1, x1);
if (y) setNumOrAccessor(path.y, y);
if (y0) setNumOrAccessor(path.y0, y0);
if (y1) setNumOrAccessor(path.y1, y1);
if (defined) path.defined(defined);
if (curve) path.curve(curve);
}: AddSVGProps<AreaProps<Datum>, SVGPathElement>) {
const path = area<Datum>({ x, x0, x1, y, y0, y1, defined, curve });
// eslint-disable-next-line react/jsx-no-useless-fragment
if (children) return <>{children({ path })}</>;
return (
<path ref={innerRef} className={cx('vx-area', className)} d={path(data) || ''} {...restProps} />
Expand Down
36 changes: 8 additions & 28 deletions packages/vx-shape/src/shapes/AreaClosed.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
/* eslint-disable @typescript-eslint/unbound-method */
import React from 'react';
import cx from 'classnames';
import { area } from 'd3-shape';
import { AreaProps } from './Area';
import { ScaleType } from '../types';
import { PositionScale, AddSVGProps, BaseAreaProps } from '../types';
import setNumOrAccessor from '../util/setNumberOrNumberAccessor';
import { area } from '../util/D3ShapeFactories';

export type AreaClosedProps<Datum> = {
yScale: ScaleType;
} & Pick<
AreaProps<Datum>,
| 'className'
| 'innerRef'
| 'children'
| 'curve'
| 'defined'
| 'data'
| 'x'
| 'x0'
| 'x1'
| 'y'
| 'y0'
| 'y1'
>;
export type AreaClosedProps<Datum> = BaseAreaProps<Datum> & {
yScale: PositionScale;
};

export default function AreaClosed<Datum>({
x,
Expand All @@ -39,11 +23,8 @@ export default function AreaClosed<Datum>({
innerRef,
children,
...restProps
}: AreaClosedProps<Datum> & Omit<React.SVGProps<SVGPathElement>, keyof AreaClosedProps<Datum>>) {
const path = area<Datum>();
if (x) setNumOrAccessor(path.x, x);
if (x0) setNumOrAccessor(path.x0, x0);
if (x1) setNumOrAccessor(path.x1, x1);
}: AddSVGProps<AreaClosedProps<Datum>, SVGPathElement>) {
const path = area<Datum>({ x, x0, x1, defined, curve });
if (y0) {
setNumOrAccessor(path.y0, y0);
} else {
Expand All @@ -55,8 +36,7 @@ export default function AreaClosed<Datum>({
}
if (y && !y1) setNumOrAccessor(path.y1, y);
if (y1 && !y) setNumOrAccessor(path.y1, y1);
if (defined) path.defined(defined);
if (curve) path.curve(curve);
// eslint-disable-next-line react/jsx-no-useless-fragment
if (children) return <>{children({ path })}</>;
return (
<path
Expand Down
7 changes: 3 additions & 4 deletions packages/vx-shape/src/shapes/AreaStack.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import cx from 'classnames';
import Stack, { StackProps } from './Stack';
import { StackKey } from '../types';
import { AddSVGProps, StackKey } from '../types';

type PickProps =
| 'className'
Expand Down Expand Up @@ -43,8 +43,7 @@ export default function AreaStack<Datum, Key extends StackKey = StackKey>({
color,
children,
...restProps
}: AreaStackProps<Datum, Key> &
Omit<React.SVGProps<SVGPathElement>, keyof AreaStackProps<Datum, Key> | PickProps>) {
}: AddSVGProps<AreaStackProps<Datum, Key>, SVGPathElement>) {
return (
<Stack<Datum, Key>
className={className}
Expand Down Expand Up @@ -72,7 +71,7 @@ export default function AreaStack<Datum, Key extends StackKey = StackKey>({
className={cx('vx-area-stack', className)}
key={`area-stack-${i}-${series.key || ''}`}
d={path(series) || ''}
fill={color && color(series.key, i)}
fill={color?.(series.key, i)}
{...restProps}
/>
)))}
Expand Down
3 changes: 2 additions & 1 deletion packages/vx-shape/src/shapes/Bar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import cx from 'classnames';
import { AddSVGProps } from '../types';

export type BarProps = {
/** className to apply to rect element. */
Expand All @@ -12,6 +13,6 @@ export default function Bar({
className,
innerRef,
...restProps
}: BarProps & Omit<React.SVGProps<SVGRectElement>, keyof BarProps>) {
}: AddSVGProps<BarProps, SVGRectElement>) {
return <rect ref={innerRef} className={cx('vx-bar', className)} {...restProps} />;
}
58 changes: 29 additions & 29 deletions packages/vx-shape/src/shapes/BarGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import React from 'react';
import cx from 'classnames';
import { Group } from '@vx/group';
import { ScaleInput } from '@vx/scale';
import Bar from './Bar';
import { BarGroup, ScaleType, GroupKey, $TSFIXME } from '../types';
import {
PositionScale,
DatumObject,
AnyScaleBand,
AddSVGProps,
BaseBarGroupProps,
BarGroup,
GroupKey,
Accessor,
} from '../types';
import getBandwidth from '../util/getBandwidth';

export type BarGroupProps<Datum, Key> = {
/** Array of data for which to generate grouped bars. */
data: Datum[];
export type BarGroupProps<
Datum extends DatumObject,
Key extends GroupKey = GroupKey,
X0Scale extends AnyScaleBand = AnyScaleBand,
X1Scale extends AnyScaleBand = AnyScaleBand
> = BaseBarGroupProps<Datum, Key> & {
/** Returns the value mapped to the x0 (group position) of a bar */
x0: (d: Datum) => $TSFIXME;
x0: Accessor<Datum, ScaleInput<X0Scale>>;
/** @vx/scale or d3-scale that takes an x0 value (position of group) and maps it to an x0 axis position of the group. */
x0Scale: ScaleType;
x0Scale: X0Scale;
/** @vx/scale or d3-scale that takes a group key and maps it to an x axis position (within a group). */
x1Scale: ScaleType;
x1Scale: X1Scale;
/** @vx/scale or d3-scale that takes an y value (Datum[key]) and maps it to a y axis position. */
yScale: ScaleType;
/** Returns the desired color for a bar with a given key and index. */
color: (key: Key, index: number) => string;
/** Array of keys corresponding to stack layers. */
keys: Key[];
yScale: PositionScale;
/** Total height of the y-axis. */
height: number;
/** className applied to Bars. */
className?: string;
/** Top offset of rendered Bars. */
top?: number;
/** Left offset of rendered Bars. */
left?: number;
/** Override render function which is passed the computed BarGroups. */
children?: (barGroups: BarGroup<Key>[]) => React.ReactNode;
};
Expand Down Expand Up @@ -70,8 +74,10 @@ export type BarGroupProps<Datum, Key> = {
* Example: [https://vx-demo.now.sh/bargroup](https://vx-demo.now.sh/bargroup)
*/
export default function BarGroupComponent<
Datum extends { [key: string]: $TSFIXME },
Key extends GroupKey = GroupKey
Datum extends DatumObject,
Key extends GroupKey = GroupKey,
X0Scale extends AnyScaleBand = AnyScaleBand,
X1Scale extends AnyScaleBand = AnyScaleBand
>({
data,
className,
Expand All @@ -86,15 +92,8 @@ export default function BarGroupComponent<
height,
children,
...restProps
}: BarGroupProps<Datum, Key> &
Omit<React.SVGProps<SVGRectElement>, keyof BarGroupProps<Datum, Key>>) {
const x1Range = x1Scale.range();
const x1Domain = x1Scale.domain();

const barWidth =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic appears in a few files so it was extracted to util/getBandwidth.ts

'bandwidth' in x1Scale && typeof x1Scale.bandwidth === 'function'
? x1Scale.bandwidth()
: Math.abs(x1Range[x1Range.length - 1] - x1Range[0]) / x1Domain.length;
}: AddSVGProps<BarGroupProps<Datum, Key, X0Scale, X1Scale>, SVGRectElement>) {
const barWidth = getBandwidth(x1Scale);

const barGroups: BarGroup<Key>[] = data.map((group, i) => ({
index: i,
Expand All @@ -114,6 +113,7 @@ export default function BarGroupComponent<
}),
}));

// eslint-disable-next-line react/jsx-no-useless-fragment
if (children) return <>{children(barGroups)}</>;

return (
Expand Down
Loading