diff --git a/packages/vx-shape/package.json b/packages/vx-shape/package.json index 44789baf9..4a8ef7e8c 100644 --- a/packages/vx-shape/package.json +++ b/packages/vx-shape/package.json @@ -5,6 +5,7 @@ "sideEffects": false, "main": "lib/index.js", "module": "esm/index.js", + "types": "lib/index.d.ts", "repository": "https://github.com/hshoff/vx", "files": [ "lib", @@ -23,21 +24,25 @@ "author": "@hshoff", "license": "MIT", "dependencies": { + "@types/classnames": "^2.2.9", + "@types/d3-path": "^1.0.8", + "@types/d3-shape": "^1.3.1", + "@types/react": "*", "@vx/curve": "0.0.192", "@vx/group": "0.0.192", - "@vx/point": "0.0.192", "classnames": "^2.2.5", "d3-path": "^1.0.5", "d3-shape": "^1.2.0", "prop-types": "^15.5.10" }, "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" + "react": "^16.3.0-0" }, "publishConfig": { "access": "public" }, "devDependencies": { + "@types/d3-hierarchy": "^1.1.6", "d3-hierarchy": "^1.1.8" } } diff --git a/packages/vx-shape/src/index.js b/packages/vx-shape/src/index.ts similarity index 100% rename from packages/vx-shape/src/index.js rename to packages/vx-shape/src/index.ts diff --git a/packages/vx-shape/src/shapes/Arc.jsx b/packages/vx-shape/src/shapes/Arc.jsx deleted file mode 100644 index 125444f06..000000000 --- a/packages/vx-shape/src/shapes/Arc.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { arc as d3Arc } from 'd3-shape'; - -Arc.propTypes = { - className: PropTypes.string, - data: PropTypes.any, - children: PropTypes.func, - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - centroid: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - innerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - outerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - cornerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - startAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - endAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - padAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - padRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), -}; - -export default function Arc({ - className, - data, - centroid, - innerRadius, - outerRadius, - cornerRadius, - startAngle, - endAngle, - padAngle, - padRadius, - children, - innerRef, - ...restProps -}) { - const arc = d3Arc(); - if (centroid) arc.centroid(centroid); - if (innerRadius !== undefined) arc.innerRadius(innerRadius); - if (outerRadius !== undefined) arc.outerRadius(outerRadius); - if (cornerRadius !== undefined) arc.cornerRadius(cornerRadius); - if (startAngle !== undefined) arc.startAngle(startAngle); - if (endAngle !== undefined) arc.endAngle(endAngle); - if (padAngle !== undefined) arc.padAngle(padAngle); - if (padRadius !== undefined) arc.padRadius(padRadius); - if (children) return children({ path: arc }); - return ; -} diff --git a/packages/vx-shape/src/shapes/Arc.tsx b/packages/vx-shape/src/shapes/Arc.tsx new file mode 100644 index 000000000..8193337c9 --- /dev/null +++ b/packages/vx-shape/src/shapes/Arc.tsx @@ -0,0 +1,62 @@ +/* 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'; + +export type ArcProps = { + /** className applied to path element. */ + className?: string; + /** A Datum for which to generate an arc. */ + data?: Datum; + /** Override render function which is passed the configured arc generator as input. */ + children?: (args: { path: ArcType<$TSFIXME, Datum> }) => React.ReactNode; + /** React ref to the path element. */ + innerRef?: React.Ref; + /** Number or accessor function which returns a number, which defines the arc innerRadius. */ + innerRadius?: NumberAccessor | number; + /** Number or accessor function which returns a number, which defines the arc outerRadius. */ + outerRadius?: NumberAccessor | number; + /** Number or accessor function which returns a number, which defines the arc cornerRadius. */ + cornerRadius?: NumberAccessor | number; + /** Number or accessor function which returns a number, which defines the arc startAngle. */ + startAngle?: NumberAccessor | number; + /** Number or accessor function which returns a number, which defines the arc endAngle. */ + endAngle?: NumberAccessor | number; + /** Number or accessor function which returns a number, which defines the arc padAngle. */ + padAngle?: NumberAccessor | number; + /** Number or accessor function which returns a number, which defines the arc padRadius. */ + padRadius?: NumberAccessor | number; +}; + +export default function Arc({ + className, + data, + innerRadius, + outerRadius, + cornerRadius, + startAngle, + endAngle, + padAngle, + padRadius, + children, + innerRef, + ...restProps +}: ArcProps & Omit, keyof ArcProps>) { + const arc = d3Arc(); + 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); + + if (children) return <>{children({ path: arc })}; + if (!data) return null; + + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/Area.jsx b/packages/vx-shape/src/shapes/Area.jsx deleted file mode 100644 index 497682006..000000000 --- a/packages/vx-shape/src/shapes/Area.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { area } from 'd3-shape'; - -Area.propTypes = { - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, - defined: PropTypes.func, - curve: PropTypes.func, - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - x: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - x0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - x1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), -}; - -export default function Area({ - children, - x, - x0, - x1, - y, - y0, - y1, - data, - defined = () => true, - className, - curve, - innerRef, - ...restProps -}) { - const path = area(); - if (x) path.x(x); - if (x0) path.x0(x0); - if (x1) path.x1(x1); - if (y) path.y(y); - if (y0) path.y0(y0); - if (y1) path.y1(y1); - if (defined) path.defined(defined); - if (curve) path.curve(curve); - if (children) return children({ path }); - return ( - - - - ); -} diff --git a/packages/vx-shape/src/shapes/Area.tsx b/packages/vx-shape/src/shapes/Area.tsx new file mode 100644 index 000000000..5ac0a71e2 --- /dev/null +++ b/packages/vx-shape/src/shapes/Area.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import cx from 'classnames'; +import { area, Area as AreaType, CurveFactory } from 'd3-shape'; +import setNumOrAccessor from '../util/setNumberOrNumberAccessor'; + +type NumberAccessor = (datum: Datum, index: number, data: Datum[]) => number; + +export type AreaProps = { + /** Override render function which is passed the configured area generator as input. */ + children?: (args: { path: AreaType }) => 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; + /** Sets the x0 accessor function, and sets x1 to null. */ + x?: NumberAccessor | number; + /** Specifies the x0 accessor function which defaults to d => d[0]. */ + x0?: NumberAccessor | number; + /** Specifies the x1 accessor function which defaults to null. */ + x1?: NumberAccessor | number; + /** Sets the y0 accessor function, and sets y1 to null. */ + y?: NumberAccessor | number; + /** Specifies the y0 accessor function which defaults to d => 0. */ + y0?: NumberAccessor | number; + /** Specifies the y1 accessor function which defaults to d => d[1]. */ + y1?: NumberAccessor | number; +}; + +export default function Area({ + children, + x, + x0, + x1, + y, + y0, + y1, + data = [], + defined = () => true, + className, + curve, + innerRef, + ...restProps +}: AreaProps & Omit, keyof AreaProps>) { + const path = area(); + 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); + if (children) return <>{children({ path })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/AreaClosed.jsx b/packages/vx-shape/src/shapes/AreaClosed.jsx deleted file mode 100644 index 03800cf25..000000000 --- a/packages/vx-shape/src/shapes/AreaClosed.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { area } from 'd3-shape'; - -AreaClosed.propTypes = { - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, - defined: PropTypes.func, - curve: PropTypes.func, - yScale: PropTypes.func, - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - x: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - x0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - x1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), -}; - -export default function AreaClosed({ - x, - x0, - x1, - y, - y1, - y0, - yScale, - data, - defined = () => true, - className, - curve, - innerRef, - children, - ...restProps -}) { - const path = area(); - if (x) path.x(x); - if (x0) path.x0(x0); - if (x1) path.x1(x1); - if (y0) { - path.y0(y0); - } else { - path.y0(yScale.range()[0]); - } - if (y && !y1) path.y1(y); - if (y1 && !y) path.y1(y1); - if (defined) path.defined(defined); - if (curve) path.curve(curve); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/AreaClosed.tsx b/packages/vx-shape/src/shapes/AreaClosed.tsx new file mode 100644 index 000000000..023655f00 --- /dev/null +++ b/packages/vx-shape/src/shapes/AreaClosed.tsx @@ -0,0 +1,69 @@ +/* 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 setNumOrAccessor from '../util/setNumberOrNumberAccessor'; + +export type AreaClosedProps = { + yScale: ScaleType; +} & Pick< + AreaProps, + | 'className' + | 'innerRef' + | 'children' + | 'curve' + | 'defined' + | 'data' + | 'x' + | 'x0' + | 'x1' + | 'y' + | 'y0' + | 'y1' +>; + +export default function AreaClosed({ + x, + x0, + x1, + y, + y1, + y0, + yScale, + data = [], + defined = () => true, + className, + curve, + innerRef, + children, + ...restProps +}: AreaClosedProps & Omit, keyof AreaClosedProps>) { + const path = area(); + if (x) setNumOrAccessor(path.x, x); + if (x0) setNumOrAccessor(path.x0, x0); + if (x1) setNumOrAccessor(path.x1, x1); + if (y0) { + setNumOrAccessor(path.y0, y0); + } else { + /** + * by default set the baseline to the first element of the yRange + * @TODO take the minimum instead? + */ + path.y0(yScale.range()[0]); + } + if (y && !y1) setNumOrAccessor(path.y1, y); + if (y1 && !y) setNumOrAccessor(path.y1, y1); + if (defined) path.defined(defined); + if (curve) path.curve(curve); + if (children) return <>{children({ path })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/AreaStack.jsx b/packages/vx-shape/src/shapes/AreaStack.jsx deleted file mode 100644 index 58782b6fa..000000000 --- a/packages/vx-shape/src/shapes/AreaStack.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import Stack from './Stack'; - -AreaStack.propTypes = { - className: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number, - keys: PropTypes.array, - data: PropTypes.array, - curve: PropTypes.func, - color: PropTypes.func, - children: PropTypes.func, - x: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - x0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - x1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - value: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - defined: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), - order: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), - offset: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), -}; - -export default function AreaStack({ - className, - top, - left, - keys, - data, - curve, - defined, - x, - x0, - x1, - y0, - y1, - value, - order, - offset, - color, - children, - ...restProps -}) { - return ( - - {children || - (({ stacks, path }) => { - return stacks.map((series, i) => { - return ( - - ); - }); - })} - - ); -} diff --git a/packages/vx-shape/src/shapes/AreaStack.tsx b/packages/vx-shape/src/shapes/AreaStack.tsx new file mode 100644 index 000000000..5001b5419 --- /dev/null +++ b/packages/vx-shape/src/shapes/AreaStack.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import cx from 'classnames'; +import Stack, { StackProps } from './Stack'; + +export type AreaStackProps = Pick< + StackProps, + | 'className' + | 'top' + | 'left' + | 'keys' + | 'data' + | 'curve' + | 'defined' + | 'x' + | 'x0' + | 'x1' + | 'y0' + | 'y1' + | 'value' + | 'order' + | 'offset' + | 'color' + | 'children' +>; + +export default function AreaStack({ + className, + top, + left, + keys, + data, + curve, + defined, + x, + x0, + x1, + y0, + y1, + value, + order, + offset, + color, + children, + ...restProps +}: AreaStackProps & Omit, keyof AreaStackProps>) { + return ( + + className={className} + top={top} + left={left} + keys={keys} + data={data} + curve={curve} + defined={defined} + x={x} + x0={x0} + x1={x1} + y0={y0} + y1={y1} + value={value} + order={order} + offset={offset} + color={color} + {...restProps} + > + {children || + (({ stacks, path }) => + stacks.map((series, i) => ( + + )))} + + ); +} diff --git a/packages/vx-shape/src/shapes/Bar.jsx b/packages/vx-shape/src/shapes/Bar.jsx deleted file mode 100644 index f2de1b495..000000000 --- a/packages/vx-shape/src/shapes/Bar.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; - -Bar.propTypes = { - className: PropTypes.string, - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), -}; - -export default function Bar({ className, innerRef, ...restProps }) { - return ; -} diff --git a/packages/vx-shape/src/shapes/Bar.tsx b/packages/vx-shape/src/shapes/Bar.tsx new file mode 100644 index 000000000..e29b1388d --- /dev/null +++ b/packages/vx-shape/src/shapes/Bar.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import cx from 'classnames'; + +export type BarProps = { + /** className to apply to rect element. */ + className?: string; + /** reference to rect element. */ + innerRef?: React.Ref; +}; + +export default function Bar({ + className, + innerRef, + ...restProps +}: BarProps & Omit, keyof BarProps>) { + return ; +} diff --git a/packages/vx-shape/src/shapes/BarGroup.jsx b/packages/vx-shape/src/shapes/BarGroup.jsx deleted file mode 100644 index 0a017e3d9..000000000 --- a/packages/vx-shape/src/shapes/BarGroup.jsx +++ /dev/null @@ -1,195 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { Group } from '@vx/group'; -import objHasMethod from '../util/objHasMethod'; -import Bar from './Bar'; - -BarGroup.propTypes = { - /** - * An array of bar group objects. - */ - data: PropTypes.array.isRequired, - /** - * ```js - * x0(barGroup) - * ``` - * An accessor function that returns the `x0` value for each datum in *props*.**data**. - */ - x0: PropTypes.func.isRequired, - /** - * ```js - * x0Scale(x0(barGroup)) - * ``` - * A scale function that returns the x position of the bar group. - */ - x0Scale: PropTypes.func.isRequired, - /** - * ```js - * x1Scale(key) - * ``` - * A scale function that returns the x position of the bar within a bar group. - */ - x1Scale: PropTypes.func.isRequired, - /** - * ```js - * yScale(value) - * ``` - * A scale function that retuns the y position of the bar within a bar group. `value` is the value of the `key` in the bar group. - */ - yScale: PropTypes.func.isRequired, - /** - * ```js - * color(key, barIndex) - * ``` - * A function that returns color for each bar within a bar group. - */ - color: PropTypes.func.isRequired, - /** - * An array of strings containing the key for each bar group. Each bar within a bar group will follow the order of this array. - */ - keys: PropTypes.array.isRequired, - /** - * Height is used to align the bottom of the the bars. barHeight = height - yScale(bar.value), where bar.y = yScale(bar.value). - */ - height: PropTypes.number.isRequired, - /** - * Add a class name to the containing `` element. - */ - className: PropTypes.string, - /** - * A top pixel offset applied to the entire bar group. - */ - top: PropTypes.number, - /** - * A left pixel offset applied to the entire bar group. - */ - left: PropTypes.number, - /** - * A function that returns a react component. Useful for generating the bar group data with full control over what is rendered. The functions first argument will be the bar groups data as an array of objects with the following properties: - * - * - `index` - the index of the group based on *props*.**data** array. - * - `x0` - the position of the group based on *props*.**x0** & *props*.**x0Scale**. - * - `bars` - array of objects, ordered by *props*.**keys**, with the following properties: - * + `index` - the index of the bar for the current group. - * + `key` - the key of the bar. - * + `width` - the width of the bar. This will be `x1Scale.bandwidth()`. If `x1Scale` does not have a bandwidth property, then it becomes: - * ```js - * x1Range = x1Scale.range(); - * x1Domain = x1Scale.domain(); - * barWidth = Math.abs(x1Range[x1Range.length - 1] - x1Range[0]) / x1Domain.length - * ``` - * + `height` - the height of the bar. - * + `x` - the x position of the bar. - * + `y` - the y position of the bar. - * + `color` - the color of the bar. - */ - children: PropTypes.func, -}; - -/** - * Generates bar groups as an array of objects and renders ``s for each datum grouped by `key`. A general setup might look like this: - * - * ```js - * const data = [{ - * date: date1, - * key1: value, - * key2: value, - * key3: value - * }, { - * date: date2, - * key1: value, - * key2: value, - * key3: value, - * }]; - * - * const x0 = d => d.date; - * const keys = [key1, key2, key3]; - * - * const x0Scale = scaleBand({ - * domain: data.map(x0), - * padding: 0.2 - * }); - * const x1Scale = scaleBand({ - * domain: keys, - * padding: 0.1 - * }); - * const yScale = scaleLinear({ - * domain: [0, Math.max(...data.map(d => Math.max(...keys.map(key => d[key]))))] - * }); - * const color = scaleOrdinal({ - * domain: keys, - * range: [blue, green, purple] - * }); - * ``` - * - * Example: [https://vx-demo.now.sh/bargroup](https://vx-demo.now.sh/bargroup) - */ -export default function BarGroup({ - data, - className, - top, - left, - x0, - x0Scale, - x1Scale, - yScale, - color, - keys, - height, - children, - ...restProps -}) { - const x1Range = x1Scale.range(); - const x1Domain = x1Scale.domain(); - - const barWidth = objHasMethod(x1Scale, 'bandwidth') - ? x1Scale.bandwidth() - : Math.abs(x1Range[x1Range.length - 1] - x1Range[0]) / x1Domain.length; - - const barGroups = data.map((group, i) => { - return { - index: i, - x0: x0Scale(x0(group)), - bars: keys.map((key, j) => { - const value = group[key]; - return { - index: j, - key, - value, - width: barWidth, - x: x1Scale(key), - y: yScale(value), - color: color(key, j), - height: height - yScale(value), - }; - }), - }; - }); - - if (children) return children(barGroups); - - return ( - - {barGroups.map(barGroup => { - return ( - - {barGroup.bars.map(bar => { - return ( - - ); - })} - - ); - })} - - ); -} diff --git a/packages/vx-shape/src/shapes/BarGroup.tsx b/packages/vx-shape/src/shapes/BarGroup.tsx new file mode 100644 index 000000000..58266cb92 --- /dev/null +++ b/packages/vx-shape/src/shapes/BarGroup.tsx @@ -0,0 +1,136 @@ +import React from 'react'; +import cx from 'classnames'; +import { Group } from '@vx/group'; +import Bar from './Bar'; +import { BarGroup, ScaleType, GroupKey, $TSFIXME } from '../types'; + +export type BarGroupProps = { + /** Array of data for which to generate grouped bars. */ + data: Datum[]; + /** Returns the value mapped to the x0 (group position) of a bar */ + x0: (d: Datum) => $TSFIXME; + /** @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; + /** @vx/scale or d3-scale that takes a group key and maps it to an x axis position (within a group). */ + x1Scale: ScaleType; + /** @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: GroupKey, index: number) => string; + /** Array of keys corresponding to stack layers. */ + keys: GroupKey[]; + /** 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[]) => React.ReactNode; +}; + +/** + * Generates bar groups as an array of objects and renders ``s for each datum grouped by `key`. A general setup might look like this: + * + * ```js + * const data = [{ + * date: date1, + * key1: value, + * key2: value, + * key3: value + * }, { + * date: date2, + * key1: value, + * key2: value, + * key3: value, + * }]; + * + * const x0 = d => d.date; + * const keys = [key1, key2, key3]; + * + * const x0Scale = scaleBand({ + * domain: data.map(x0), + * padding: 0.2 + * }); + * const x1Scale = scaleBand({ + * domain: keys, + * padding: 0.1 + * }); + * const yScale = scaleLinear({ + * domain: [0, Math.max(...data.map(d => Math.max(...keys.map(key => d[key]))))] + * }); + * const color = scaleOrdinal({ + * domain: keys, + * range: [blue, green, purple] + * }); + * ``` + * + * Example: [https://vx-demo.now.sh/bargroup](https://vx-demo.now.sh/bargroup) + */ +export default function BarGroupComponent({ + data, + className, + top, + left, + x0, + x0Scale, + x1Scale, + yScale, + color, + keys, + height, + children, + ...restProps +}: BarGroupProps & Omit, keyof BarGroupProps>) { + const x1Range = x1Scale.range(); + const x1Domain = x1Scale.domain(); + + const barWidth = + 'bandwidth' in x1Scale && typeof x1Scale.bandwidth === 'function' + ? x1Scale.bandwidth() + : Math.abs(x1Range[x1Range.length - 1] - x1Range[0]) / x1Domain.length; + + const barGroups: BarGroup[] = data.map((group, i) => { + return { + index: i, + x0: x0Scale(x0(group)), + bars: keys.map((key, j) => { + const value = group[key]; + return { + index: j, + key, + value, + width: barWidth, + x: x1Scale(key), + y: yScale(value), + color: color(key, j), + height: height - yScale(value), + }; + }), + }; + }); + + if (children) return <>{children(barGroups)}; + + return ( + + {barGroups.map(barGroup => ( + + {barGroup.bars.map(bar => ( + + ))} + + ))} + + ); +} diff --git a/packages/vx-shape/src/shapes/BarGroupHorizontal.jsx b/packages/vx-shape/src/shapes/BarGroupHorizontal.jsx deleted file mode 100644 index 16e8b51ce..000000000 --- a/packages/vx-shape/src/shapes/BarGroupHorizontal.jsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { Group } from '@vx/group'; -import objHasMethod from '../util/objHasMethod'; -import Bar from './Bar'; - -BarGroupHorizontal.propTypes = { - data: PropTypes.array.isRequired, - y0: PropTypes.func.isRequired, - y0Scale: PropTypes.func.isRequired, - y1Scale: PropTypes.func.isRequired, - xScale: PropTypes.func.isRequired, - color: PropTypes.func.isRequired, - keys: PropTypes.array.isRequired, - width: PropTypes.number.isRequired, - className: PropTypes.string, - x: PropTypes.func, - top: PropTypes.number, - left: PropTypes.number, - children: PropTypes.func, -}; - -export default function BarGroupHorizontal({ - data, - className, - top, - left, - x = (/** val */) => 0, - y0, - y0Scale, - y1Scale, - xScale, - color, - keys, - width, - children, - ...restProps -}) { - const y1Range = y1Scale.range(); - const y1Domain = y1Scale.domain(); - const barHeight = objHasMethod(y1Scale, 'bandwidth') - ? y1Scale.bandwidth() - : Math.abs(y1Range[y1Range.length - 1] - y1Range[0]) / y1Domain.length; - - const barGroups = data.map((group, i) => { - return { - index: i, - y0: y0Scale(y0(group)), - bars: keys.map((key, j) => { - const value = group[key]; - return { - index: j, - key, - value, - height: barHeight, - x: x(value), - y: y1Scale(key), - color: color(key, j), - width: width - xScale(value), - }; - }), - }; - }); - - if (children) return children(barGroups); - - return ( - - {barGroups.map(barGroup => { - return ( - - {barGroup.bars.map(bar => { - return ( - - ); - })} - - ); - })} - - ); -} diff --git a/packages/vx-shape/src/shapes/BarGroupHorizontal.tsx b/packages/vx-shape/src/shapes/BarGroupHorizontal.tsx new file mode 100644 index 000000000..2f5b27e07 --- /dev/null +++ b/packages/vx-shape/src/shapes/BarGroupHorizontal.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import cx from 'classnames'; +import { Group } from '@vx/group'; +import Bar from './Bar'; +import { BarGroupProps } from './BarGroup'; +import { ScaleType, BarGroupHorizontal, $TSFIXME } from '../types'; + +export type BarGroupHorizontalProps = Pick< + BarGroupProps, + 'data' | 'className' | 'top' | 'left' | 'keys' | 'color' +> & { + /** Returns the value (Datum[key]) mapped to the x of a bar */ + x?: (barValue: number) => number; + /** Returns the value mapped to the y0 (position of group) of a bar */ + y0: (d: Datum) => $TSFIXME; + /** @vx/scale or d3-scale that takes a key value (Datum[key]) and maps it to an x axis position (width of bar). */ + xScale: ScaleType; + /** @vx/scale or d3-scale that takes a y0 value (position of group) and maps it to a y axis position. */ + y0Scale: ScaleType; + /** @vx/scale or d3-scale that takes a group key and maps it to an y axis position (within a group). */ + y1Scale: ScaleType; + /** Total width of the x-axis. */ + width: number; + /** Override render function which is passed the computed Ba/rGroups. */ + children?: (barGroups: BarGroupHorizontal[]) => React.ReactNode; +}; + +export default function BarGroupHorizontalComponent({ + data, + className, + top, + left, + x = (/** val */) => 0, + y0, + y0Scale, + y1Scale, + xScale, + color, + keys, + width, + children, + ...restProps +}: BarGroupHorizontalProps & + Omit, keyof BarGroupHorizontalProps>) { + const y1Range = y1Scale.range(); + const y1Domain = y1Scale.domain(); + const barHeight = + 'bandwidth' in y1Scale && typeof y1Scale.bandwidth === 'function' + ? y1Scale.bandwidth() + : Math.abs(y1Range[y1Range.length - 1] - y1Range[0]) / y1Domain.length; + + const barGroups = data.map((group, i) => { + return { + index: i, + y0: y0Scale(y0(group)), + bars: keys.map((key, j) => { + const value = group[key]; + return { + index: j, + key, + value, + height: barHeight, + x: x(value), + y: y1Scale(key), + color: color(key, j), + width: width - xScale(value), + }; + }), + }; + }); + + if (children) return <>{children(barGroups)}; + + return ( + + {barGroups.map(barGroup => ( + + {barGroup.bars.map(bar => ( + + ))} + + ))} + + ); +} diff --git a/packages/vx-shape/src/shapes/BarStack.jsx b/packages/vx-shape/src/shapes/BarStack.jsx deleted file mode 100644 index 3f47618e1..000000000 --- a/packages/vx-shape/src/shapes/BarStack.jsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { Group } from '@vx/group'; -import { stack as d3stack } from 'd3-shape'; -import stackOrder from '../util/stackOrder'; -import stackOffset from '../util/stackOffset'; -import objHasMethod from '../util/objHasMethod'; -import Bar from './Bar'; - -BarStack.propTypes = { - data: PropTypes.array.isRequired, - x: PropTypes.func.isRequired, - xScale: PropTypes.func.isRequired, - yScale: PropTypes.func.isRequired, - color: PropTypes.func.isRequired, - keys: PropTypes.array.isRequired, - className: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number, - children: PropTypes.func, - y0: PropTypes.func, - y1: PropTypes.func, - order: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), - offset: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), - value: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), -}; - -export default function BarStack({ - data, - className, - top, - left, - x, - y0 = d => d[0], - y1 = d => d[1], - xScale, - yScale, - color, - keys, - value, - order, - offset, - children, - ...restProps -}) { - const stack = d3stack(); - if (keys) stack.keys(keys); - if (value) stack.value(value); - if (order) stack.order(stackOrder(order)); - if (offset) stack.offset(stackOffset(offset)); - - const stacks = stack(data); - - const xRange = xScale.range(); - const xDomain = xScale.domain(); - const barWidth = objHasMethod(xScale, 'bandwidth') - ? xScale.bandwidth() - : Math.abs(xRange[xRange.length - 1] - xRange[0]) / xDomain.length; - - const barStacks = stacks.map((barStack, i) => { - const { key } = barStack; - return { - index: i, - key, - bars: barStack.map((bar, j) => { - const barHeight = yScale(y0(bar)) - yScale(y1(bar)); - const barY = yScale(y1(bar)); - const barX = objHasMethod(xScale, 'bandwidth') - ? xScale(x(bar.data)) - : Math.max(xScale(x(bar.data)) - barWidth / 2); - return { - bar, - key, - index: j, - height: barHeight, - width: barWidth, - x: barX, - y: barY, - color: color(barStack.key, j), - }; - }), - }; - }); - - if (children) return children(barStacks); - - return ( - - {barStacks.map(barStack => { - return barStack.bars.map(bar => { - return ( - - ); - }); - })} - - ); -} diff --git a/packages/vx-shape/src/shapes/BarStack.tsx b/packages/vx-shape/src/shapes/BarStack.tsx new file mode 100644 index 000000000..12da86991 --- /dev/null +++ b/packages/vx-shape/src/shapes/BarStack.tsx @@ -0,0 +1,117 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +import React from 'react'; +import cx from 'classnames'; +import { Group } from '@vx/group'; +import { stack as d3stack, SeriesPoint } from 'd3-shape'; + +import stackOrder from '../util/stackOrder'; +import stackOffset from '../util/stackOffset'; +import Bar from './Bar'; +import { StackProps, NumAccessor } from './Stack'; +import { ScaleType, StackKey, BarStack, $TSFIXME } from '../types'; +import setNumOrAccessor from '../util/setNumberOrNumberAccessor'; + +export { NumAccessor }; + +export type BarStackProps = Pick< + StackProps, + 'data' | 'className' | 'top' | 'left' | 'keys' | 'order' | 'offset' | 'value' +> & { + /** Returns the value mapped to the x of a bar */ + x: (d: Datum) => number; + /** Returns the value mapped to the y0 of a bar. */ + y0?: (d: SeriesPoint) => number; + /** Returns the value mapped to the y1 of a bar. */ + y1?: (d: SeriesPoint) => number; + /** @vx/scale or d3-scale that takes an x value and maps it to an x axis position. */ + xScale: ScaleType; + /** @vx/scale or d3-scale that takes a y value and maps it to an y axis position. */ + yScale: ScaleType; + /** Returns the desired color for a bar with a given key and index. */ + color: (key: StackKey, index: number) => string; + /** Override render function which is passed the configured arc generator as input. */ + children?: (stacks: BarStack[]) => React.ReactNode; +}; + +export default function BarStackComponent({ + data, + className, + top, + left, + x, + y0 = (d: $TSFIXME) => d && d[0], + y1 = (d: $TSFIXME) => d && d[1], + xScale, + yScale, + color, + keys, + value, + order, + offset, + children, + ...restProps +}: BarStackProps & Omit, keyof BarStackProps>) { + const stack = d3stack(); + if (keys) stack.keys(keys); + if (value) setNumOrAccessor>(stack.value, value); + if (order) stack.order(stackOrder(order)); + if (offset) stack.offset(stackOffset(offset)); + + const stacks = stack(data); + + const xRange = xScale.range(); + const xDomain = xScale.domain(); + const barWidth = + 'bandwidth' in xScale && typeof xScale.bandwidth === 'function' + ? xScale.bandwidth() + : Math.abs(xRange[xRange.length - 1] - xRange[0]) / xDomain.length; + + const barStacks: BarStack[] = stacks.map((barStack, i) => { + const { key } = barStack; + return { + index: i, + key, + bars: barStack.map((bar, j) => { + const barHeight = yScale(y0(bar)) - yScale(y1(bar)); + const barY = yScale(y1(bar)); + const barX = + 'bandwidth' in xScale && typeof xScale.bandwidth === 'function' + ? xScale(x(bar.data)) + : Math.max(xScale(x(bar.data)) - barWidth / 2); + + return { + bar, + key, + index: j, + height: barHeight, + width: barWidth, + x: barX, + y: barY, + color: color(barStack.key, j), + }; + }), + }; + }); + + if (children) return <>{children(barStacks)}; + + return ( + + {barStacks.map(barStack => { + return barStack.bars.map(bar => { + return ( + + ); + }); + })} + + ); +} diff --git a/packages/vx-shape/src/shapes/BarStackHorizontal.jsx b/packages/vx-shape/src/shapes/BarStackHorizontal.tsx similarity index 50% rename from packages/vx-shape/src/shapes/BarStackHorizontal.jsx rename to packages/vx-shape/src/shapes/BarStackHorizontal.tsx index 194dd19cc..ddcc8f412 100644 --- a/packages/vx-shape/src/shapes/BarStackHorizontal.jsx +++ b/packages/vx-shape/src/shapes/BarStackHorizontal.tsx @@ -1,39 +1,47 @@ +/* eslint-disable @typescript-eslint/unbound-method */ import React from 'react'; -import PropTypes from 'prop-types'; import cx from 'classnames'; import { Group } from '@vx/group'; -import { stack as d3stack } from 'd3-shape'; +import { stack as d3stack, SeriesPoint } from 'd3-shape'; + import stackOrder from '../util/stackOrder'; import stackOffset from '../util/stackOffset'; -import objHasMethod from '../util/objHasMethod'; import Bar from './Bar'; +import { BarStackProps, NumAccessor } from './BarStack'; +import { StackKey, $TSFIXME } from '../types'; +import setNumOrAccessor from '../util/setNumberOrNumberAccessor'; -BarStackHorizontal.propTypes = { - data: PropTypes.array.isRequired, - y: PropTypes.func.isRequired, - x0: PropTypes.func, - x1: PropTypes.func, - xScale: PropTypes.func.isRequired, - yScale: PropTypes.func.isRequired, - color: PropTypes.func.isRequired, - keys: PropTypes.array.isRequired, - className: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number, - order: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), - offset: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), - value: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - children: PropTypes.func, +export type BarStackHorizontalProps = Pick< + BarStackProps, + | 'data' + | 'className' + | 'top' + | 'left' + | 'keys' + | 'order' + | 'offset' + | 'value' + | 'xScale' + | 'yScale' + | 'color' + | 'children' +> & { + /** Returns the value mapped to the x0 of a bar. */ + x0?: (d: SeriesPoint) => number; + /** Returns the value mapped to the x1 of a bar. */ + x1?: (d: SeriesPoint) => number; + /** Returns the value mapped to the y of a bar. */ + y: (d: Datum) => number; }; -export default function BarStackHorizontal({ +export default function BarStackHorizontal({ data, className, top, left, y, - x0 = d => d[0], - x1 = d => d[1], + x0 = (d: $TSFIXME) => d && d[0], + x1 = (d: $TSFIXME) => d && d[1], xScale, yScale, color, @@ -43,10 +51,11 @@ export default function BarStackHorizontal({ offset, children, ...restProps -}) { - const stack = d3stack(); +}: BarStackHorizontalProps & + Omit, keyof BarStackHorizontalProps>) { + const stack = d3stack(); if (keys) stack.keys(keys); - if (value) stack.value(value); + if (value) setNumOrAccessor>(stack.value, value); if (order) stack.order(stackOrder(order)); if (offset) stack.offset(stackOffset(offset)); @@ -54,9 +63,10 @@ export default function BarStackHorizontal({ const yRange = yScale.range(); const yDomain = yScale.domain(); - const barHeight = objHasMethod(yScale, 'bandwidth') - ? yScale.bandwidth() - : Math.abs(yRange[yRange.length - 1] - yRange[0]) / yDomain.length; + const barHeight = + 'bandwidth' in yScale && typeof yScale.bandwidth === 'function' + ? yScale.bandwidth() + : Math.abs(yRange[yRange.length - 1] - yRange[0]) / yDomain.length; const barStacks = stacks.map((barStack, i) => { const { key } = barStack; @@ -66,9 +76,10 @@ export default function BarStackHorizontal({ bars: barStack.map((bar, j) => { const barWidth = xScale(x1(bar)) - xScale(x0(bar)); const barX = xScale(x0(bar)); - const barY = objHasMethod(yScale, 'bandwidth') - ? yScale(y(bar.data)) - : Math.max(yScale(y(bar.data)) - barWidth / 2); + const barY = + 'bandwidth' in yScale && typeof yScale.bandwidth === 'function' + ? yScale(y(bar.data)) + : Math.max(yScale(y(bar.data)) - barWidth / 2); return { bar, key, @@ -83,7 +94,7 @@ export default function BarStackHorizontal({ }; }); - if (children) return children(barStacks); + if (children) return <>{children(barStacks)}; return ( diff --git a/packages/vx-shape/src/shapes/Circle.jsx b/packages/vx-shape/src/shapes/Circle.jsx deleted file mode 100644 index 7e1382bf5..000000000 --- a/packages/vx-shape/src/shapes/Circle.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; - -Circle.propTypes = { - className: PropTypes.string, - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), -}; - -export default function Circle({ className, innerRef, ...restProps }) { - return ; -} diff --git a/packages/vx-shape/src/shapes/Circle.tsx b/packages/vx-shape/src/shapes/Circle.tsx new file mode 100644 index 000000000..9b8480518 --- /dev/null +++ b/packages/vx-shape/src/shapes/Circle.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import cx from 'classnames'; + +export type CircleProps = { + /** className to apply to circle element. */ + className?: string; + /** reference to circle element. */ + innerRef?: React.Ref; +}; + +export default function Circle({ + className, + innerRef, + ...restProps +}: CircleProps & Omit, keyof CircleProps>) { + return ; +} diff --git a/packages/vx-shape/src/shapes/Line.jsx b/packages/vx-shape/src/shapes/Line.jsx deleted file mode 100644 index 9ac3b41d1..000000000 --- a/packages/vx-shape/src/shapes/Line.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { Point } from '@vx/point'; - -Line.propTypes = { - className: PropTypes.string, - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - fill: PropTypes.string, - from: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }), - to: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }), -}; - -export default function Line({ - from = new Point({ x: 0, y: 0 }), - to = new Point({ x: 1, y: 1 }), - fill = 'transparent', - className = '', - innerRef, - ...restProps -}) { - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/Line.tsx b/packages/vx-shape/src/shapes/Line.tsx new file mode 100644 index 000000000..a5242fb89 --- /dev/null +++ b/packages/vx-shape/src/shapes/Line.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import cx from 'classnames'; + +interface Point { + x?: number; + y?: number; +} + +export type LineProps = { + /** className to apply to line element. */ + className?: string; + /** reference to line element. */ + innerRef?: React.Ref; + /** fill color applied to line element. */ + fill?: string; + /** Starting x,y point of the line. */ + from?: Point; + /** Ending x,y point of the line. */ + to?: Point; +}; + +export default function Line({ + from = { x: 0, y: 0 }, + to = { x: 1, y: 1 }, + fill = 'transparent', + className, + innerRef, + ...restProps +}: LineProps & Omit, keyof LineProps>) { + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/LinePath.jsx b/packages/vx-shape/src/shapes/LinePath.jsx deleted file mode 100644 index 21446aadd..000000000 --- a/packages/vx-shape/src/shapes/LinePath.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { line } from 'd3-shape'; - -LinePath.propTypes = { - data: PropTypes.array, - curve: PropTypes.func, - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - defined: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), - x: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - children: PropTypes.func, - fill: PropTypes.string, - className: PropTypes.string, -}; - -export default function LinePath({ - children, - data, - x, - y, - fill = 'transparent', - className, - curve, - innerRef, - defined = () => true, - ...restProps -}) { - const path = line(); - if (x) path.x(x); - if (y) path.y(y); - if (defined) path.defined(defined); - if (curve) path.curve(curve); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/LinePath.tsx b/packages/vx-shape/src/shapes/LinePath.tsx new file mode 100644 index 000000000..dbe028fd4 --- /dev/null +++ b/packages/vx-shape/src/shapes/LinePath.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import cx from 'classnames'; +import { line, Line as LineType, CurveFactory } from 'd3-shape'; + +export type LinePathProps = { + /** Array of data for which to generate a line shape. */ + data?: Datum[]; + /** 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; + /** The defined accessor for the shape. The final line shape includes all points for which this function returns true. By default all points are defined. */ + defined?: (datum: Datum, index: number, data: Datum[]) => boolean; + /** Given a datum, returns the x value. Defaults to d[0]. */ + x?: (datum: Datum, index: number, data: Datum[]) => number; + /** Given a datum, returns the y value. Defaults to d[1]. */ + y?: (datum: Datum, index: number, data: Datum[]) => number; + /** Override render function which is passed the configured path generator as input. */ + children?: (args: { path: LineType }) => React.ReactNode; + /** Fill color of the path element. */ + fill?: string; + /** className applied to path element. */ + className?: string; +}; + +export default function LinePath({ + children, + data = [], + x, + y, + fill = 'transparent', + className, + curve, + innerRef, + defined = () => true, + ...restProps +}: LinePathProps & Omit, keyof LinePathProps>) { + const path = line(); + if (x) path.x(x); + if (y) path.y(y); + if (defined) path.defined(defined); + if (curve) path.curve(curve); + if (children) return <>{children({ path })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/LineRadial.jsx b/packages/vx-shape/src/shapes/LineRadial.jsx deleted file mode 100644 index bcfc17445..000000000 --- a/packages/vx-shape/src/shapes/LineRadial.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { radialLine } from 'd3-shape'; - -LineRadial.propTypes = { - className: PropTypes.string, - children: PropTypes.func, - curve: PropTypes.func, - data: PropTypes.any, - defined: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), - fill: PropTypes.string, - angle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - radius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), -}; - -export default function LineRadial({ - className, - angle, - radius, - defined, - curve, - data, - innerRef, - children, - fill = 'transparent', - ...restProps -}) { - const path = radialLine(); - if (angle) path.angle(angle); - if (radius) path.radius(radius); - if (defined) path.defined(defined); - if (curve) path.curve(curve); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/LineRadial.tsx b/packages/vx-shape/src/shapes/LineRadial.tsx new file mode 100644 index 000000000..3b5496e52 --- /dev/null +++ b/packages/vx-shape/src/shapes/LineRadial.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import cx from 'classnames'; +import { radialLine, RadialLine } from 'd3-shape'; +import { LinePathProps } from './LinePath'; + +export type LineRadialProps = Pick< + LinePathProps, + 'className' | 'curve' | 'data' | 'defined' | 'fill' | 'innerRef' +> & { + /** Override render function which is passed the configured path generator as input. */ + children?: (args: { path: RadialLine }) => React.ReactNode; + /** Returns the angle value in radians for a given Datum, with 0 at -y (12 o’clock). */ + angle?: (datum: Datum, index: number, data: Datum[]) => number; + /** Returns the radius value in radians for a given Datum, with 0 at the center. */ + radius?: (datum: Datum, index: number, data: Datum[]) => number; +}; + +export default function LineRadial({ + className, + angle, + radius, + defined, + curve, + data = [], + innerRef, + children, + fill = 'transparent', + ...restProps +}: LineRadialProps & Omit, keyof LineRadialProps>) { + const path = radialLine(); + if (angle) path.angle(angle); + if (radius) path.radius(radius); + if (defined) path.defined(defined); + if (curve) path.curve(curve); + if (children) return <>{children({ path })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/Pie.jsx b/packages/vx-shape/src/shapes/Pie.jsx deleted file mode 100644 index 8d21a06e5..000000000 --- a/packages/vx-shape/src/shapes/Pie.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { Group } from '@vx/group'; -import { arc as d3Arc, pie as d3Pie } from 'd3-shape'; - -Pie.propTypes = { - className: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number, - data: PropTypes.array, - centroid: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - innerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - outerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - cornerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - startAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - endAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - padAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - padRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - pieValue: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - pieSort: PropTypes.func, - pieSortValues: PropTypes.func, - children: PropTypes.func, -}; - -export default function Pie({ - className, - top, - left, - data, - centroid, - innerRadius = 0, - outerRadius, - cornerRadius, - startAngle, - endAngle, - padAngle, - padRadius, - pieSort, - pieSortValues, - pieValue, - children, - ...restProps -}) { - const path = d3Arc(); - if (innerRadius != null) path.innerRadius(innerRadius); - if (outerRadius != null) path.outerRadius(outerRadius); - if (cornerRadius != null) path.cornerRadius(cornerRadius); - if (padRadius != null) path.padRadius(padRadius); - - const pie = d3Pie(); - if (pieSort !== undefined) pie.sort(pieSort); - if (pieSortValues !== undefined) pie.sortValues(pieSortValues); - if (pieValue !== undefined) pie.value(pieValue); - if (padAngle != null) pie.padAngle(padAngle); - if (startAngle != null) pie.startAngle(startAngle); - if (endAngle != null) pie.endAngle(endAngle); - - const arcs = pie(data); - - if (children) return children({ arcs, path, pie }); - - return ( - - {arcs.map((arc, i) => { - return ( - - - {centroid && centroid(path.centroid(arc), arc)} - - ); - })} - - ); -} diff --git a/packages/vx-shape/src/shapes/Pie.tsx b/packages/vx-shape/src/shapes/Pie.tsx new file mode 100644 index 000000000..3ea4f4a7d --- /dev/null +++ b/packages/vx-shape/src/shapes/Pie.tsx @@ -0,0 +1,99 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +import React from 'react'; +import cx from 'classnames'; +import { Group } from '@vx/group'; +import { arc as d3Arc, Arc as ArcType, PieArcDatum, pie as d3Pie, Pie as PieType } from 'd3-shape'; +import setNumOrAccessor, { NumberAccessor as NumAccessor } from '../util/setNumberOrNumberAccessor'; +import { $TSFIXME } from '../types'; + +export type PieProps = { + /** className applied to path element. */ + className?: string; + /** Top offset of rendered Pie. */ + top?: number; + /** Left offset of rendered Pie. */ + left?: number; + /** Array of data to generate a Pie for. */ + data?: Datum[]; + /** Invoked for each datum, returns the value for a given Pie segment/arc datum. */ + pieValue?: NumAccessor; + /** Comparator function to sort *arcs*, overridden by pieSortValues if defined. If pieSort and pieSortValues are null, arcs match input data order. */ + pieSort?: null | ((a: Datum, b: Datum) => number); + /** Comparator function to sort arc *values*, overrides pieSort if defined. If pieSort and pieSortValues are null, arcs match input data order. */ + pieSortValues?: null | ((a: number, b: number) => number); + /** Optional render function invoked for each Datum to render something (e.g., a Label) at each pie centroid. */ + centroid?: (xyCoords: [number, number], arc: PieArcDatum) => React.ReactNode; + /** Inner radius of the Arc shape. */ + innerRadius?: NumAccessor | number; + /** Inner radius of the Arc shape. */ + outerRadius?: NumAccessor | number; + /** Inner radius of the Arc shape. */ + cornerRadius?: NumAccessor | number; + /** Padding radius of the Arc shape, which determines the fixed linear distance separating adjacent arcs. */ + padRadius?: NumAccessor | number; + /** Returns the start angle of the overall Pie shape (the first value starts at startAngle), with 0 at -y (12 o’clock) and positive angles proceeding clockwise. */ + startAngle?: NumAccessor | number; + /** Returns the end angle of the overall Pie shape (the last value ends at endAngle), with 0 at -y (12 o’clock) and positive angles proceeding clockwise. */ + endAngle?: NumAccessor | number; + /** Padding angle of the Pie shape, which sets a fixed linear distance separating adjacent arcs. */ + padAngle?: NumAccessor | number; + /** Render function override which is passed the configured arc generator as input. */ + children?: (args: { + path: ArcType<$TSFIXME, PieArcDatum>; + arcs: PieArcDatum[]; + pie: PieType<$TSFIXME, Datum>; + }) => React.ReactNode; +}; + +export default function Pie({ + className, + top, + left, + data = [], + centroid, + innerRadius = 0, + outerRadius, + cornerRadius, + startAngle, + endAngle, + padAngle, + padRadius, + pieSort, + pieSortValues, + pieValue, + children, + ...restProps +}: PieProps & Omit, keyof PieProps>) { + const path = d3Arc>(); + + if (innerRadius != null) setNumOrAccessor>(path.innerRadius, innerRadius); + if (outerRadius != null) setNumOrAccessor>(path.outerRadius, outerRadius); + if (cornerRadius != null) setNumOrAccessor>(path.cornerRadius, cornerRadius); + if (padRadius != null) setNumOrAccessor>(path.padRadius, padRadius); + + const pie = d3Pie(); + // ts can't distinguish between these method overloads + if (pieSort === null) pie.sort(pieSort); + else if (pieSort != null) pie.sort(pieSort); + if (pieSortValues === null) pie.sortValues(pieSortValues); + else if (pieSortValues != null) pie.sortValues(pieSortValues); + + if (pieValue != null) pie.value(pieValue); + if (padAngle != null) setNumOrAccessor>(pie.padAngle, padAngle); + if (startAngle != null) setNumOrAccessor>(pie.startAngle, startAngle); + if (endAngle != null) setNumOrAccessor>(pie.endAngle, endAngle); + + const arcs = pie(data); + if (children) return <>{children({ arcs, path, pie })}; + + return ( + + {arcs.map((arc, i) => ( + + + {centroid && centroid(path.centroid(arc), arc)} + + ))} + + ); +} diff --git a/packages/vx-shape/src/shapes/Polygon.jsx b/packages/vx-shape/src/shapes/Polygon.jsx deleted file mode 100644 index 12098b133..000000000 --- a/packages/vx-shape/src/shapes/Polygon.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { Point } from '@vx/point'; -import { degreesToRadians } from '../util/trigonometry'; - -Polygon.propTypes = { - sides: PropTypes.number.isRequired, - size: PropTypes.number.isRequired, - className: PropTypes.string, - rotate: PropTypes.number, - children: PropTypes.func, - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - center: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }), -}; - -export const getPoint = ({ sides, size, center, rotate, side }) => { - const degrees = (360 / sides) * side - rotate; - const radians = degreesToRadians(degrees); - - return new Point({ - x: center.x + size * Math.cos(radians), - y: center.y + size * Math.sin(radians), - }); -}; - -export const getPoints = ({ sides, size, center, rotate }) => - new Array(sides).fill(0).map((_, side) => - getPoint({ - sides, - size, - center, - rotate, - side, - }), - ); - -export default function Polygon({ - sides, - size = 25, - center = new Point({ x: 0, y: 0 }), - rotate = 0, - className, - children, - innerRef, - ...restProps -}) { - const points = getPoints({ - sides, - size, - center, - rotate, - }).map(p => p.toArray()); - - if (children) return children({ points }); - - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/Polygon.tsx b/packages/vx-shape/src/shapes/Polygon.tsx new file mode 100644 index 000000000..bc5df4427 --- /dev/null +++ b/packages/vx-shape/src/shapes/Polygon.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import cx from 'classnames'; +import { degreesToRadians } from '../util/trigonometry'; + +const DEFAULT_CENTER = { x: 0, y: 0 }; + +export const getPoint = ({ + sides, + size, + center = DEFAULT_CENTER, + rotate = 0, + side, +}: { side: number } & Pick) => { + const degrees = (360 / sides) * side - rotate; + const radians = degreesToRadians(degrees); + + return { + x: center.x + size * Math.cos(radians), + y: center.y + size * Math.sin(radians), + }; +}; + +export const getPoints = ({ + sides, + size, + center, + rotate, +}: Pick) => + new Array(sides).fill(0).map((_, side) => + getPoint({ + sides, + size, + center, + rotate, + side, + }), + ); + +export type PolygonProps = { + /** Number of polygon sides. */ + sides: number; + /** Size of the shape. */ + size: number; + /** className to apply to polygon element. */ + className?: string; + /** Rotation transform to apply to polygon. */ + rotate?: number; + /** Render function override which is passed the generated polygon points. */ + children?: (args: { points: [number, number][] }) => React.ReactNode; + /** Reference to polygon element. */ + innerRef?: React.Ref; + /** Polygon center position. */ + center?: { + x: number; + y: number; + }; +}; + +export default function Polygon({ + sides, + size = 25, + center = DEFAULT_CENTER, + rotate = 0, + className, + children, + innerRef, + ...restProps +}: PolygonProps & Omit, keyof PolygonProps>) { + const points: [number, number][] = getPoints({ + sides, + size, + center, + rotate, + }).map(({ x, y }) => [x, y]); + + if (children) return <>{children({ points })}; + + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/Stack.jsx b/packages/vx-shape/src/shapes/Stack.jsx deleted file mode 100644 index 321837afc..000000000 --- a/packages/vx-shape/src/shapes/Stack.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { Group } from '@vx/group'; -import { area, stack as d3stack } from 'd3-shape'; -import stackOrder from '../util/stackOrder'; -import stackOffset from '../util/stackOffset'; - -Stack.propTypes = { - data: PropTypes.array.isRequired, - className: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number, - curve: PropTypes.func, - color: PropTypes.func, - keys: PropTypes.array, - children: PropTypes.func, - x: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - x0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - x1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - y1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - value: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - defined: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), - order: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), - offset: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), -}; - -export default function Stack({ - className, - top, - left, - keys, - data, - curve, - defined, - x, - x0, - x1, - y0, - y1, - value, - order, - offset, - color, - children, - ...restProps -}) { - const stack = d3stack(); - if (keys) stack.keys(keys); - if (value) stack.value(value); - if (order) stack.order(stackOrder(order)); - if (offset) stack.offset(stackOffset(offset)); - - const path = area(); - if (x) path.x(x); - if (x0) path.x0(x0); - if (x1) path.x1(x1); - if (y0) path.y0(y0); - if (y1) path.y1(y1); - if (curve) path.curve(curve); - if (defined) path.defined(defined); - - const stacks = stack(data); - - if (children) return children({ stacks, path, stack }); - - return ( - - {stacks.map((series, i) => { - return ( - - ); - })} - - ); -} diff --git a/packages/vx-shape/src/shapes/Stack.tsx b/packages/vx-shape/src/shapes/Stack.tsx new file mode 100644 index 000000000..136cf1685 --- /dev/null +++ b/packages/vx-shape/src/shapes/Stack.tsx @@ -0,0 +1,115 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +import React from 'react'; +import cx from 'classnames'; +import { Group } from '@vx/group'; +import { + area, + Area as AreaType, + stack as d3stack, + Stack as StackType, + CurveFactory, + SeriesPoint, + Series, +} from 'd3-shape'; + +import setNumOrAccessor from '../util/setNumberOrNumberAccessor'; +import stackOrder, { STACK_ORDERS } from '../util/stackOrder'; +import stackOffset, { STACK_OFFSETS } from '../util/stackOffset'; +import { StackKey, $TSFIXME } from '../types'; + +export type NumAccessor = (datum: Datum, index: number, data: Datum[]) => number; + +export type StackProps = { + /** Array of data for which to generate a stack. */ + data: Datum[]; + /** className applied to path element. */ + className?: string; + /** Top offset of rendered Stack. */ + top?: number; + /** Left offset of rendered Stack. */ + left?: number; + /** Sets the curve factory (from @vx/curve or d3-curve) for the area generator. Defaults to curveLinear. */ + curve?: CurveFactory; + /** Returns a color for a given stack key and index. */ + color?: (key: StackKey, index: number) => string; + /** Array of keys corresponding to stack layers. */ + keys?: StackKey[]; + /** Override render function which is passed the configured arc generator as input. */ + children?: (args: { + stacks: Series[]; + path: AreaType>; + stack: StackType<$TSFIXME, Datum, StackKey>; + }) => React.ReactNode; + /** Sets the x0 accessor function, and sets x1 to null. */ + x?: NumAccessor>; + /** Specifies the x0 accessor function which defaults to d => d[0]. */ + x0?: NumAccessor>; + /** Specifies the x1 accessor function which defaults to null. */ + x1?: NumAccessor>; + /** Specifies the y0 accessor function which defaults to d => 0. */ + y0?: NumAccessor>; + /** Specifies the y1 accessor function which defaults to d => d[1]. */ + y1?: NumAccessor>; + /** Sets the value accessor for a Datum, which defaults to d[key]. */ + value?: number | ((d: Datum, key: StackKey) => number); + /** 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: SeriesPoint, index: number, data: SeriesPoint[]) => boolean; + /** Sets the stack order to the pre-defined d3 function, see https://github.com/d3/d3-shape#stack_order. */ + order?: keyof typeof STACK_ORDERS; + /** Sets the stack offset to the pre-defined d3 offset, see https://github.com/d3/d3-shape#stack_offset. */ + offset?: keyof typeof STACK_OFFSETS; +}; + +export default function Stack({ + className, + top, + left, + keys, + data, + curve, + defined, + x, + x0, + x1, + y0, + y1, + value, + order, + offset, + color, + children, + ...restProps +}: StackProps & Omit, keyof StackProps>) { + const stack = d3stack(); + if (keys) stack.keys(keys); + if (value) setNumOrAccessor(stack.value, value); + if (order) stack.order(stackOrder(order)); + if (offset) stack.offset(stackOffset(offset)); + + const path = area>(); + if (x) path.x(x); + if (x0) path.x0(x0); + if (x1) path.x1(x1); + if (y0) path.y0(y0); + if (y1) path.y1(y1); + if (curve) path.curve(curve); + if (defined) path.defined(defined); + + const stacks = stack(data); + + if (children) return <>{children({ stacks, path, stack })}; + + return ( + + {stacks.map((series, i) => ( + + ))} + + ); +} diff --git a/packages/vx-shape/src/shapes/link/curve/LinkHorizontalCurve.jsx b/packages/vx-shape/src/shapes/link/curve/LinkHorizontalCurve.jsx deleted file mode 100644 index 170848996..000000000 --- a/packages/vx-shape/src/shapes/link/curve/LinkHorizontalCurve.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { path as d3Path } from 'd3-path'; - -export function pathHorizontalCurve({ source, target, x, y, percent }) { - return data => { - const sourceData = source(data); - const targetData = target(data); - - const sx = x(sourceData); - const sy = y(sourceData); - const tx = x(targetData); - const ty = y(targetData); - - const dx = tx - sx; - const dy = ty - sy; - const ix = percent * (dx + dy); - const iy = percent * (dy - dx); - - const path = d3Path(); - path.moveTo(sx, sy); - path.bezierCurveTo(sx + ix, sy + iy, tx + iy, ty - ix, tx, ty); - - return path.toString(); - }; -} - -LinkHorizontalCurve.propTypes = { - className: PropTypes.string, - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - percent: PropTypes.number, - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - path: PropTypes.func, - children: PropTypes.func, - data: PropTypes.any, -}; - -export default function LinkHorizontalCurve({ - className, - innerRef, - data, - path, - x = d => d.y, - y = d => d.x, - source = d => d.source, - target = d => d.target, - percent = 0.2, - children, - ...restProps -}) { - const pathGen = path || pathHorizontalCurve({ source, target, x, y, percent }); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/link/curve/LinkHorizontalCurve.tsx b/packages/vx-shape/src/shapes/link/curve/LinkHorizontalCurve.tsx new file mode 100644 index 000000000..84810c820 --- /dev/null +++ b/packages/vx-shape/src/shapes/link/curve/LinkHorizontalCurve.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import cx from 'classnames'; +import { path as d3Path } from 'd3-path'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; + +export function pathHorizontalCurve({ + source, + target, + x, + y, + percent, +}: Required> & { percent: number }) { + return (link: Link) => { + const sourceData = source(link); + const targetData = target(link); + + const sx = x(sourceData); + const sy = y(sourceData); + const tx = x(targetData); + const ty = y(targetData); + + const dx = tx - sx; + const dy = ty - sy; + const ix = percent * (dx + dy); + const iy = percent * (dy - dx); + + const path = d3Path(); + path.moveTo(sx, sy); + path.bezierCurveTo(sx + ix, sy + iy, tx + iy, ty - ix, tx, ty); + + return path.toString(); + }; +} + +export type LinkHorizontalCurveProps = AccessorProps & + SharedLinkProps & { + percent?: number; + }; + +export default function LinkHorizontalCurve({ + className, + children, + data, + innerRef, + path, + percent = 0.2, + x = (n: $TSFIXME) => n && n.y, // note this returns a y value + y = (n: $TSFIXME) => n && n.x, // note this returns an x value + source = (l: $TSFIXME) => l && l.source, + target = (l: $TSFIXME) => l && l.target, + ...restProps +}: LinkHorizontalCurveProps & + Omit, keyof LinkHorizontalCurveProps>) { + const pathGen = path || pathHorizontalCurve({ source, target, x, y, percent }); + if (children) return <>{children({ path: pathGen })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/link/curve/LinkRadialCurve.jsx b/packages/vx-shape/src/shapes/link/curve/LinkRadialCurve.tsx similarity index 54% rename from packages/vx-shape/src/shapes/link/curve/LinkRadialCurve.jsx rename to packages/vx-shape/src/shapes/link/curve/LinkRadialCurve.tsx index b0c44bada..50406959e 100644 --- a/packages/vx-shape/src/shapes/link/curve/LinkRadialCurve.jsx +++ b/packages/vx-shape/src/shapes/link/curve/LinkRadialCurve.tsx @@ -1,12 +1,18 @@ import React from 'react'; import cx from 'classnames'; -import PropTypes from 'prop-types'; import { path as d3Path } from 'd3-path'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; -export function pathRadialCurve({ source, target, x, y, percent }) { - return data => { - const sourceData = source(data); - const targetData = target(data); +export function pathRadialCurve({ + source, + target, + x, + y, + percent, +}: Required> & { percent: number }) { + return (link: Link) => { + const sourceData = source(link); + const targetData = target(link); const sa = x(sourceData) - Math.PI / 2; const sr = y(sourceData); @@ -36,39 +42,32 @@ export function pathRadialCurve({ source, target, x, y, percent }) { }; } -LinkRadialCurve.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - percent: PropTypes.number, - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - path: PropTypes.func, - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, -}; +export type LinkRadialCurveProps = { + percent?: number; +} & AccessorProps & + SharedLinkProps; -export default function LinkRadialCurve({ +export default function LinkRadialCurve({ className, - innerRef, + children, data, + innerRef, path, - x = d => d.x, - y = d => d.y, - source = d => d.source, - target = d => d.target, percent = 0.2, - children, + x = (n: $TSFIXME) => n && n.x, + y = (n: $TSFIXME) => n && n.y, + source = (l: $TSFIXME) => l && l.source, + target = (l: $TSFIXME) => l && l.target, ...restProps -}) { +}: LinkRadialCurveProps & + Omit, keyof LinkRadialCurveProps>) { const pathGen = path || pathRadialCurve({ source, target, x, y, percent }); - if (children) return children({ path }); + if (children) return <>{children({ path: pathGen })}; return ( ); diff --git a/packages/vx-shape/src/shapes/link/curve/LinkVerticalCurve.jsx b/packages/vx-shape/src/shapes/link/curve/LinkVerticalCurve.jsx deleted file mode 100644 index 5d9a3b6a7..000000000 --- a/packages/vx-shape/src/shapes/link/curve/LinkVerticalCurve.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { path as d3Path } from 'd3-path'; - -export function pathVerticalCurve({ source, target, x, y, percent }) { - return data => { - const sourceData = source(data); - const targetData = target(data); - - const sx = x(sourceData); - const sy = y(sourceData); - const tx = x(targetData); - const ty = y(targetData); - - const dx = tx - sx; - const dy = ty - sy; - const ix = percent * (dx + dy); - const iy = percent * (dy - dx); - - const path = d3Path(); - path.moveTo(sx, sy); - path.bezierCurveTo(sx + ix, sy + iy, tx + iy, ty - ix, tx, ty); - - return path.toString(); - }; -} - -LinkVerticalCurve.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - percent: PropTypes.number, - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - path: PropTypes.func, - children: PropTypes.func, - data: PropTypes.any, - className: PropTypes.string, -}; - -export default function LinkVerticalCurve({ - className, - innerRef, - data, - path, - x = d => d.x, - y = d => d.y, - source = d => d.source, - target = d => d.target, - percent = 0.2, - children, - ...restProps -}) { - const pathGen = path || pathVerticalCurve({ source, target, x, y, percent }); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/link/curve/LinkVerticalCurve.tsx b/packages/vx-shape/src/shapes/link/curve/LinkVerticalCurve.tsx new file mode 100644 index 000000000..e6b388465 --- /dev/null +++ b/packages/vx-shape/src/shapes/link/curve/LinkVerticalCurve.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import cx from 'classnames'; +import { path as d3Path } from 'd3-path'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; + +export function pathVerticalCurve({ + source, + target, + x, + y, + percent, +}: Required> & { percent: number }) { + return (link: Link) => { + const sourceData = source(link); + const targetData = target(link); + + const sx = x(sourceData); + const sy = y(sourceData); + const tx = x(targetData); + const ty = y(targetData); + + const dx = tx - sx; + const dy = ty - sy; + const ix = percent * (dx + dy); + const iy = percent * (dy - dx); + + const path = d3Path(); + path.moveTo(sx, sy); + path.bezierCurveTo(sx + ix, sy + iy, tx + iy, ty - ix, tx, ty); + + return path.toString(); + }; +} + +export type LinkVerticalCurveProps = { + percent?: number; +} & AccessorProps & + SharedLinkProps; + +export default function LinkVerticalCurve({ + className, + children, + data, + innerRef, + path, + percent = 0.2, + x = (n: $TSFIXME) => n && n.x, + y = (n: $TSFIXME) => n && n.y, + source = (l: $TSFIXME) => l && l.source, + target = (l: $TSFIXME) => l && l.target, + ...restProps +}: LinkVerticalCurveProps & + Omit, keyof LinkVerticalCurveProps>) { + const pathGen = path || pathVerticalCurve({ source, target, x, y, percent }); + if (children) return <>{children({ path: pathGen })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/link/diagonal/LinkHorizontal.jsx b/packages/vx-shape/src/shapes/link/diagonal/LinkHorizontal.jsx deleted file mode 100644 index 92bd3c31b..000000000 --- a/packages/vx-shape/src/shapes/link/diagonal/LinkHorizontal.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { linkHorizontal } from 'd3-shape'; - -export function pathHorizontalDiagonal({ source, target, x, y }) { - return data => { - const link = linkHorizontal(); - link.x(x); - link.y(y); - link.source(source); - link.target(target); - return link(data); - }; -} - -LinkHorizontal.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - path: PropTypes.func, - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, -}; - -export default function LinkHorizontal({ - className, - innerRef, - data, - path, - x = d => d.y, - y = d => d.x, - source = d => d.source, - target = d => d.target, - children, - ...restProps -}) { - const pathGen = path || pathHorizontalDiagonal({ source, target, x, y }); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/link/diagonal/LinkHorizontal.tsx b/packages/vx-shape/src/shapes/link/diagonal/LinkHorizontal.tsx new file mode 100644 index 000000000..4751dbfe8 --- /dev/null +++ b/packages/vx-shape/src/shapes/link/diagonal/LinkHorizontal.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import cx from 'classnames'; +import { linkHorizontal } from 'd3-shape'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; + +export function pathHorizontalDiagonal({ + source, + target, + x, + y, +}: Required>) { + return (data: Link) => { + const link = linkHorizontal(); + link.x(x); + link.y(y); + link.source(source); + link.target(target); + return link(data); + }; +} + +export type LinkHorizontalDiagonalProps = AccessorProps & + SharedLinkProps; + +export default function LinkHorizontalDiagonal({ + className, + children, + data, + innerRef, + path, + x = (n: $TSFIXME) => n && n.y, // note this returns a y value + y = (n: $TSFIXME) => n && n.x, // note this returns an x value + source = (l: $TSFIXME) => l && l.source, + target = (l: $TSFIXME) => l && l.target, + ...restProps +}: LinkHorizontalDiagonalProps & + Omit, keyof LinkHorizontalDiagonalProps>) { + const pathGen = path || pathHorizontalDiagonal({ source, target, x, y }); + if (children) return <>{children({ path: pathGen })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/link/diagonal/LinkRadial.jsx b/packages/vx-shape/src/shapes/link/diagonal/LinkRadial.jsx deleted file mode 100644 index d2507566f..000000000 --- a/packages/vx-shape/src/shapes/link/diagonal/LinkRadial.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { linkRadial } from 'd3-shape'; - -export function pathRadialDiagonal({ source, target, angle, radius }) { - return data => { - const link = linkRadial(); - link.angle(angle); - link.radius(radius); - link.source(source); - link.target(target); - return link(data); - }; -} - -LinkRadial.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - angle: PropTypes.func, - radius: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - path: PropTypes.func, - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, -}; - -export default function LinkRadial({ - className, - innerRef, - data, - path, - angle = d => d.x, - radius = d => d.y, - source = d => d.source, - target = d => d.target, - children, - ...restProps -}) { - const pathGen = path || pathRadialDiagonal({ source, target, angle, radius }); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/link/diagonal/LinkRadial.tsx b/packages/vx-shape/src/shapes/link/diagonal/LinkRadial.tsx new file mode 100644 index 000000000..b7bd3e072 --- /dev/null +++ b/packages/vx-shape/src/shapes/link/diagonal/LinkRadial.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import cx from 'classnames'; +import { linkRadial } from 'd3-shape'; +import { SharedLinkProps, RadialAccessorProps, $TSFIXME } from '../../../types'; + +export function pathRadialDiagonal({ + source, + target, + angle, + radius, +}: Required>) { + return (data: Link) => { + const link = linkRadial(); + link.angle(angle); + link.radius(radius); + link.source(source); + link.target(target); + return link(data); + }; +} + +type LinkRadialDiagonalProps = { + angle: (node: Node) => number; + radius: (node: Node) => number; +} & RadialAccessorProps & + SharedLinkProps; + +export default function LinkRadialDiagonal({ + className, + children, + data, + innerRef, + path, + angle = (n: $TSFIXME) => n.x, + radius = (n: $TSFIXME) => n.y, + source = (n: $TSFIXME) => n.source, + target = (n: $TSFIXME) => n.target, + ...restProps +}: LinkRadialDiagonalProps & + Omit, keyof LinkRadialDiagonalProps>) { + const pathGen = path || pathRadialDiagonal({ source, target, angle, radius }); + if (children) return <>{children({ path: pathGen })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/link/diagonal/LinkVertical.jsx b/packages/vx-shape/src/shapes/link/diagonal/LinkVertical.jsx deleted file mode 100644 index 0f3d3bb4d..000000000 --- a/packages/vx-shape/src/shapes/link/diagonal/LinkVertical.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { linkVertical } from 'd3-shape'; - -export function pathVerticalDiagonal({ source, target, x, y }) { - return data => { - const link = linkVertical(); - link.x(x); - link.y(y); - link.source(source); - link.target(target); - return link(data); - }; -} - -LinkVertical.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - path: PropTypes.func, - className: PropTypes.string, - children: PropTypes.func, - data: PropTypes.any, -}; - -export default function LinkVertical({ - className, - innerRef, - data, - path, - x = d => d.x, - y = d => d.y, - source = d => d.source, - target = d => d.target, - children, - ...restProps -}) { - const pathGen = path || pathVerticalDiagonal({ source, target, x, y }); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/link/diagonal/LinkVertical.tsx b/packages/vx-shape/src/shapes/link/diagonal/LinkVertical.tsx new file mode 100644 index 000000000..5e36f69e2 --- /dev/null +++ b/packages/vx-shape/src/shapes/link/diagonal/LinkVertical.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import cx from 'classnames'; +import { linkVertical } from 'd3-shape'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; + +export function pathVerticalDiagonal({ + source, + target, + x, + y, +}: Required>) { + return (data: Link) => { + const link = linkVertical(); + link.x(x); + link.y(y); + link.source(source); + link.target(target); + return link(data); + }; +} + +type LinkVerticalDiagonalProps = AccessorProps & SharedLinkProps; + +export default function LinkVerticalDiagonal({ + className, + children, + data, + innerRef, + path, + x = (d: $TSFIXME) => d.x, + y = (d: $TSFIXME) => d.y, + source = (d: $TSFIXME) => d.source, + target = (d: $TSFIXME) => d.target, + ...restProps +}: LinkVerticalDiagonalProps & + Omit, keyof LinkVerticalDiagonalProps>) { + const pathGen = path || pathVerticalDiagonal({ source, target, x, y }); + if (children) return <>{children({ path: pathGen })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/link/line/LinkHorizontalLine.jsx b/packages/vx-shape/src/shapes/link/line/LinkHorizontalLine.jsx deleted file mode 100644 index fcf705474..000000000 --- a/packages/vx-shape/src/shapes/link/line/LinkHorizontalLine.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { path as d3Path } from 'd3-path'; - -export function pathHorizontalLine({ source, target, x, y }) { - return data => { - const sourceData = source(data); - const targetData = target(data); - - const sx = x(sourceData); - const sy = y(sourceData); - const tx = x(targetData); - const ty = y(targetData); - - const path = d3Path(); - path.moveTo(sx, sy); - path.lineTo(tx, ty); - - return path.toString(); - }; -} - -LinkHorizontalLine.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - path: PropTypes.func, - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, -}; - -export default function LinkHorizontalLine({ - className, - innerRef, - data, - path, - x = d => d.y, - y = d => d.x, - source = d => d.source, - target = d => d.target, - children, - ...restProps -}) { - const pathGen = path || pathHorizontalLine({ source, target, x, y }); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/link/line/LinkHorizontalLine.tsx b/packages/vx-shape/src/shapes/link/line/LinkHorizontalLine.tsx new file mode 100644 index 000000000..190871891 --- /dev/null +++ b/packages/vx-shape/src/shapes/link/line/LinkHorizontalLine.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import cx from 'classnames'; +import { path as d3Path } from 'd3-path'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; + +export function pathHorizontalLine({ + source, + target, + x, + y, +}: Required>) { + return (data: Link) => { + const sourceData = source(data); + const targetData = target(data); + + const sx = x(sourceData); + const sy = y(sourceData); + const tx = x(targetData); + const ty = y(targetData); + + const path = d3Path(); + path.moveTo(sx, sy); + path.lineTo(tx, ty); + + return path.toString(); + }; +} + +export type LinkHorizontalLineProps = AccessorProps & SharedLinkProps; + +export default function LinkHorizontalLine({ + className, + children, + innerRef, + data, + path, + x = (d: $TSFIXME) => d.y, // note this returns a y value + y = (d: $TSFIXME) => d.x, // note this returns a y value + source = (d: $TSFIXME) => d.source, + target = (d: $TSFIXME) => d.target, + ...restProps +}: LinkHorizontalLineProps & + Omit, keyof LinkHorizontalLineProps>) { + const pathGen = path || pathHorizontalLine({ source, target, x, y }); + if (children) return <>{children({ path: pathGen })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/link/line/LinkRadialLine.jsx b/packages/vx-shape/src/shapes/link/line/LinkRadialLine.tsx similarity index 54% rename from packages/vx-shape/src/shapes/link/line/LinkRadialLine.jsx rename to packages/vx-shape/src/shapes/link/line/LinkRadialLine.tsx index 367756a91..a41a0f175 100644 --- a/packages/vx-shape/src/shapes/link/line/LinkRadialLine.jsx +++ b/packages/vx-shape/src/shapes/link/line/LinkRadialLine.tsx @@ -1,10 +1,15 @@ import React from 'react'; import cx from 'classnames'; -import PropTypes from 'prop-types'; import { path as d3Path } from 'd3-path'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; -export function pathRadialLine({ source, target, x, y }) { - return data => { +export function pathRadialLine({ + source, + target, + x, + y, +}: Required>) { + return (data: Link) => { const sourceData = source(data); const targetData = target(data); @@ -26,37 +31,28 @@ export function pathRadialLine({ source, target, x, y }) { }; } -LinkRadialStep.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - path: PropTypes.func, - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, -}; +export type LinkRadialLineProps = AccessorProps & SharedLinkProps; -export default function LinkRadialStep({ +export default function LinkRadialLine({ className, innerRef, data, path, - x = d => d.x, - y = d => d.y, - source = d => d.source, - target = d => d.target, + x = (d: $TSFIXME) => d.x, + y = (d: $TSFIXME) => d.y, + source = (d: $TSFIXME) => d.source, + target = (d: $TSFIXME) => d.target, children, ...restProps -}) { +}: LinkRadialLineProps & + Omit, keyof LinkRadialLineProps>) { const pathGen = path || pathRadialLine({ source, target, x, y }); - if (children) return children({ path }); + if (children) return <>{children({ path: pathGen })}; return ( ); diff --git a/packages/vx-shape/src/shapes/link/line/LinkVerticalLine.jsx b/packages/vx-shape/src/shapes/link/line/LinkVerticalLine.jsx deleted file mode 100644 index bfc1912b3..000000000 --- a/packages/vx-shape/src/shapes/link/line/LinkVerticalLine.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { path as d3Path } from 'd3-path'; - -export function pathVerticalLine({ source, target, x, y }) { - return data => { - const sourceData = source(data); - const targetData = target(data); - - const sx = x(sourceData); - const sy = y(sourceData); - const tx = x(targetData); - const ty = y(targetData); - - const path = d3Path(); - path.moveTo(sx, sy); - path.lineTo(tx, ty); - - return path.toString(); - }; -} - -LinkVerticalLine.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - path: PropTypes.func, - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, -}; - -export default function LinkVerticalLine({ - className, - innerRef, - data, - path, - x = d => d.x, - y = d => d.y, - source = d => d.source, - target = d => d.target, - children, - ...restProps -}) { - const pathGen = path || pathVerticalLine({ source, target, x, y }); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/link/line/LinkVerticalLine.tsx b/packages/vx-shape/src/shapes/link/line/LinkVerticalLine.tsx new file mode 100644 index 000000000..bdfc39bb0 --- /dev/null +++ b/packages/vx-shape/src/shapes/link/line/LinkVerticalLine.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import cx from 'classnames'; +import { path as d3Path } from 'd3-path'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; + +export function pathVerticalLine({ + source, + target, + x, + y, +}: Required>) { + return (data: Link) => { + const sourceData = source(data); + const targetData = target(data); + + const sx = x(sourceData); + const sy = y(sourceData); + const tx = x(targetData); + const ty = y(targetData); + + const path = d3Path(); + path.moveTo(sx, sy); + path.lineTo(tx, ty); + + return path.toString(); + }; +} + +export type LinkVerticalLineProps = AccessorProps & SharedLinkProps; + +export default function LinkVerticalLine({ + className, + innerRef, + data, + path, + x = (d: $TSFIXME) => d.x, + y = (d: $TSFIXME) => d.y, + source = (d: $TSFIXME) => d.source, + target = (d: $TSFIXME) => d.target, + children, + ...restProps +}: LinkVerticalLineProps & + Omit, keyof LinkVerticalLineProps>) { + const pathGen = path || pathVerticalLine({ source, target, x, y }); + if (children) return <>{children({ path: pathGen })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/link/step/LinkHorizontalStep.jsx b/packages/vx-shape/src/shapes/link/step/LinkHorizontalStep.jsx deleted file mode 100644 index b56505c39..000000000 --- a/packages/vx-shape/src/shapes/link/step/LinkHorizontalStep.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { path as d3Path } from 'd3-path'; - -export function pathHorizontalStep({ source, target, x, y, percent }) { - return data => { - const sourceData = source(data); - const targetData = target(data); - - const sx = x(sourceData); - const sy = y(sourceData); - const tx = x(targetData); - const ty = y(targetData); - - const path = d3Path(); - path.moveTo(sx, sy); - path.lineTo(sx + (tx - sx) * percent, sy); - path.lineTo(sx + (tx - sx) * percent, ty); - path.lineTo(tx, ty); - - return path.toString(); - }; -} - -LinkHorizontalStep.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - percent: PropTypes.number, - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - path: PropTypes.func, - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, -}; - -export default function LinkHorizontalStep({ - className, - innerRef, - data, - path, - percent = 0.5, - x = d => d.y, - y = d => d.x, - source = d => d.source, - target = d => d.target, - children, - ...restProps -}) { - const pathGen = path || pathHorizontalStep({ source, target, x, y, percent }); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/link/step/LinkHorizontalStep.tsx b/packages/vx-shape/src/shapes/link/step/LinkHorizontalStep.tsx new file mode 100644 index 000000000..b416a6bf9 --- /dev/null +++ b/packages/vx-shape/src/shapes/link/step/LinkHorizontalStep.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import cx from 'classnames'; +import { path as d3Path } from 'd3-path'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; + +export function pathHorizontalStep({ + source, + target, + x, + y, + percent, +}: Required> & { percent: number }) { + return (link: Link) => { + const sourceData = source(link); + const targetData = target(link); + + const sx = x(sourceData); + const sy = y(sourceData); + const tx = x(targetData); + const ty = y(targetData); + + const path = d3Path(); + path.moveTo(sx, sy); + path.lineTo(sx + (tx - sx) * percent, sy); + path.lineTo(sx + (tx - sx) * percent, ty); + path.lineTo(tx, ty); + + return path.toString(); + }; +} + +type LinkHorizontalStepProps = { + percent?: number; +} & AccessorProps & + SharedLinkProps; + +export default function LinkHorizontalStep({ + className, + innerRef, + data, + path, + percent = 0.5, + x = (d: $TSFIXME) => d.y, + y = (d: $TSFIXME) => d.x, + source = (d: $TSFIXME) => d.source, + target = (d: $TSFIXME) => d.target, + children, + ...restProps +}: LinkHorizontalStepProps & + Omit, keyof LinkHorizontalStepProps>) { + const pathGen = path || pathHorizontalStep({ source, target, x, y, percent }); + if (children) return <>{children({ path: pathGen })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/shapes/link/step/LinkRadialStep.jsx b/packages/vx-shape/src/shapes/link/step/LinkRadialStep.tsx similarity index 52% rename from packages/vx-shape/src/shapes/link/step/LinkRadialStep.jsx rename to packages/vx-shape/src/shapes/link/step/LinkRadialStep.tsx index b628acba1..ad85029fb 100644 --- a/packages/vx-shape/src/shapes/link/step/LinkRadialStep.jsx +++ b/packages/vx-shape/src/shapes/link/step/LinkRadialStep.tsx @@ -1,11 +1,16 @@ import React from 'react'; import cx from 'classnames'; -import PropTypes from 'prop-types'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; -export function pathRadialStep({ source, target, x, y }) { - return data => { - const sourceData = source(data); - const targetData = target(data); +export function pathRadialStep({ + source, + target, + x, + y, +}: Required>) { + return (link: Link) => { + const sourceData = source(link); + const targetData = target(link); const sx = x(sourceData); const sy = y(sourceData); @@ -31,37 +36,31 @@ export function pathRadialStep({ source, target, x, y }) { }; } -LinkRadialStep.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - path: PropTypes.func, - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, -}; +type LinkRadialStepProps = { + percent?: number; +} & AccessorProps & + SharedLinkProps; -export default function LinkRadialStep({ +export default function LinkRadialStep({ className, innerRef, data, path, - x = d => d.x, - y = d => d.y, - source = d => d.source, - target = d => d.target, + x = (d: $TSFIXME) => d.x, + y = (d: $TSFIXME) => d.y, + source = (d: $TSFIXME) => d.source, + target = (d: $TSFIXME) => d.target, children, ...restProps -}) { +}: LinkRadialStepProps & + Omit, keyof LinkRadialStepProps>) { const pathGen = path || pathRadialStep({ source, target, x, y }); - if (children) return children({ path }); + if (children) return <>{children({ path: pathGen })}; return ( ); diff --git a/packages/vx-shape/src/shapes/link/step/LinkVerticalStep.jsx b/packages/vx-shape/src/shapes/link/step/LinkVerticalStep.jsx deleted file mode 100644 index a30cc1d9b..000000000 --- a/packages/vx-shape/src/shapes/link/step/LinkVerticalStep.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import { path as d3Path } from 'd3-path'; - -export function pathVerticalStep({ source, target, x, y, percent }) { - return data => { - const sourceData = source(data); - const targetData = target(data); - - const sx = x(sourceData); - const sy = y(sourceData); - const tx = x(targetData); - const ty = y(targetData); - - const path = d3Path(); - path.moveTo(sx, sy); - path.lineTo(sx, sy + (ty - sy) * percent); - path.lineTo(tx, sy + (ty - sy) * percent); - path.lineTo(tx, ty); - - return path.toString(); - }; -} - -LinkVerticalStep.propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - percent: PropTypes.number, - x: PropTypes.func, - y: PropTypes.func, - source: PropTypes.func, - target: PropTypes.func, - path: PropTypes.func, - children: PropTypes.func, - className: PropTypes.string, - data: PropTypes.any, -}; - -export default function LinkVerticalStep({ - className, - innerRef, - data, - path, - percent = 0.5, - x = d => d.x, - y = d => d.y, - source = d => d.source, - target = d => d.target, - children, - ...restProps -}) { - const pathGen = path || pathVerticalStep({ source, target, x, y, percent }); - if (children) return children({ path }); - return ( - - ); -} diff --git a/packages/vx-shape/src/shapes/link/step/LinkVerticalStep.tsx b/packages/vx-shape/src/shapes/link/step/LinkVerticalStep.tsx new file mode 100644 index 000000000..437e0947d --- /dev/null +++ b/packages/vx-shape/src/shapes/link/step/LinkVerticalStep.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import cx from 'classnames'; +import { path as d3Path } from 'd3-path'; +import { SharedLinkProps, AccessorProps, $TSFIXME } from '../../../types'; + +export function pathVerticalStep({ + source, + target, + x, + y, + percent, +}: Required> & { percent: number }) { + return (link: Link) => { + const sourceData = source(link); + const targetData = target(link); + + const sx = x(sourceData); + const sy = y(sourceData); + const tx = x(targetData); + const ty = y(targetData); + + const path = d3Path(); + path.moveTo(sx, sy); + path.lineTo(sx, sy + (ty - sy) * percent); + path.lineTo(tx, sy + (ty - sy) * percent); + path.lineTo(tx, ty); + + return path.toString(); + }; +} + +type LinkVerticalStepProps = { + percent?: number; +} & AccessorProps & + SharedLinkProps; + +export default function LinkVerticalStep({ + className, + innerRef, + data, + path, + percent = 0.5, + x = (d: $TSFIXME) => d.x, + y = (d: $TSFIXME) => d.y, + source = (d: $TSFIXME) => d.source, + target = (d: $TSFIXME) => d.target, + children, + ...restProps +}: LinkVerticalStepProps & + Omit, keyof LinkVerticalStepProps>) { + const pathGen = path || pathVerticalStep({ source, target, x, y, percent }); + if (children) return <>{children({ path: pathGen })}; + return ( + + ); +} diff --git a/packages/vx-shape/src/types.ts b/packages/vx-shape/src/types.ts new file mode 100644 index 000000000..09a6cf010 --- /dev/null +++ b/packages/vx-shape/src/types.ts @@ -0,0 +1,117 @@ +import { SeriesPoint } from 'd3-shape'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type $TSFIXME = any; + +/** Unique key for item in a stack. */ +export type StackKey = string | number; + +/** Unique key for item in a group. */ +export type GroupKey = string | number; + +/** One BarGroup is returned for each datum, which has multiple sub-bars (based on keys). */ +export interface BarGroup { + /** index of BarGroup (matches input Datum index). */ + index: number; + /** x0 position of bar group */ + x0: number; + /** bars within group, one for each key. */ + bars: BarGroupBar[]; +} + +/** One BarGroup is returned for each datum, which has multiple sub-bars (based on keys). */ +export interface BarGroupHorizontal { + /** index of BarGroup (matches input Datum index). */ + index: number; + /** y0 position of bar group */ + y0: number; + /** bars within group, one for each key. */ + bars: BarGroupBar[]; +} + +export interface BarGroupBar { + /** group key */ + key: GroupKey; + /** index of BarGroup (matches input Datum index). */ + index: number; + /** group value (Datum[key]) */ + value: number; + /** height of bar. */ + height: number; + /** width of bar. */ + width: number; + /** x position of bar. */ + x: number; + /** y position of bar. */ + y: number; + /** color of bar. */ + color: string; +} + +/** One BarStack is returned for each datum, which has multiple sub-bars (based on keys). */ +export interface BarStack { + index: number; + key: StackKey; + bars: ({ + /** Processed bar Datum with bar bounds and original datum. */ + bar: SeriesPoint; + /** group key */ + key: StackKey; + /** index of BarGroup (matches input Datum index). */ + index: number; + /** height of bar. */ + height: number; + /** width of bar. */ + width: number; + /** x position of bar. */ + x: number; + /** y position of bar. */ + y: number; + /** color of bar. */ + color: string; + })[]; +} + +export type AccessorProps = { + /** Given a node, returns its x coordinate. */ + x?: (node: Node) => number; + /** Given a node, returns its y coordinate. */ + y?: (node: Node) => number; + /** Given a link, returns the source node. */ + source?: (link: Link) => Node; + /** Given a link, returns the target node. */ + target?: (link: Link) => Node; +}; + +export type RadialAccessorProps = Pick< + AccessorProps, + 'source' | 'target' +> & { + /** Given a node, returns its x coordinate. */ + angle?: (node: Node) => number; + /** Given a node, returns its y coordinate. */ + radius?: (node: Node) => number; +}; + +type PathType = (link: Link) => string | null; + +export type SharedLinkProps = { + /** className applied to path element. */ + className?: string; + /** React ref to the path element. */ + innerRef?: React.Ref; + /** Path generator, given a link returns a path d attribute string */ + path?: PathType; + /** Render function override which is passed the configured path generator as input. */ + children?: (args: { path: PathType }) => React.ReactNode; + /** Datum for which to render a link. */ + data: Link; +}; + +/** This is meant to be a generic interface for any scale based on usage in this package. */ +export interface ScaleType { + (...args: $TSFIXME[]): number; + range(): [number, number]; + domain(): [$TSFIXME, $TSFIXME]; + bandwidth?: () => number; +} diff --git a/packages/vx-shape/src/util/objHasMethod.js b/packages/vx-shape/src/util/objHasMethod.js deleted file mode 100644 index d7a949041..000000000 --- a/packages/vx-shape/src/util/objHasMethod.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function objHasMethod(obj, funcName) { - return !!obj[funcName] && typeof obj[funcName] === 'function'; -} diff --git a/packages/vx-shape/src/util/setNumberOrNumberAccessor.ts b/packages/vx-shape/src/util/setNumberOrNumberAccessor.ts new file mode 100644 index 000000000..6c0a2974f --- /dev/null +++ b/packages/vx-shape/src/util/setNumberOrNumberAccessor.ts @@ -0,0 +1,13 @@ +export type NumberAccessor = (d: Datum) => number; + +/** + * This is a workaround for TypeScript not inferring the correct + * method overload/signature for some d3 shape methods. + */ +export default function setNumberOrNumberAccessor( + func: (d: number | NumAccessor) => void, + value: number | NumAccessor, +) { + if (typeof value === 'number') func(value); + else func(value); +} diff --git a/packages/vx-shape/src/util/stackOffset.js b/packages/vx-shape/src/util/stackOffset.ts similarity index 73% rename from packages/vx-shape/src/util/stackOffset.js rename to packages/vx-shape/src/util/stackOffset.ts index 946307278..5e0c6d2e5 100644 --- a/packages/vx-shape/src/util/stackOffset.js +++ b/packages/vx-shape/src/util/stackOffset.ts @@ -16,6 +16,6 @@ export const STACK_OFFSETS = { export const STACK_OFFSET_NAMES = Object.keys(STACK_OFFSETS); -export default function stackOffset(offset) { - return STACK_OFFSETS[offset] || STACK_OFFSETS.none; +export default function stackOffset(offset?: keyof typeof STACK_OFFSETS) { + return (offset && STACK_OFFSETS[offset]) || STACK_OFFSETS.none; } diff --git a/packages/vx-shape/src/util/stackOrder.js b/packages/vx-shape/src/util/stackOrder.ts similarity index 74% rename from packages/vx-shape/src/util/stackOrder.js rename to packages/vx-shape/src/util/stackOrder.ts index 24c1fa671..d3c0bbbc3 100644 --- a/packages/vx-shape/src/util/stackOrder.js +++ b/packages/vx-shape/src/util/stackOrder.ts @@ -16,6 +16,6 @@ export const STACK_ORDERS = { export const STACK_ORDER_NAMES = Object.keys(STACK_ORDERS); -export default function stackOrder(order) { - return STACK_ORDERS[order] || STACK_ORDERS.none; +export default function stackOrder(order?: keyof typeof STACK_ORDERS) { + return (order && STACK_ORDERS[order]) || STACK_ORDERS.none; } diff --git a/packages/vx-shape/src/util/trigonometry.js b/packages/vx-shape/src/util/trigonometry.js deleted file mode 100644 index 3b1eabce4..000000000 --- a/packages/vx-shape/src/util/trigonometry.js +++ /dev/null @@ -1 +0,0 @@ -export const degreesToRadians = degrees => (Math.PI / 180) * degrees; diff --git a/packages/vx-shape/src/util/trigonometry.ts b/packages/vx-shape/src/util/trigonometry.ts new file mode 100644 index 000000000..fe373bdb1 --- /dev/null +++ b/packages/vx-shape/src/util/trigonometry.ts @@ -0,0 +1 @@ +export const degreesToRadians = (degrees: number) => (Math.PI / 180) * degrees; diff --git a/packages/vx-shape/test/Arc.test.jsx b/packages/vx-shape/test/Arc.test.tsx similarity index 85% rename from packages/vx-shape/test/Arc.test.jsx rename to packages/vx-shape/test/Arc.test.tsx index ce906a3ab..1b9894fab 100644 --- a/packages/vx-shape/test/Arc.test.jsx +++ b/packages/vx-shape/test/Arc.test.tsx @@ -2,37 +2,31 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { Arc } from '../src'; - -const browserUsage = [ - { - date: '2015 Jun 15', - 'Google Chrome': '48.09', - 'Internet Explorer': '24.14', - Firefox: '18.82', - Safari: '7.46', - 'Microsoft Edge': '0.03', - Opera: '1.32', - Mozilla: '0.12', - 'Other/Unknown': '0.01', - }, - { - date: '2015 Jun 16', - 'Google Chrome': '48', - 'Internet Explorer': '24.19', - Firefox: '18.96', - Safari: '7.36', - 'Microsoft Edge': '0.03', - Opera: '1.32', - Mozilla: '0.12', - 'Other/Unknown': '0.01', - }, -]; - -const ArcWrapper = overrideProps => shallow(); - -const ArcChildren = ({ children, ...restProps }) => +import { ArcProps } from '../src/shapes/Arc'; + +interface Datum { + data: number; + value: number; + index: number; + startAngle: number; + endAngle: number; + padAngle: number; +} + +const data: Datum = { + data: 1, + value: 1, + index: 6, + startAngle: 6.050474740247008, + endAngle: 6.166830023713296, + padAngle: 0, +}; + +const ArcWrapper = (overrideProps = {}) => shallow(); + +const ArcChildren = ({ children, ...restProps }: Partial>) => shallow( - + {children} , ); @@ -194,18 +188,18 @@ describe('', () => { const fn = jest.fn(); ArcChildren({ children: fn }); const args = fn.mock.calls[0][0]; - expect(typeof args.path(browserUsage)).toBe('string'); + expect(typeof args.path(data)).toBe('string'); }); test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('path'); + const refCallback = (ref: SVGPathElement) => { + expect(ref.tagName).toMatch('path'); done(); }; mount( - + , ); }); diff --git a/packages/vx-shape/test/Area.test.jsx b/packages/vx-shape/test/Area.test.tsx similarity index 88% rename from packages/vx-shape/test/Area.test.jsx rename to packages/vx-shape/test/Area.test.tsx index 7d4f9b1aa..26071e0b2 100644 --- a/packages/vx-shape/test/Area.test.jsx +++ b/packages/vx-shape/test/Area.test.tsx @@ -2,14 +2,20 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { Area } from '../src'; +import { AreaProps } from '../src/shapes/Area'; -const fakeData = [ +interface Datum { + x: Date; + y: number; +} + +const fakeData: Datum[] = [ { x: new Date('2017-01-01'), y: 5 }, { x: new Date('2017-01-02'), y: 5 }, { x: new Date('2017-01-03'), y: 5 }, ]; -const AreaChildren = ({ children, ...restProps }) => +const AreaChildren = ({ children, ...restProps }: Partial>) => shallow( {children} @@ -20,8 +26,8 @@ const xScale = () => 50; const yScale = () => 50; yScale.range = () => [100, 0]; -const x = d => xScale(d.x); -const y = d => yScale(d.y); +const x = () => xScale(); +const y = () => yScale(); describe('', () => { test('it should be defined', () => { @@ -35,8 +41,8 @@ describe('', () => { test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('path'); + const refCallback = (ref: SVGPathElement) => { + expect(ref.tagName).toMatch('path'); done(); }; mount( diff --git a/packages/vx-shape/test/AreaClosed.test.jsx b/packages/vx-shape/test/AreaClosed.test.tsx similarity index 83% rename from packages/vx-shape/test/AreaClosed.test.jsx rename to packages/vx-shape/test/AreaClosed.test.tsx index 052f86b1f..6657d009a 100644 --- a/packages/vx-shape/test/AreaClosed.test.jsx +++ b/packages/vx-shape/test/AreaClosed.test.tsx @@ -2,24 +2,34 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { AreaClosed } from '../src'; +import { AreaClosedProps } from '../src/shapes/AreaClosed'; -const data = [ +interface Datum { + x: Date; + y: number; +} + +const data: Datum[] = [ { x: new Date('2017-01-01'), y: 5 }, { x: new Date('2017-01-02'), y: 5 }, { x: new Date('2017-01-03'), y: 5 }, ]; const xScale = () => 50; +xScale.range = () => [0, 100]; +xScale.domain = () => [0, 100]; + const yScale = () => 50; -yScale.range = () => [100, 0]; +yScale.range = () => [100, 0] as [number, number]; +yScale.domain = () => [0, 100] as [number, number]; -const x = d => xScale(d.x); -const y = d => yScale(d.y); +const x = () => xScale(); +const y = () => yScale(); -const AreaClosedWrapper = restProps => +const AreaClosedWrapper = (restProps = {}) => shallow(); -const AreaClosedChildren = ({ children, ...restProps }) => +const AreaClosedChildren = ({ children, ...restProps }: Partial>) => shallow( {children} @@ -37,8 +47,8 @@ describe('', () => { test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('path'); + const refCallback = (ref: SVGPathElement) => { + expect(ref.tagName).toMatch('path'); done(); }; mount( diff --git a/packages/vx-shape/test/AreaStack.test.jsx b/packages/vx-shape/test/AreaStack.test.tsx similarity index 100% rename from packages/vx-shape/test/AreaStack.test.jsx rename to packages/vx-shape/test/AreaStack.test.tsx diff --git a/packages/vx-shape/test/Bar.test.jsx b/packages/vx-shape/test/Bar.test.tsx similarity index 78% rename from packages/vx-shape/test/Bar.test.jsx rename to packages/vx-shape/test/Bar.test.tsx index 3bd3a0374..5c57e18ef 100644 --- a/packages/vx-shape/test/Bar.test.jsx +++ b/packages/vx-shape/test/Bar.test.tsx @@ -3,7 +3,7 @@ import { shallow, mount } from 'enzyme'; import { Bar } from '../src'; -const BarWrapper = restProps => shallow(); +const BarWrapper = (restProps = {}) => shallow(); describe('', () => { test('it should be defined', () => { @@ -20,8 +20,8 @@ describe('', () => { test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('rect'); + const refCallback = (ref: SVGRectElement) => { + expect(ref.tagName).toMatch('rect'); done(); }; mount( diff --git a/packages/vx-shape/test/BarGroup.test.jsx b/packages/vx-shape/test/BarGroup.test.tsx similarity index 75% rename from packages/vx-shape/test/BarGroup.test.jsx rename to packages/vx-shape/test/BarGroup.test.tsx index b4a289e69..6cbef67b0 100644 --- a/packages/vx-shape/test/BarGroup.test.jsx +++ b/packages/vx-shape/test/BarGroup.test.tsx @@ -2,8 +2,16 @@ import React from 'react'; import { shallow } from 'enzyme'; import { BarGroup } from '../src'; +import { BarGroupProps } from '../src/shapes/BarGroup'; -const data = [ +interface Datum { + date: Date; + 'New York': string; + 'San Francisco': string; + Austin: string; +} + +const data: Datum[] = [ { date: new Date(), 'New York': '63.4', @@ -17,19 +25,27 @@ const data = [ Austin: '67.7', }, ]; -const x0 = d => d.date; + +const x0 = () => 5; const x0Scale = () => 2; x0Scale.bandwidth = () => 10; -const x1Scale = d => d; +x0Scale.domain = () => [0, 100] as [number, number]; +x0Scale.range = () => [0, 100] as [number, number]; + +const x1Scale = () => 5; x1Scale.bandwidth = () => 2; -x1Scale.domain = () => [0, 100]; -x1Scale.range = () => [0, 100]; -const yScale = d => d; -const color = d => d; +x1Scale.domain = () => [0, 100] as [number, number]; +x1Scale.range = () => [0, 100] as [number, number]; + +const yScale = () => 5; +yScale.domain = () => [0, 100] as [number, number]; +yScale.range = () => [0, 100] as [number, number]; + +const color = () => 'skyblue'; const keys = ['New York', 'San Francisco', 'Austin']; const height = 1; -const BarGroupWrapper = restProps => +const BarGroupWrapper = (restProps = {}) => shallow( />, ); -const BarGroupChildren = ({ children, ...restProps }) => +const BarGroupChildren = ({ children, ...restProps }: Partial>) => shallow( d.date; + +const y0 = () => 1; const y0Scale = () => 2; y0Scale.bandwidth = () => 10; -const y1Scale = d => d; +y0Scale.domain = () => [0, 100] as [number, number]; +y0Scale.range = () => [0, 100] as [number, number]; +const y1Scale = () => 1; y1Scale.bandwidth = () => 2; -y1Scale.domain = () => [0, 100]; -y1Scale.range = () => [0, 100]; -const xScale = d => d; -const color = d => d; +y1Scale.domain = () => [0, 100] as [number, number]; +y1Scale.range = () => [0, 100] as [number, number]; +const xScale = (d: Datum) => 5; +xScale.domain = () => [0, 100] as [number, number]; +xScale.range = () => [0, 100] as [number, number]; +const color = () => 'violet'; const keys = ['New York', 'San Francisco', 'Austin']; const width = 1; -const BarGroupWrapper = restProps => +const BarGroupWrapper = (restProps = {}) => shallow( />, ); -const BarGroupChildren = ({ children, ...restProps }) => +const BarGroupChildren = ({ children, ...restProps }: Partial>) => shallow( [0, 100]; -xScale.range = () => [0, 100]; -xScale.bandwidth = jest.fn(); -xScale.step = jest.fn(); +const xScale = () => 2; +xScale.domain = () => [0, 100] as [number, number]; +xScale.range = () => [0, 100] as [number, number]; +xScale.bandwidth = () => 2; +xScale.step = () => 2; xScale.paddingInner = jest.fn(); xScale.paddingOuter = jest.fn(); @@ -24,8 +24,8 @@ describe('', () => { left={3} x={d => d} xScale={xScale} - yScale={d => d} - color={d => d} + yScale={xScale} + color={d => d.toString()} keys={[]} />, ); @@ -41,8 +41,8 @@ describe('', () => { left={3} x={d => d} xScale={xScale} - yScale={d => d} - color={d => d} + yScale={xScale} + color={d => d.toString()} keys={[]} />, ); @@ -58,8 +58,8 @@ describe('', () => { left={3} x={d => d} xScale={xScale} - yScale={d => d} - color={d => d} + yScale={xScale} + color={d => d.toString()} keys={[]} />, ); diff --git a/packages/vx-shape/test/BarStackHorizontal.test.jsx b/packages/vx-shape/test/BarStackHorizontal.test.tsx similarity index 71% rename from packages/vx-shape/test/BarStackHorizontal.test.jsx rename to packages/vx-shape/test/BarStackHorizontal.test.tsx index 87812c2d7..c3e7fd2b9 100644 --- a/packages/vx-shape/test/BarStackHorizontal.test.jsx +++ b/packages/vx-shape/test/BarStackHorizontal.test.tsx @@ -3,13 +3,13 @@ import { shallow } from 'enzyme'; import { BarStackHorizontal } from '../src'; -const yScale = jest.fn(); -yScale.domain = () => [0, 100]; -yScale.range = () => [0, 100]; -yScale.bandwidth = jest.fn(); -yScale.step = jest.fn(); -yScale.paddingInner = jest.fn(); -yScale.paddingOuter = jest.fn(); +const scale = () => 5; +scale.domain = () => [0, 100] as [number, number]; +scale.range = () => [0, 100] as [number, number]; +scale.bandwidth = () => 5; +scale.step = () => 5; +scale.paddingInner = () => 5; +scale.paddingOuter = () => 5; describe('', () => { test('it should be defined', () => { @@ -23,9 +23,9 @@ describe('', () => { top={2} left={3} y={d => d} - xScale={d => d} - yScale={yScale} - color={d => d} + xScale={scale} + yScale={scale} + color={d => d.toString()} keys={[]} />, ); @@ -40,9 +40,9 @@ describe('', () => { top={2} left={3} y={d => d} - xScale={d => d} - yScale={yScale} - color={d => d} + xScale={scale} + yScale={scale} + color={d => d.toString()} keys={[]} />, ); @@ -57,9 +57,9 @@ describe('', () => { top={2} left={3} y={d => d} - xScale={d => d} - yScale={yScale} - color={d => d} + xScale={scale} + yScale={scale} + color={d => d.toString()} keys={[]} />, ); diff --git a/packages/vx-shape/test/Circle.test.jsx b/packages/vx-shape/test/Circle.test.tsx similarity index 87% rename from packages/vx-shape/test/Circle.test.jsx rename to packages/vx-shape/test/Circle.test.tsx index de53de6dd..bffc52daf 100644 --- a/packages/vx-shape/test/Circle.test.jsx +++ b/packages/vx-shape/test/Circle.test.tsx @@ -20,8 +20,8 @@ describe('', () => { test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('circle'); + const refCallback = (ref: SVGCircleElement) => { + expect(ref.tagName).toMatch('circle'); done(); }; mount( diff --git a/packages/vx-shape/test/Line.test.jsx b/packages/vx-shape/test/Line.test.tsx similarity index 76% rename from packages/vx-shape/test/Line.test.jsx rename to packages/vx-shape/test/Line.test.tsx index 332552133..c1b7a5b2f 100644 --- a/packages/vx-shape/test/Line.test.jsx +++ b/packages/vx-shape/test/Line.test.tsx @@ -3,7 +3,7 @@ import { shallow, mount } from 'enzyme'; import { Line } from '../src'; -const LineWrapper = restProps => shallow(); +const LineWrapper = (restProps = {}) => shallow(); describe('', () => { test('it should be defined', () => { @@ -20,8 +20,8 @@ describe('', () => { test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('line'); + const refCallback = (ref: SVGLineElement) => { + expect(ref.tagName).toMatch('line'); done(); }; mount( diff --git a/packages/vx-shape/test/LinePath.test.jsx b/packages/vx-shape/test/LinePath.test.tsx similarity index 73% rename from packages/vx-shape/test/LinePath.test.jsx rename to packages/vx-shape/test/LinePath.test.tsx index 5eeae2fba..859b1cb2a 100644 --- a/packages/vx-shape/test/LinePath.test.jsx +++ b/packages/vx-shape/test/LinePath.test.tsx @@ -2,15 +2,21 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { LinePath } from '../src'; +import { LinePathProps } from '../src/shapes/LinePath'; + +interface Datum { + x: number; + y: number; +} const linePathProps = { data: [{ x: 0, y: 0 }, { x: 1, y: 1 }], - x: d => d.x, - y: d => d.y, + x: (d: Datum) => d.x, + y: (d: Datum) => d.y, }; -const LinePathWrapper = restProps => shallow(); -const LinePathChildren = ({ children, ...restProps }) => +const LinePathWrapper = (restProps = {}) => shallow(); +const LinePathChildren = ({ children, ...restProps }: Partial>) => shallow({children}); describe('', () => { @@ -42,8 +48,8 @@ describe('', () => { test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('path'); + const refCallback = (ref: SVGPathElement) => { + expect(ref.tagName).toMatch('path'); done(); }; mount( diff --git a/packages/vx-shape/test/LineRadial.test.jsx b/packages/vx-shape/test/LineRadial.test.tsx similarity index 53% rename from packages/vx-shape/test/LineRadial.test.jsx rename to packages/vx-shape/test/LineRadial.test.tsx index 82c63f241..ad84b3d3d 100644 --- a/packages/vx-shape/test/LineRadial.test.jsx +++ b/packages/vx-shape/test/LineRadial.test.tsx @@ -2,15 +2,21 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { LineRadial } from '../src'; +import { LineRadialProps } from '../src/shapes/LineRadial'; -const LineRadialProps = { +interface Datum { + x: number; + y: number; +} + +const mockProps = { data: [{ x: 0, y: 0 }, { x: 1, y: 1 }], - angle: d => d.x, - radius: d => d.y, + angle: (d: Datum) => d.x, + radius: (d: Datum) => d.y, }; -const LineRadialWrapper = restProps => shallow(); -const LineRadialChildren = ({ children, ...restProps }) => +const LineRadialWrapper = (restProps = {}) => shallow(); +const LineRadialChildren = ({ children, ...restProps }: Partial>) => shallow({children}); describe('', () => { @@ -19,22 +25,22 @@ describe('', () => { }); test('it should have the .vx-line-radial class', () => { - expect(LineRadialWrapper(LineRadialProps).prop('className')).toBe('vx-line-radial'); + expect(LineRadialWrapper(mockProps).prop('className')).toBe('vx-line-radial'); }); test('it should contain paths', () => { - expect(LineRadialWrapper(LineRadialProps).find('path').length).toBeGreaterThan(0); + expect(LineRadialWrapper(mockProps).find('path').length).toBeGreaterThan(0); }); test('it should take a children as function prop', () => { const fn = jest.fn(); - LineRadialChildren({ children: fn, ...LineRadialProps }); + LineRadialChildren({ children: fn, ...mockProps }); expect(fn).toHaveBeenCalled(); }); test('it should call children function with { path }', () => { const fn = jest.fn(); - LineRadialChildren({ children: fn, ...LineRadialProps }); + LineRadialChildren({ children: fn, ...mockProps }); const args = fn.mock.calls[0][0]; const keys = Object.keys(args); expect(keys.includes('path')).toEqual(true); @@ -42,13 +48,13 @@ describe('', () => { test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('path'); + const refCallback = (ref: SVGPathElement) => { + expect(ref.tagName).toMatch('path'); done(); }; mount( - + , ); }); diff --git a/packages/vx-shape/test/LinkHorizontal.test.jsx b/packages/vx-shape/test/LinkHorizontal.test.tsx similarity index 88% rename from packages/vx-shape/test/LinkHorizontal.test.jsx rename to packages/vx-shape/test/LinkHorizontal.test.tsx index cc53e22a9..4ecb0fd58 100644 --- a/packages/vx-shape/test/LinkHorizontal.test.jsx +++ b/packages/vx-shape/test/LinkHorizontal.test.tsx @@ -14,6 +14,7 @@ const mockHierarchy = hierarchy({ }, ], }); + const link = mockHierarchy.links()[0]; describe('', () => { @@ -23,8 +24,8 @@ describe('', () => { test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('path'); + const refCallback = (ref: SVGPathElement) => { + expect(ref.tagName).toMatch('path'); done(); }; mount( diff --git a/packages/vx-shape/test/LinkRadial.test.jsx b/packages/vx-shape/test/LinkRadial.test.tsx similarity index 100% rename from packages/vx-shape/test/LinkRadial.test.jsx rename to packages/vx-shape/test/LinkRadial.test.tsx diff --git a/packages/vx-shape/test/LinkVertical.test.jsx b/packages/vx-shape/test/LinkVertical.test.tsx similarity index 88% rename from packages/vx-shape/test/LinkVertical.test.jsx rename to packages/vx-shape/test/LinkVertical.test.tsx index 87ebcd804..349dc6296 100644 --- a/packages/vx-shape/test/LinkVertical.test.jsx +++ b/packages/vx-shape/test/LinkVertical.test.tsx @@ -23,8 +23,8 @@ describe('', () => { test('it should expose its ref via an innerRef prop', () => { return new Promise(done => { - const refCallback = n => { - expect(n.tagName).toMatch('path'); + const refCallback = (ref: SVGPathElement) => { + expect(ref.tagName).toMatch('path'); done(); }; mount( diff --git a/packages/vx-shape/test/Pie.test.jsx b/packages/vx-shape/test/Pie.test.tsx similarity index 78% rename from packages/vx-shape/test/Pie.test.jsx rename to packages/vx-shape/test/Pie.test.tsx index 8404c1fb3..1f592d23e 100644 --- a/packages/vx-shape/test/Pie.test.jsx +++ b/packages/vx-shape/test/Pie.test.tsx @@ -2,8 +2,21 @@ import React from 'react'; import { shallow } from 'enzyme'; import { Pie } from '../src'; +import { PieProps } from '../src/shapes/Pie'; -const browserUsage = [ +interface Datum { + date: string; + 'Google Chrome': string; + 'Internet Explorer': string; + Firefox: string; + Safari: string; + 'Microsoft Edge': string; + Opera: string; + Mozilla: string; + 'Other/Unknown': string; +} + +const browserUsage: Datum[] = [ { date: '2015 Jun 15', 'Google Chrome': '48.09', @@ -28,8 +41,8 @@ const browserUsage = [ }, ]; -const PieWrapper = restProps => shallow(); -const PieChildren = ({ children, ...restProps }) => +const PieWrapper = (restProps = {}) => shallow(); +const PieChildren = ({ children, ...restProps }: Partial>) => shallow( {children} @@ -38,6 +51,7 @@ const PieChildren = ({ children, ...restProps }) => describe('', () => { beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/unbound-method global.console.error = jest.fn(); }); @@ -67,6 +81,8 @@ describe('', () => { {({ arcs }) => { expect(arcs[0]).toMatchObject({ value: A, index: 0 }); expect(arcs[1]).toMatchObject({ value: B, index: 1 }); + + return null; }} , ); @@ -75,8 +91,9 @@ describe('', () => { test('it should break on invalid sort callbacks', () => { expect(() => PieWrapper({ pieSort: 12 })).toThrow(); expect(() => PieWrapper({ pieSortValues: 12 })).toThrow(); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(console.error).toBeCalled(); - expect(console.error.mock.calls).toHaveLength(2); + expect((console.error as jest.Mock).mock.calls).toHaveLength(2); }); test('it should have the .vx-pie-arcs-group class', () => { diff --git a/packages/vx-shape/test/Polygon.test.jsx b/packages/vx-shape/test/Polygon.test.tsx similarity index 78% rename from packages/vx-shape/test/Polygon.test.jsx rename to packages/vx-shape/test/Polygon.test.tsx index ed7e04fe8..6fa89d84a 100644 --- a/packages/vx-shape/test/Polygon.test.jsx +++ b/packages/vx-shape/test/Polygon.test.tsx @@ -2,10 +2,15 @@ import React from 'react'; import { shallow } from 'enzyme'; import { Polygon } from '../src'; +import { PolygonProps } from '../src/shapes/Polygon'; -const PolygonWrapper = restProps => shallow(); -const PolygonChildren = ({ children, ...restProps }) => - shallow({children}); +const PolygonWrapper = (restProps = {}) => shallow(); +const PolygonChildren = ({ children, ...restProps }: Partial) => + shallow( + + {children} + , + ); describe('', () => { it('should be defined', () => { @@ -15,7 +20,7 @@ describe('', () => { it('should render an octagon', () => { const wrapper = PolygonWrapper({ sides: 8, size: 25 }); const polygon = wrapper.find('polygon'); - const points = polygon.props().points.split(' '); + const points = polygon.props().points!.split(' '); expect(points).toHaveLength(8); }); diff --git a/packages/vx-shape/test/Stack.test.jsx b/packages/vx-shape/test/Stack.test.tsx similarity index 100% rename from packages/vx-shape/test/Stack.test.jsx rename to packages/vx-shape/test/Stack.test.tsx diff --git a/packages/vx-shape/test/stackOffset.test.js b/packages/vx-shape/test/stackOffset.test.ts similarity index 94% rename from packages/vx-shape/test/stackOffset.test.js rename to packages/vx-shape/test/stackOffset.test.ts index 3e15b8345..8bc765df7 100644 --- a/packages/vx-shape/test/stackOffset.test.js +++ b/packages/vx-shape/test/stackOffset.test.ts @@ -12,6 +12,7 @@ describe('STACK_OFFSETS', () => { describe('stackOffset()', () => { test('it should default to d3.stackOffsetNone', () => { + // @ts-ignore allow invalid input const offset = stackOffset('x'); expect(offset).toEqual(STACK_OFFSETS.none); }); diff --git a/packages/vx-shape/test/stackOrder.test.js b/packages/vx-shape/test/stackOrder.test.ts similarity index 94% rename from packages/vx-shape/test/stackOrder.test.js rename to packages/vx-shape/test/stackOrder.test.ts index 2a5ab59a2..db358ebb1 100644 --- a/packages/vx-shape/test/stackOrder.test.js +++ b/packages/vx-shape/test/stackOrder.test.ts @@ -12,6 +12,7 @@ describe('STACK_ORDERS', () => { describe('stackOrders()', () => { test('it should default to d3.stackOrderNone', () => { + // @ts-ignore allow invalid input const offset = stackOrder('x'); expect(offset).toEqual(STACK_ORDERS.none); });