From 13fb9a2c4d8ee3897f904edb3566fadafe313f97 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Mon, 16 Sep 2019 11:45:48 -0700 Subject: [PATCH 01/22] broken(vx-legend): mid work --- packages/vx-legend/package.json | 4 + packages/vx-legend/src/{index.js => index.ts} | 8 +- packages/vx-legend/src/legend/Legend.tsx | 124 ++++++++++++++++++ .../LegendItem.jsx => legend/LegendItem.tsx} | 15 +-- .../LegendLabel.tsx} | 15 +-- packages/vx-legend/src/legend/LegendShape.tsx | 47 +++++++ packages/vx-legend/src/legends/Legend.jsx | 107 --------------- .../vx-legend/src/legends/LegendShape.jsx | 47 ------- packages/vx-legend/src/legends/Linear.jsx | 51 ------- packages/vx-legend/src/legends/Linear.tsx | 32 +++++ packages/vx-legend/src/legends/Ordinal.jsx | 40 ------ packages/vx-legend/src/legends/Ordinal.tsx | 11 ++ packages/vx-legend/src/legends/Quantile.jsx | 47 ------- packages/vx-legend/src/legends/Quantile.tsx | 54 ++++++++ .../src/legends/{Size.jsx => Size.tsx} | 17 ++- .../legends/{Threshold.jsx => Threshold.tsx} | 0 packages/vx-legend/src/shapes/Circle.jsx | 24 ---- packages/vx-legend/src/shapes/Circle.tsx | 26 ++++ packages/vx-legend/src/shapes/Rect.jsx | 22 ---- packages/vx-legend/src/shapes/Rect.tsx | 21 +++ packages/vx-legend/src/types/index.ts | 49 +++++++ .../src/util/labelTransformFactory.ts | 17 +++ packages/vx-legend/src/util/renderShape.js | 34 ----- packages/vx-legend/src/util/renderShape.ts | 55 ++++++++ .../vx-legend/src/util/valueOrIdentity.js | 4 - .../vx-legend/src/util/valueOrIdentity.ts | 6 + 26 files changed, 472 insertions(+), 405 deletions(-) rename packages/vx-legend/src/{index.js => index.ts} (55%) create mode 100644 packages/vx-legend/src/legend/Legend.tsx rename packages/vx-legend/src/{legends/LegendItem.jsx => legend/LegendItem.tsx} (58%) rename packages/vx-legend/src/{legends/LegendLabel.jsx => legend/LegendLabel.tsx} (55%) create mode 100644 packages/vx-legend/src/legend/LegendShape.tsx delete mode 100644 packages/vx-legend/src/legends/Legend.jsx delete mode 100644 packages/vx-legend/src/legends/LegendShape.jsx delete mode 100644 packages/vx-legend/src/legends/Linear.jsx create mode 100644 packages/vx-legend/src/legends/Linear.tsx delete mode 100644 packages/vx-legend/src/legends/Ordinal.jsx create mode 100644 packages/vx-legend/src/legends/Ordinal.tsx delete mode 100644 packages/vx-legend/src/legends/Quantile.jsx create mode 100644 packages/vx-legend/src/legends/Quantile.tsx rename packages/vx-legend/src/legends/{Size.jsx => Size.tsx} (78%) rename packages/vx-legend/src/legends/{Threshold.jsx => Threshold.tsx} (100%) delete mode 100644 packages/vx-legend/src/shapes/Circle.jsx create mode 100644 packages/vx-legend/src/shapes/Circle.tsx delete mode 100644 packages/vx-legend/src/shapes/Rect.jsx create mode 100644 packages/vx-legend/src/shapes/Rect.tsx create mode 100644 packages/vx-legend/src/types/index.ts create mode 100644 packages/vx-legend/src/util/labelTransformFactory.ts delete mode 100644 packages/vx-legend/src/util/renderShape.js create mode 100644 packages/vx-legend/src/util/renderShape.ts delete mode 100644 packages/vx-legend/src/util/valueOrIdentity.js create mode 100644 packages/vx-legend/src/util/valueOrIdentity.ts diff --git a/packages/vx-legend/package.json b/packages/vx-legend/package.json index 5a1c44618d..c46bff1134 100644 --- a/packages/vx-legend/package.json +++ b/packages/vx-legend/package.json @@ -5,6 +5,7 @@ "sideEffects": false, "main": "lib/index.js", "module": "esm/index.js", + "types": "lib/index.d.ts", "files": [ "lib", "esm" @@ -36,6 +37,9 @@ "access": "public" }, "dependencies": { + "@types/classnames": "^2.2.9", + "@types/d3-scale": "^2.1.1", + "@types/react": "*", "@vx/group": "0.0.192", "classnames": "^2.2.5", "prop-types": "^15.5.10" diff --git a/packages/vx-legend/src/index.js b/packages/vx-legend/src/index.ts similarity index 55% rename from packages/vx-legend/src/index.js rename to packages/vx-legend/src/index.ts index eeea03ed23..a9e1094d39 100644 --- a/packages/vx-legend/src/index.js +++ b/packages/vx-legend/src/index.ts @@ -1,9 +1,9 @@ -export { default as Legend } from './legends/Legend'; +export { default as Legend } from './legend/Legend'; export { default as LegendQuantile } from './legends/Quantile'; export { default as LegendLinear } from './legends/Linear'; export { default as LegendOrdinal } from './legends/Ordinal'; export { default as LegendThreshold } from './legends/Threshold'; export { default as LegendSize } from './legends/Size'; -export { default as LegendItem } from './legends/LegendItem'; -export { default as LegendLabel } from './legends/LegendLabel'; -export { default as LegendShape } from './legends/LegendShape'; +export { default as LegendItem } from './legend/LegendItem'; +export { default as LegendLabel } from './legend/LegendLabel'; +export { default as LegendShape } from './legend/LegendShape'; diff --git a/packages/vx-legend/src/legend/Legend.tsx b/packages/vx-legend/src/legend/Legend.tsx new file mode 100644 index 0000000000..2940b033a0 --- /dev/null +++ b/packages/vx-legend/src/legend/Legend.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import cx from 'classnames'; +import LegendItem from './LegendItem'; +import LegendLabel from './LegendLabel'; +import LegendShape from './LegendShape'; +import valueOrIdentity from '../util/valueOrIdentity'; +import labelTransformFactory from '../util/labelTransformFactory'; +import { + ScaleType, + FormattedLabel, + LabelFormatter, + LabelFormatterFactory, + LegendShape as LegendShapeType, +} from '../types'; + +export type LegendProps = { + /** Optional render function override. */ + children?: (labels: FormattedLabel[]) => any; + /** Classname to be applied to legend container. */ + className?: string; + /** Styles to be applied to the legend container. */ + style?: React.CSSProperties; + /** Legend domain. */ + domain?: Datum[]; + /** Width of the legend shape. */ + shapeWidth?: string | number; + /** Height of the legend shape. */ + shapeHeight?: string | number; + /** Margin of the legend shape. */ + shapeMargin?: React.CSSProperties['margin']; + /** Flex-box alignment of legend item labels. */ + labelAlign?: React.CSSProperties['justifyContent']; + /** @TODO handle object type? */ + scale: ScaleType; + /** Flex-box flex of legend item labels. */ + labelFlex?: React.CSSProperties['flex']; + /** Margin of legend item labels. */ + labelMargin?: React.CSSProperties['margin']; + /** Margin of legend items. */ + itemMargin?: React.CSSProperties['margin']; + /** Flex direction of the legend itself. */ + direction: React.CSSProperties['flexDirection']; + /** Flex direction of legend items. */ + itemDirection: React.CSSProperties['flexDirection']; + /** Legend item fill accessor function. */ + fill?: (label: FormattedLabel) => React.CSSProperties['background']; + /** Legend item size accessor function. */ + size?: (label: FormattedLabel) => string | number | undefined; + /** Legend shape string preset or Element or Component. */ + shape?: LegendShapeType; + /** Styles applied to legend shapes. */ + shapeStyle?: (label: FormattedLabel) => React.CSSProperties; + /** Given a legend item and its index, returns an item label. */ + labelFormat?: LabelFormatter; + /** Given the legend scale and labelFormatter, returns a label with datum, index, value, and label. */ + labelTransform?: LabelFormatterFactory; +}; + +const defaultStyle = { + display: 'flex', +}; + +export default function Legend({ + className, + style = defaultStyle, + scale, + shape, + domain: inputDomain, + fill = valueOrIdentity, + size = valueOrIdentity, + labelFormat = valueOrIdentity, + labelTransform = labelTransformFactory, + shapeWidth = 15, + shapeHeight = 15, + shapeMargin = '2px 4px 2px 0', + shapeStyle, + labelAlign = 'left', + labelFlex = '1', + labelMargin = '0 4px', + itemMargin = '0', + direction = 'column', + itemDirection = 'row', + children, + ...restProps +}: LegendProps) { + const domain = inputDomain || (scale.domain() as Datum[]); + const labelFormatter = labelTransform({ scale, labelFormat }); + const labels = domain.map(labelFormatter); + if (children) return children(labels); + + return ( +
+ {labels.map((label, i) => { + const { text } = label; + return ( + + + + + ); + })} +
+ ); +} diff --git a/packages/vx-legend/src/legends/LegendItem.jsx b/packages/vx-legend/src/legend/LegendItem.tsx similarity index 58% rename from packages/vx-legend/src/legends/LegendItem.jsx rename to packages/vx-legend/src/legend/LegendItem.tsx index 60ac5487f7..6e780622c8 100644 --- a/packages/vx-legend/src/legends/LegendItem.jsx +++ b/packages/vx-legend/src/legend/LegendItem.tsx @@ -1,12 +1,11 @@ import React from 'react'; -import PropTypes from 'prop-types'; -LegendItem.propTypes = { - flexDirection: PropTypes.string, - alignItems: PropTypes.string, - margin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - children: PropTypes.any, - display: PropTypes.string, +export type LegendItemProps = { + flexDirection?: React.CSSProperties['flexDirection']; + alignItems?: React.CSSProperties['alignItems']; + margin?: React.CSSProperties['margin']; + children?: React.ReactNode; + display?: React.CSSProperties['display']; }; export default function LegendItem({ @@ -16,7 +15,7 @@ export default function LegendItem({ display = 'flex', children, ...restProps -}) { +}: LegendItemProps) { return (
= { + label: FormattedLabel; + margin?: React.CSSProperties['margin']; + shape?: LegendShape; + fill?: (label: FormattedLabel) => any; + size?: (label: FormattedLabel) => any; + shapeStyle?: (label: FormattedLabel) => any; + width?: React.CSSProperties['width']; + height?: React.CSSProperties['height']; +}; + +export default function LegendShape({ + shape = ShapeRect, + width, + height, + margin, + label, + fill, + size, + shapeStyle, +}: LegendShapeProps) { + return ( +
+ {renderShape({ + shape, + label, + width, + height, + fill, + shapeStyle, + })} +
+ ); +} diff --git a/packages/vx-legend/src/legends/Legend.jsx b/packages/vx-legend/src/legends/Legend.jsx deleted file mode 100644 index fa30b71414..0000000000 --- a/packages/vx-legend/src/legends/Legend.jsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import cx from 'classnames'; -import PropTypes from 'prop-types'; -import LegendItem from './LegendItem'; -import LegendLabel from './LegendLabel'; -import LegendShape from './LegendShape'; -import valueOrIdentity from '../util/valueOrIdentity'; - -Legend.propTypes = { - className: PropTypes.string, - style: PropTypes.any, - domain: PropTypes.array, - scale: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, - shapeWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - shapeHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - shapeMargin: PropTypes.any, - labelAlign: PropTypes.string, - labelFlex: PropTypes.string, - labelMargin: PropTypes.string, - itemMargin: PropTypes.string, - direction: PropTypes.string, - itemDirection: PropTypes.string, - fill: PropTypes.any, - size: PropTypes.any, - shape: PropTypes.any, - shapeStyle: PropTypes.any, - labelFormat: PropTypes.func, - labelTransform: PropTypes.func, - children: PropTypes.func, -}; - -const defaultStyle = { - display: 'flex', -}; - -export default function Legend({ - className, - style = defaultStyle, - shapeStyle, - scale, - shape, - domain: inputDomain, - fill = valueOrIdentity, - size = valueOrIdentity, - labelFormat = valueOrIdentity, - labelTransform = defaultTransform, - shapeWidth = 15, - shapeHeight = 15, - shapeMargin = '2px 4px 2px 0', - labelAlign = 'left', - labelFlex = '1', - labelMargin = '0 4px', - itemMargin = '0', - direction = 'column', - itemDirection = 'row', - children, - ...restProps -}) { - const domain = inputDomain || scale.domain(); - const labels = domain.map(labelTransform({ scale, labelFormat })); - if (children) return children(labels); - return ( -
- {labels.map((label, i) => { - const { text } = label; - return ( - - - - - ); - })} -
- ); -} - -function defaultTransform({ scale, labelFormat }) { - return (d, i) => { - return { - datum: d, - index: i, - text: `${labelFormat(d, i)}`, - value: scale(d), - }; - }; -} diff --git a/packages/vx-legend/src/legends/LegendShape.jsx b/packages/vx-legend/src/legends/LegendShape.jsx deleted file mode 100644 index 6cff0bb67e..0000000000 --- a/packages/vx-legend/src/legends/LegendShape.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ShapeRect from '../shapes/Rect'; -import renderShape from '../util/renderShape'; - -LegendShape.propTypes = { - shape: PropTypes.any, - width: PropTypes.any, - height: PropTypes.any, - margin: PropTypes.any, - label: PropTypes.any, - fill: PropTypes.any, - size: PropTypes.any, - shapeStyle: PropTypes.any, -}; - -export default function LegendShape({ - shape = ShapeRect, - width, - height, - margin, - label, - fill, - size, - shapeStyle, -}) { - return ( -
- {renderShape({ - shape, - label, - width, - height, - fill, - shapeStyle, - })} -
- ); -} diff --git a/packages/vx-legend/src/legends/Linear.jsx b/packages/vx-legend/src/legends/Linear.jsx deleted file mode 100644 index 48493a52ac..0000000000 --- a/packages/vx-legend/src/legends/Linear.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Legend from './Legend'; - -LegendLinear.propTypes = { - scale: PropTypes.func.isRequired, - domain: PropTypes.array, - steps: PropTypes.number, - labelFormat: PropTypes.func, - labelTransform: PropTypes.func, -}; - -export default function LegendLinear({ - scale, - domain: inputDomain, - steps = 5, - labelFormat = x => x, - labelTransform = defaultTransform, - ...restProps -}) { - const domain = inputDomain || defaultDomain({ steps, scale }); - return ( - - ); -} - -function defaultDomain({ steps, scale }) { - const domain = scale.domain(); - const start = domain[0]; - const end = domain[domain.length - 1]; - const step = (end - start) / (steps - 1); - return new Array(steps).fill(1).reduce((acc, cur, i) => { - acc.push(start + i * step); - return acc; - }, []); -} - -function defaultTransform({ scale, labelFormat }) { - return (d, i) => { - return { - text: `${labelFormat(d, i)}`, - value: scale(d), - }; - }; -} diff --git a/packages/vx-legend/src/legends/Linear.tsx b/packages/vx-legend/src/legends/Linear.tsx new file mode 100644 index 0000000000..f351d23d07 --- /dev/null +++ b/packages/vx-legend/src/legends/Linear.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import Legend, { LegendProps } from '../legend/Legend'; + +export type LegendLinearProps = { + steps?: number; +} & LegendProps; + +/** Linear scales map from continuous inputs to continuous outputs. */ +export default function LegendLinear({ + scale, + domain: inputDomain, + steps = 5, + ...restProps +}: LegendLinearProps) { + const domain = inputDomain || defaultDomain({ steps, scale }); + return scale={scale} domain={domain} {...restProps} />; +} + +function defaultDomain({ + steps = 5, + scale, +}: Pick, 'steps' | 'scale'>) { + const domain = scale.domain(); + const start = domain[0]; + const end = domain[domain.length - 1]; + const step = (end - start) / (steps - 1); + + return new Array(steps).fill(1).reduce((acc, cur, i) => { + acc.push(start + i * step); + return acc; + }, []); +} diff --git a/packages/vx-legend/src/legends/Ordinal.jsx b/packages/vx-legend/src/legends/Ordinal.jsx deleted file mode 100644 index e7e894f1a2..0000000000 --- a/packages/vx-legend/src/legends/Ordinal.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Legend from './Legend'; -import valueOrIdentity from '../util/valueOrIdentity'; - -LegendOrdinal.propTypes = { - scale: PropTypes.func.isRequired, - domain: PropTypes.array, - labelTransform: PropTypes.func, - labelFormat: PropTypes.func, -}; - -export default function LegendOrdinal({ - scale, - domain, - labelTransform = defaultTransform, - labelFormat = valueOrIdentity, - ...restProps -}) { - return ( - - ); -} - -function defaultTransform({ scale, labelFormat }) { - return (d, i) => { - return { - datum: d, - index: i, - text: `${labelFormat(d, i)}`, - value: scale(d), - }; - }; -} diff --git a/packages/vx-legend/src/legends/Ordinal.tsx b/packages/vx-legend/src/legends/Ordinal.tsx new file mode 100644 index 0000000000..ec969581f1 --- /dev/null +++ b/packages/vx-legend/src/legends/Ordinal.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import Legend, { LegendProps } from '../legend/Legend'; + +export type LegendOrdinalProps = LegendProps; + +/** Ordinal scales map from strings to an Output type. */ +export default function LegendOrdinal( + props: LegendOrdinalProps, +) { + return ; +} diff --git a/packages/vx-legend/src/legends/Quantile.jsx b/packages/vx-legend/src/legends/Quantile.jsx deleted file mode 100644 index 28273ef69b..0000000000 --- a/packages/vx-legend/src/legends/Quantile.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Legend from './Legend'; - -LegendQuantile.propTypes = { - scale: PropTypes.func.isRequired, - domain: PropTypes.array, - labelFormat: PropTypes.func, - labelTransform: PropTypes.func, - labelDelimiter: PropTypes.string, -}; - -export default function LegendQuantile({ - scale, - domain: inputDomain, - labelFormat = x => x, - labelTransform: inputLabelTransform, - labelDelimiter = '-', - ...restProps -}) { - const domain = inputDomain || scale.range(); - const labelTransform = inputLabelTransform || defaultTransform({ labelDelimiter }); - return ( - - ); -} - -function defaultTransform({ labelDelimiter }) { - return ({ scale, labelFormat }) => { - return (d, i) => { - const [x0, x1] = scale.invertExtent(d); - return { - extent: [x0, x1], - text: `${labelFormat(x0, i)} ${labelDelimiter} ${labelFormat(x1, i)}`, - value: scale(x0), - datum: d, - index: i, - }; - }; - }; -} diff --git a/packages/vx-legend/src/legends/Quantile.tsx b/packages/vx-legend/src/legends/Quantile.tsx new file mode 100644 index 0000000000..f5bb525fc9 --- /dev/null +++ b/packages/vx-legend/src/legends/Quantile.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import Legend, { LegendProps } from '../legend/Legend'; +import { QuantileScaleType } from '../types'; +import { LabelFormatterFactory } from '../util/labelTransformFactory'; +import { ScaleQuantile } from 'd3-scale'; + +export type LegendQuantileProps = { + labelDelimiter?: string; + scale: ScaleQuantile; +} & Omit, 'scale'>; + +export default function LegendQuantile({ + domain: inputDomain, + scale, + labelFormat = x => x, + labelTransform: inputLabelTransform, + labelDelimiter = '-', + ...restProps +}: LegendQuantileProps) { + const domain = inputDomain || scale.domain(); + const labelTransform = + inputLabelTransform || labelFormatterFactoryFactory({ labelDelimiter }); + + return ( + + scale={scale} + domain={domain} + labelFormat={labelFormat} + labelTransform={labelTransform} + {...restProps} + /> + ); +} + +function labelFormatterFactoryFactory({ + labelDelimiter, +}: Pick, 'labelDelimiter'>): LabelFormatterFactory< + number, + Output, + QuantileScaleType +> { + return ({ scale, labelFormat }) => { + return (d: Output, i) => { + const [x0, x1] = scale.invertExtent(d); + return { + extent: [x0, x1], + text: `${labelFormat(x0, i)} ${labelDelimiter} ${labelFormat(x1, i)}`, + value: scale(x0), + datum: d, + index: i, + }; + }; + }; +} diff --git a/packages/vx-legend/src/legends/Size.jsx b/packages/vx-legend/src/legends/Size.tsx similarity index 78% rename from packages/vx-legend/src/legends/Size.jsx rename to packages/vx-legend/src/legends/Size.tsx index 1c303bceca..542df09837 100644 --- a/packages/vx-legend/src/legends/Size.jsx +++ b/packages/vx-legend/src/legends/Size.tsx @@ -1,14 +1,13 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import Legend from './Legend'; +import Legend, { LegendProps } from '../legend/Legend'; -LegendSize.propTypes = { - scale: PropTypes.func.isRequired, - domain: PropTypes.array, - steps: PropTypes.number, - labelFormat: PropTypes.func, - labelTransform: PropTypes.func, -}; +// LegendSize.propTypes = { +// scale: PropTypes.func.isRequired, +// domain: PropTypes.array, +// steps: PropTypes.number, +// labelFormat: PropTypes.func, +// labelTransform: PropTypes.func, +// }; export default function LegendSize({ scale, diff --git a/packages/vx-legend/src/legends/Threshold.jsx b/packages/vx-legend/src/legends/Threshold.tsx similarity index 100% rename from packages/vx-legend/src/legends/Threshold.jsx rename to packages/vx-legend/src/legends/Threshold.tsx diff --git a/packages/vx-legend/src/shapes/Circle.jsx b/packages/vx-legend/src/shapes/Circle.jsx deleted file mode 100644 index d70b69bd38..0000000000 --- a/packages/vx-legend/src/shapes/Circle.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Group } from '@vx/group'; - -ShapeCircle.propTypes = { - fill: PropTypes.any, - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - style: PropTypes.object, -}; - -export default function ShapeCircle({ fill, width, height, style }) { - const cleanWidth = typeof width === 'string' ? 0 : width; - const cleanHeight = typeof height === 'string' ? 0 : height; - const size = Math.max(cleanWidth, cleanHeight); - const radius = size / 2; - return ( - - - - - - ); -} diff --git a/packages/vx-legend/src/shapes/Circle.tsx b/packages/vx-legend/src/shapes/Circle.tsx new file mode 100644 index 0000000000..78a1124909 --- /dev/null +++ b/packages/vx-legend/src/shapes/Circle.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Group } from '@vx/group'; + +type CircleProps = React.SVGProps; +type SVGProps = React.SVGProps; + +export type ShapeCircleProps = { + fill: CircleProps['fill']; + width: SVGProps['width']; + height: SVGProps['height']; + style: CircleProps['style']; +}; + +export default function ShapeCircle({ fill, width, height, style }: ShapeCircleProps) { + const cleanWidth = typeof width === 'string' || typeof width === 'undefined' ? 0 : width; + const cleanHeight = typeof height === 'string' || typeof height === 'undefined' ? 0 : height; + const size = Math.max(cleanWidth, cleanHeight); + const radius = size / 2; + return ( + + + + + + ); +} diff --git a/packages/vx-legend/src/shapes/Rect.jsx b/packages/vx-legend/src/shapes/Rect.jsx deleted file mode 100644 index 6169c7d324..0000000000 --- a/packages/vx-legend/src/shapes/Rect.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -ShapeRect.propTypes = { - fill: PropTypes.any, - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - style: PropTypes.object, -}; - -export default function ShapeRect({ fill, width, height, style }) { - return ( -
- ); -} diff --git a/packages/vx-legend/src/shapes/Rect.tsx b/packages/vx-legend/src/shapes/Rect.tsx new file mode 100644 index 0000000000..630a253e1c --- /dev/null +++ b/packages/vx-legend/src/shapes/Rect.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +export type ShapeRectProps = { + fill: React.CSSProperties['background']; + width: React.CSSProperties['width']; + height: React.CSSProperties['height']; + style: React.CSSProperties; +}; + +export default function ShapeRect({ fill, width, height, style }: ShapeRectProps) { + return ( +
+ ); +} diff --git a/packages/vx-legend/src/types/index.ts b/packages/vx-legend/src/types/index.ts new file mode 100644 index 0000000000..634fa0700c --- /dev/null +++ b/packages/vx-legend/src/types/index.ts @@ -0,0 +1,49 @@ +import { ScaleLinear, ScaleOrdinal, ScaleBand, ScaleThreshold, ScaleQuantile } from 'd3-scale'; + +export { ScaleLinear, ScaleOrdinal, ScaleBand, ScaleThreshold, ScaleQuantile }; + +export type ScaleType< + Input extends string | number | Date, + Output extends string | number | Date +> = + | ScaleLinear // number input, number output + | ScaleOrdinal // string input, any output + | ScaleBand // string input, number output + | ScaleThreshold + | ScaleQuantile; + +export type LabelFormatterFactory = (args: { + scale: ScaleType; + labelFormat: LabelFormatter; +}) => ItemTransformer; + +export type LabelFormatter = ( + item: Datum, + itemIndex: number, +) => Datum | string | number | undefined; + +export type FormattedLabel = { + datum: Datum; + index: number; + text: string; + value: Output; +} & ExtraAttributes; + +export type ItemTransformer = ( + item: Datum, + itemIndex: number, +) => FormattedLabel; + +export type LegendShape = 'rect' | 'circle' | React.FunctionComponent | React.ComponentClass; + +export type FillAccessor = ( + label: FormattedLabel, +) => string | undefined; + +export type SizeAccessor = ( + label: FormattedLabel, +) => string | number | undefined; + +export type ShapeStyleAccessor = ( + label: FormattedLabel, +) => React.CSSProperties | React.SVGProps | undefined; diff --git a/packages/vx-legend/src/util/labelTransformFactory.ts b/packages/vx-legend/src/util/labelTransformFactory.ts new file mode 100644 index 0000000000..cd22fed2d1 --- /dev/null +++ b/packages/vx-legend/src/util/labelTransformFactory.ts @@ -0,0 +1,17 @@ +import { LabelFormatter, ScaleType, ItemTransformer } from '../types'; + +/** Returns a function which takes a Datum and index as input, and returns a formatted label object. */ +export default function labelTransformFactory({ + scale, + labelFormat, +}: { + scale: ScaleType; + labelFormat: LabelFormatter; +}): ItemTransformer { + return (d, i) => ({ + datum: d, + index: i, + text: `${labelFormat(d, i)}`, + value: scale(d), + }); +} diff --git a/packages/vx-legend/src/util/renderShape.js b/packages/vx-legend/src/util/renderShape.js deleted file mode 100644 index 1481f27687..0000000000 --- a/packages/vx-legend/src/util/renderShape.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react'; -import ShapeRect from '../shapes/Rect'; -import ShapeCircle from '../shapes/Circle'; -import valueOrIdentity from './valueOrIdentity'; - -export default function renderShape({ - shape = 'rect', - fill = valueOrIdentity, - size = valueOrIdentity, - width, - height, - label, - shapeStyle = (/** x */) => undefined, -}) { - const props = { - width, - height, - label, - fill: fill({ ...label }), - size: size({ ...label }), - style: shapeStyle({ ...label }), - }; - if (typeof shape === 'string') { - if (shape === 'rect') { - return ; - } - return ; - } - if (React.isValidElement(shape)) { - return React.cloneElement(shape, props); - } - return React.createElement(shape, props); -} diff --git a/packages/vx-legend/src/util/renderShape.ts b/packages/vx-legend/src/util/renderShape.ts new file mode 100644 index 0000000000..d675332edd --- /dev/null +++ b/packages/vx-legend/src/util/renderShape.ts @@ -0,0 +1,55 @@ +import React from 'react'; +import ShapeRect from '../shapes/Rect'; +import ShapeCircle from '../shapes/Circle'; +import valueOrIdentity from './valueOrIdentity'; +import { + LegendShape, + FormattedLabel, + FillAccessor, + SizeAccessor, + ShapeStyleAccessor, +} from '../types'; + +interface RenderShape { + shape?: LegendShape; + label: FormattedLabel; + fill?: FillAccessor; + size?: SizeAccessor; + shapeStyle?: ShapeStyleAccessor; + width?: React.CSSProperties['width']; + height?: React.CSSProperties['height']; +} + +const NO_OP = () => undefined; + +export default function renderShape({ + shape = 'rect', + fill = NO_OP, + size = NO_OP, + width, + height, + label, + shapeStyle = NO_OP, +}: RenderShape) { + const props = { + width, + height, + label, + fill: fill({ ...label }), + size: size({ ...label }), + style: shapeStyle({ ...label }), + }; + if (typeof shape === 'string') { + if (shape === 'rect') { + return ; + } + return ; + } + if (React.isValidElement(shape)) { + return React.cloneElement(shape, props); + } + if (shape) { + return React.createElement(shape, props as React.Attributes); + } + return null; +} diff --git a/packages/vx-legend/src/util/valueOrIdentity.js b/packages/vx-legend/src/util/valueOrIdentity.js deleted file mode 100644 index 32692db062..0000000000 --- a/packages/vx-legend/src/util/valueOrIdentity.js +++ /dev/null @@ -1,4 +0,0 @@ -export default function valueOrIdentity(x) { - if (x && x.value) return x.value; - return x; -} diff --git a/packages/vx-legend/src/util/valueOrIdentity.ts b/packages/vx-legend/src/util/valueOrIdentity.ts new file mode 100644 index 0000000000..469c805ae3 --- /dev/null +++ b/packages/vx-legend/src/util/valueOrIdentity.ts @@ -0,0 +1,6 @@ +export type ValueOrIdentity = T | { value?: T }; + +export default function valueOrIdentity(x: ValueOrIdentity): T { + if (x && 'value' in x && typeof x.value !== 'undefined') return x.value; + return x as T; +} From ff50a048b9fd0396c55132ba51f162d02ff8a4d1 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 23 Oct 2019 09:18:18 -0700 Subject: [PATCH 02/22] typescript(vx-legend): rename test files jsx => tsx --- packages/vx-legend/test/{Legend.test.jsx => Legend.test.tsx} | 0 .../test/{LegendLinear.test.jsx => LegendLinear.test.tsx} | 0 .../test/{LegendOrdinal.test.jsx => LegendOrdinal.test.tsx} | 0 .../test/{LegendQuantile.test.jsx => LegendQuantile.test.tsx} | 0 .../vx-legend/test/{LegendSize.test.jsx => LegendSize.test.tsx} | 0 .../test/{LegendThreshold.test.jsx => LegendThreshold.test.tsx} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename packages/vx-legend/test/{Legend.test.jsx => Legend.test.tsx} (100%) rename packages/vx-legend/test/{LegendLinear.test.jsx => LegendLinear.test.tsx} (100%) rename packages/vx-legend/test/{LegendOrdinal.test.jsx => LegendOrdinal.test.tsx} (100%) rename packages/vx-legend/test/{LegendQuantile.test.jsx => LegendQuantile.test.tsx} (100%) rename packages/vx-legend/test/{LegendSize.test.jsx => LegendSize.test.tsx} (100%) rename packages/vx-legend/test/{LegendThreshold.test.jsx => LegendThreshold.test.tsx} (100%) diff --git a/packages/vx-legend/test/Legend.test.jsx b/packages/vx-legend/test/Legend.test.tsx similarity index 100% rename from packages/vx-legend/test/Legend.test.jsx rename to packages/vx-legend/test/Legend.test.tsx diff --git a/packages/vx-legend/test/LegendLinear.test.jsx b/packages/vx-legend/test/LegendLinear.test.tsx similarity index 100% rename from packages/vx-legend/test/LegendLinear.test.jsx rename to packages/vx-legend/test/LegendLinear.test.tsx diff --git a/packages/vx-legend/test/LegendOrdinal.test.jsx b/packages/vx-legend/test/LegendOrdinal.test.tsx similarity index 100% rename from packages/vx-legend/test/LegendOrdinal.test.jsx rename to packages/vx-legend/test/LegendOrdinal.test.tsx diff --git a/packages/vx-legend/test/LegendQuantile.test.jsx b/packages/vx-legend/test/LegendQuantile.test.tsx similarity index 100% rename from packages/vx-legend/test/LegendQuantile.test.jsx rename to packages/vx-legend/test/LegendQuantile.test.tsx diff --git a/packages/vx-legend/test/LegendSize.test.jsx b/packages/vx-legend/test/LegendSize.test.tsx similarity index 100% rename from packages/vx-legend/test/LegendSize.test.jsx rename to packages/vx-legend/test/LegendSize.test.tsx diff --git a/packages/vx-legend/test/LegendThreshold.test.jsx b/packages/vx-legend/test/LegendThreshold.test.tsx similarity index 100% rename from packages/vx-legend/test/LegendThreshold.test.jsx rename to packages/vx-legend/test/LegendThreshold.test.tsx From ff54e5ee3050eb2d7b913777192ac9fa821aeea8 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 23 Oct 2019 16:21:37 -0700 Subject: [PATCH 03/22] typescript(vx-legend): re-write package in TypeScript --- packages/vx-legend/src/legend/Legend.tsx | 108 ++++++++++------- packages/vx-legend/src/legend/LegendItem.tsx | 18 ++- packages/vx-legend/src/legend/LegendLabel.tsx | 10 +- packages/vx-legend/src/legend/LegendShape.tsx | 12 +- packages/vx-legend/src/legends/Linear.tsx | 27 +++-- packages/vx-legend/src/legends/Ordinal.tsx | 7 +- packages/vx-legend/src/legends/Quantile.tsx | 55 +++++---- packages/vx-legend/src/legends/Size.tsx | 60 +++++----- packages/vx-legend/src/legends/Threshold.tsx | 113 ++++++++++-------- packages/vx-legend/src/shapes/Circle.tsx | 11 +- packages/vx-legend/src/shapes/Rect.tsx | 8 +- packages/vx-legend/src/types/index.ts | 34 ++++-- .../src/util/labelTransformFactory.ts | 11 +- packages/vx-legend/src/util/renderShape.ts | 41 ++++--- .../vx-legend/src/util/valueOrIdentity.ts | 12 +- 15 files changed, 299 insertions(+), 228 deletions(-) diff --git a/packages/vx-legend/src/legend/Legend.tsx b/packages/vx-legend/src/legend/Legend.tsx index 2940b033a0..ecb4853809 100644 --- a/packages/vx-legend/src/legend/Legend.tsx +++ b/packages/vx-legend/src/legend/Legend.tsx @@ -3,9 +3,11 @@ import cx from 'classnames'; import LegendItem from './LegendItem'; import LegendLabel from './LegendLabel'; import LegendShape from './LegendShape'; -import valueOrIdentity from '../util/valueOrIdentity'; +import valueOrIdentity, { valueOrIdentityString } from '../util/valueOrIdentity'; import labelTransformFactory from '../util/labelTransformFactory'; import { + BaseInput, + BaseOutput, ScaleType, FormattedLabel, LabelFormatter, @@ -13,9 +15,23 @@ import { LegendShape as LegendShapeType, } from '../types'; -export type LegendProps = { +type FlexDirection = + | 'inherit' + | 'initial' + | 'revert' + | 'unset' + | 'column' + | 'column-reverse' + | 'row' + | 'row-reverse'; + +export type LegendProps< + Datum extends BaseInput, + Output extends BaseOutput, + Scale extends ScaleType = ScaleType +> = { /** Optional render function override. */ - children?: (labels: FormattedLabel[]) => any; + children?: (labels: FormattedLabel[]) => React.ReactNode; /** Classname to be applied to legend container. */ className?: string; /** Styles to be applied to the legend container. */ @@ -27,47 +43,51 @@ export type LegendProps = { /** Height of the legend shape. */ shapeHeight?: string | number; /** Margin of the legend shape. */ - shapeMargin?: React.CSSProperties['margin']; + shapeMargin?: string | number; /** Flex-box alignment of legend item labels. */ - labelAlign?: React.CSSProperties['justifyContent']; + labelAlign?: string; /** @TODO handle object type? */ - scale: ScaleType; + scale: Scale; /** Flex-box flex of legend item labels. */ - labelFlex?: React.CSSProperties['flex']; + labelFlex?: string | number; /** Margin of legend item labels. */ - labelMargin?: React.CSSProperties['margin']; + labelMargin?: string | number; /** Margin of legend items. */ - itemMargin?: React.CSSProperties['margin']; + itemMargin?: string | number; /** Flex direction of the legend itself. */ - direction: React.CSSProperties['flexDirection']; + direction: FlexDirection; /** Flex direction of legend items. */ - itemDirection: React.CSSProperties['flexDirection']; + itemDirection: FlexDirection; /** Legend item fill accessor function. */ - fill?: (label: FormattedLabel) => React.CSSProperties['background']; + fill?: (label: FormattedLabel) => string | number | undefined; /** Legend item size accessor function. */ size?: (label: FormattedLabel) => string | number | undefined; /** Legend shape string preset or Element or Component. */ - shape?: LegendShapeType; + shape?: LegendShapeType; /** Styles applied to legend shapes. */ shapeStyle?: (label: FormattedLabel) => React.CSSProperties; /** Given a legend item and its index, returns an item label. */ labelFormat?: LabelFormatter; /** Given the legend scale and labelFormatter, returns a label with datum, index, value, and label. */ - labelTransform?: LabelFormatterFactory; + labelTransform?: LabelFormatterFactory; }; const defaultStyle = { display: 'flex', }; -export default function Legend({ +export default function Legend< + Datum extends BaseInput, + Output extends BaseOutput, + Scale extends ScaleType = ScaleType +>({ className, style = defaultStyle, scale, shape, domain: inputDomain, - fill = valueOrIdentity, - size = valueOrIdentity, + fill = valueOrIdentityString, + size = valueOrIdentityString, labelFormat = valueOrIdentity, labelTransform = labelTransformFactory, shapeWidth = 15, @@ -81,12 +101,12 @@ export default function Legend({ direction = 'column', itemDirection = 'row', children, - ...restProps -}: LegendProps) { + ...legendItemProps +}: LegendProps) { const domain = inputDomain || (scale.domain() as Datum[]); const labelFormatter = labelTransform({ scale, labelFormat }); const labels = domain.map(labelFormatter); - if (children) return children(labels); + if (children) return <>{children(labels)}; return (
({ flexDirection: direction, }} > - {labels.map((label, i) => { - const { text } = label; - return ( - - - - - ); - })} + {labels.map((label, i) => ( + + + + + ))}
); } diff --git a/packages/vx-legend/src/legend/LegendItem.tsx b/packages/vx-legend/src/legend/LegendItem.tsx index 6e780622c8..c6112edb17 100644 --- a/packages/vx-legend/src/legend/LegendItem.tsx +++ b/packages/vx-legend/src/legend/LegendItem.tsx @@ -1,11 +1,19 @@ import React from 'react'; export type LegendItemProps = { - flexDirection?: React.CSSProperties['flexDirection']; - alignItems?: React.CSSProperties['alignItems']; - margin?: React.CSSProperties['margin']; + flexDirection?: + | 'inherit' + | 'initial' + | 'revert' + | 'unset' + | 'column' + | 'column-reverse' + | 'row' + | 'row-reverse'; + alignItems?: string; + margin?: string | number; children?: React.ReactNode; - display?: React.CSSProperties['display']; + display?: string; }; export default function LegendItem({ @@ -15,7 +23,7 @@ export default function LegendItem({ display = 'flex', children, ...restProps -}: LegendItemProps) { +}: LegendItemProps & Omit, keyof LegendItemProps>) { return (
, keyof LegendLabelProps>) { return (
{children || label}
diff --git a/packages/vx-legend/src/legend/LegendShape.tsx b/packages/vx-legend/src/legend/LegendShape.tsx index 7fa15bf41b..5541b1edf1 100644 --- a/packages/vx-legend/src/legend/LegendShape.tsx +++ b/packages/vx-legend/src/legend/LegendShape.tsx @@ -1,20 +1,20 @@ import React from 'react'; import ShapeRect from '../shapes/Rect'; import renderShape from '../util/renderShape'; -import { FormattedLabel, LegendShape } from '../types'; +import { FormattedLabel, LegendShape, BaseInput, BaseOutput } from '../types'; export type LegendShapeProps = { label: FormattedLabel; - margin?: React.CSSProperties['margin']; - shape?: LegendShape; + margin?: string | number; + shape?: LegendShape; fill?: (label: FormattedLabel) => any; size?: (label: FormattedLabel) => any; shapeStyle?: (label: FormattedLabel) => any; - width?: React.CSSProperties['width']; - height?: React.CSSProperties['height']; + width?: string | number; + height?: string | number; }; -export default function LegendShape({ +export default function LegendShape({ shape = ShapeRect, width, height, diff --git a/packages/vx-legend/src/legends/Linear.tsx b/packages/vx-legend/src/legends/Linear.tsx index f351d23d07..9584eb0849 100644 --- a/packages/vx-legend/src/legends/Linear.tsx +++ b/packages/vx-legend/src/legends/Linear.tsx @@ -1,22 +1,12 @@ import React from 'react'; import Legend, { LegendProps } from '../legend/Legend'; +import { BaseOutput } from '../types'; -export type LegendLinearProps = { +export type LegendLinearProps = { steps?: number; } & LegendProps; -/** Linear scales map from continuous inputs to continuous outputs. */ -export default function LegendLinear({ - scale, - domain: inputDomain, - steps = 5, - ...restProps -}: LegendLinearProps) { - const domain = inputDomain || defaultDomain({ steps, scale }); - return scale={scale} domain={domain} {...restProps} />; -} - -function defaultDomain({ +function defaultDomain({ steps = 5, scale, }: Pick, 'steps' | 'scale'>) { @@ -30,3 +20,14 @@ function defaultDomain({ return acc; }, []); } + +/** Linear scales map from continuous inputs to continuous outputs. */ +export default function LegendLinear({ + scale, + domain: inputDomain, + steps = 5, + ...restProps +}: LegendLinearProps) { + const domain = inputDomain || defaultDomain({ steps, scale }); + return scale={scale} domain={domain} {...restProps} />; +} diff --git a/packages/vx-legend/src/legends/Ordinal.tsx b/packages/vx-legend/src/legends/Ordinal.tsx index ec969581f1..6415c326c6 100644 --- a/packages/vx-legend/src/legends/Ordinal.tsx +++ b/packages/vx-legend/src/legends/Ordinal.tsx @@ -1,11 +1,12 @@ import React from 'react'; import Legend, { LegendProps } from '../legend/Legend'; +import { BaseOutput } from '../types'; -export type LegendOrdinalProps = LegendProps; +export type LegendOrdinalProps = LegendProps; /** Ordinal scales map from strings to an Output type. */ -export default function LegendOrdinal( +export default function LegendOrdinal( props: LegendOrdinalProps, ) { - return ; + return {...props} />; } diff --git a/packages/vx-legend/src/legends/Quantile.tsx b/packages/vx-legend/src/legends/Quantile.tsx index f5bb525fc9..606a5ba230 100644 --- a/packages/vx-legend/src/legends/Quantile.tsx +++ b/packages/vx-legend/src/legends/Quantile.tsx @@ -1,15 +1,35 @@ import React from 'react'; + import Legend, { LegendProps } from '../legend/Legend'; -import { QuantileScaleType } from '../types'; -import { LabelFormatterFactory } from '../util/labelTransformFactory'; -import { ScaleQuantile } from 'd3-scale'; +import { LabelFormatterFactory, ScaleQuantile, BaseOutput } from '../types'; -export type LegendQuantileProps = { +export type LegendQuantileProps = { labelDelimiter?: string; scale: ScaleQuantile; -} & Omit, 'scale'>; + labelTransform: LabelFormatterFactory>; +} & Omit, 'scale' | 'labelTransform'>; + +function labelFormatterFactoryFactory({ + labelDelimiter, +}: Pick, 'labelDelimiter'>): LabelFormatterFactory< + number, + Output, + ScaleQuantile +> { + return ({ scale, labelFormat }) => (datum: number, index: number) => { + const [x0, x1] = scale.invertExtent(scale(datum)); + return { + extent: [x0, x1], + text: `${labelFormat(x0, index)} ${labelDelimiter} ${labelFormat(x1, index)}`, + value: scale(x0), + datum, + index, + }; + }; +} -export default function LegendQuantile({ +/** A Quantile scale takes a number input and returns an Output. */ +export default function LegendQuantile({ domain: inputDomain, scale, labelFormat = x => x, @@ -22,7 +42,7 @@ export default function LegendQuantile({ inputLabelTransform || labelFormatterFactoryFactory({ labelDelimiter }); return ( - + > scale={scale} domain={domain} labelFormat={labelFormat} @@ -31,24 +51,3 @@ export default function LegendQuantile({ /> ); } - -function labelFormatterFactoryFactory({ - labelDelimiter, -}: Pick, 'labelDelimiter'>): LabelFormatterFactory< - number, - Output, - QuantileScaleType -> { - return ({ scale, labelFormat }) => { - return (d: Output, i) => { - const [x0, x1] = scale.invertExtent(d); - return { - extent: [x0, x1], - text: `${labelFormat(x0, i)} ${labelDelimiter} ${labelFormat(x1, i)}`, - value: scale(x0), - datum: d, - index: i, - }; - }; - }; -} diff --git a/packages/vx-legend/src/legends/Size.tsx b/packages/vx-legend/src/legends/Size.tsx index 542df09837..acc24f6fc6 100644 --- a/packages/vx-legend/src/legends/Size.tsx +++ b/packages/vx-legend/src/legends/Size.tsx @@ -1,22 +1,40 @@ import React from 'react'; import Legend, { LegendProps } from '../legend/Legend'; +import { BaseInput, BaseOutput, ScaleType } from '../types'; +import labelTransformFactory from '../util/labelTransformFactory'; -// LegendSize.propTypes = { -// scale: PropTypes.func.isRequired, -// domain: PropTypes.array, -// steps: PropTypes.number, -// labelFormat: PropTypes.func, -// labelTransform: PropTypes.func, -// }; +export type LegendSizeProps = { + steps?: number; +} & LegendProps; -export default function LegendSize({ +function defaultDomain({ + steps, + scale, +}: { + steps: number; + scale: ScaleType; +}) { + const domain = scale.domain(); + const start = domain[0]; + const end = domain[domain.length - 1]; + if (typeof start === 'number' && typeof end === 'number') { + const step = (end - start) / (steps - 1); + return new Array(steps).fill(1).reduce((acc, cur, i) => { + acc.push(start + i * step); + return acc; + }, []); + } + return []; +} + +export default function LegendSize({ scale, domain: inputDomain, steps = 5, labelFormat = x => x, - labelTransform = defaultTransform, + labelTransform = labelTransformFactory, ...restProps -}) { +}: LegendSizeProps) { const domain = inputDomain || defaultDomain({ steps, scale }); return ( ); } - -function defaultDomain({ steps, scale }) { - const domain = scale.domain(); - const start = domain[0]; - const end = domain[domain.length - 1]; - const step = (end - start) / (steps - 1); - return new Array(steps).fill(1).reduce((acc, cur, i) => { - acc.push(start + i * step); - return acc; - }, []); -} - -function defaultTransform({ scale, labelFormat }) { - return (d, i) => { - return { - text: `${labelFormat(d, i)}`, - value: scale(d), - datum: d, - index: i, - }; - }; -} diff --git a/packages/vx-legend/src/legends/Threshold.tsx b/packages/vx-legend/src/legends/Threshold.tsx index 5c7fc6a7a6..669dd5e5d6 100644 --- a/packages/vx-legend/src/legends/Threshold.tsx +++ b/packages/vx-legend/src/legends/Threshold.tsx @@ -1,37 +1,80 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import Legend from './Legend'; +import Legend, { LegendProps } from '../legend/Legend'; +import { BaseInput, BaseOutput, ScaleThreshold, LabelFormatterFactory } from '../types'; -LegendThreshold.propTypes = { - scale: PropTypes.func.isRequired, - domain: PropTypes.array, - labelTransform: PropTypes.func, - labelFormat: PropTypes.func, - labelDelimiter: PropTypes.string, - labelLower: PropTypes.string, - labelUpper: PropTypes.string, -}; +const formatZero = (label: unknown) => (label === 0 ? '0' : label || ''); -export default function LegendThreshold({ +export type LegendThresholdProps = { + labelDelimiter?: string; + labelLower?: string; + labelUpper?: string; + scale: ScaleThreshold; + labelTransform: LabelFormatterFactory>; +} & LegendProps; + +/** Default transform implicitly assumes that Datum is of type number. */ +function defaultTransform({ + labelDelimiter, + labelLower, + labelUpper, +}: Pick< + LegendThresholdProps, + 'labelDelimiter' | 'labelLower' | 'labelUpper' +>): LabelFormatterFactory> { + return ({ scale, labelFormat }) => (d, i) => { + let [x0, x1] = scale.invertExtent(scale(d)); + let delimiter = ` ${labelDelimiter} `; + let value; + + if (x0 !== 0 && !x0 && (x1 === 0 || !!x1)) { + // lower threshold + value = x1 - 1; + delimiter = labelLower || delimiter; + } else if ((x0 === 0 || !!x0) && (x1 === 0 || !!x1)) { + // threshold step + value = x0; + } else if (!x1 && (x0 === 0 || !!x0)) { + // upper threshold + value = x0 + scale.domain()[1]; + x1 = x0; + x0 = undefined; + delimiter = labelUpper || delimiter; + } + + return { + extent: [x0, x1], + text: `${formatZero(labelFormat(x0 || d, i))}${delimiter}${formatZero( + labelFormat(x1 || d, i), + )}`, + value: scale(value || d), + datum: d, + index: i, + }; + }; +} + +export default function LegendThreshold({ scale, domain: inputDomain, - labelFormat = x => x, + labelFormat = (d: Datum) => d, labelTransform: inputLabelTransform, labelDelimiter = 'to', labelLower = 'Less than ', labelUpper = 'More than ', ...restProps -}) { - const domain = inputDomain || scale.range(); +}: LegendThresholdProps) { + const domain = inputDomain || (scale.domain() as Datum[]); // this was .range? + const labelTransform = inputLabelTransform || - defaultTransform({ + defaultTransform({ labelDelimiter, labelLower, labelUpper, }); + return ( - > scale={scale} domain={domain} labelFormat={labelFormat} @@ -40,39 +83,3 @@ export default function LegendThreshold({ /> ); } - -function defaultTransform({ labelDelimiter, labelLower, labelUpper }) { - return ({ scale, labelFormat }) => { - function format(_labelFormat, value, i) { - const formattedValue = _labelFormat(value, i); - if (formattedValue === 0) return '0'; - return formattedValue || ''; - } - return (d, i) => { - let [x0, x1] = scale.invertExtent(d); - let delimiter = ` ${labelDelimiter} `; - let value; - if (x0 !== 0 && !x0 && (x1 === 0 || !!x1)) { - // lower threshold - value = x1 - 1; - delimiter = labelLower; - } else if ((x0 === 0 || !!x0) && (x1 === 0 || !!x1)) { - // threshold step - value = x0; - } else if (!x1 && (x0 === 0 || !!x0)) { - // upper threshold - value = x0 + scale.domain()[1]; - x1 = x0; - x0 = undefined; - delimiter = labelUpper; - } - return { - extent: [x0, x1], - text: `${format(labelFormat, x0, i)}${delimiter}${format(labelFormat, x1, i)}`, - value: scale(value), - datum: d, - index: i, - }; - }; - }; -} diff --git a/packages/vx-legend/src/shapes/Circle.tsx b/packages/vx-legend/src/shapes/Circle.tsx index 78a1124909..f3494deca5 100644 --- a/packages/vx-legend/src/shapes/Circle.tsx +++ b/packages/vx-legend/src/shapes/Circle.tsx @@ -1,14 +1,11 @@ import React from 'react'; import { Group } from '@vx/group'; -type CircleProps = React.SVGProps; -type SVGProps = React.SVGProps; - export type ShapeCircleProps = { - fill: CircleProps['fill']; - width: SVGProps['width']; - height: SVGProps['height']; - style: CircleProps['style']; + fill?: string; + width?: string | number; + height?: string | number; + style?: React.CSSProperties; }; export default function ShapeCircle({ fill, width, height, style }: ShapeCircleProps) { diff --git a/packages/vx-legend/src/shapes/Rect.tsx b/packages/vx-legend/src/shapes/Rect.tsx index 630a253e1c..a94d7281e2 100644 --- a/packages/vx-legend/src/shapes/Rect.tsx +++ b/packages/vx-legend/src/shapes/Rect.tsx @@ -1,10 +1,10 @@ import React from 'react'; export type ShapeRectProps = { - fill: React.CSSProperties['background']; - width: React.CSSProperties['width']; - height: React.CSSProperties['height']; - style: React.CSSProperties; + fill?: string | number; + width?: string | number; + height?: string | number; + style?: React.CSSProperties; }; export default function ShapeRect({ fill, width, height, style }: ShapeRectProps) { diff --git a/packages/vx-legend/src/types/index.ts b/packages/vx-legend/src/types/index.ts index 634fa0700c..7dfcf67fa5 100644 --- a/packages/vx-legend/src/types/index.ts +++ b/packages/vx-legend/src/types/index.ts @@ -2,20 +2,21 @@ import { ScaleLinear, ScaleOrdinal, ScaleBand, ScaleThreshold, ScaleQuantile } f export { ScaleLinear, ScaleOrdinal, ScaleBand, ScaleThreshold, ScaleQuantile }; -export type ScaleType< - Input extends string | number | Date, - Output extends string | number | Date -> = +export type BaseInput = string | number | Date; +export type BaseOutput = string | number | Date; + +export type ScaleType = | ScaleLinear // number input, number output | ScaleOrdinal // string input, any output | ScaleBand // string input, number output | ScaleThreshold | ScaleQuantile; -export type LabelFormatterFactory = (args: { - scale: ScaleType; - labelFormat: LabelFormatter; -}) => ItemTransformer; +export type LabelFormatterFactory< + Datum extends BaseInput, + Output extends BaseOutput, + Scale extends ScaleType = ScaleType +> = (args: { scale: Scale; labelFormat: LabelFormatter }) => ItemTransformer; export type LabelFormatter = ( item: Datum, @@ -26,7 +27,7 @@ export type FormattedLabel = { datum: Datum; index: number; text: string; - value: Output; + value?: Output; } & ExtraAttributes; export type ItemTransformer = ( @@ -34,7 +35,20 @@ export type ItemTransformer = ( itemIndex: number, ) => FormattedLabel; -export type LegendShape = 'rect' | 'circle' | React.FunctionComponent | React.ComponentClass; +export type RenderShapeProvidedProps = { + width?: string | number; + height?: string | number; + label?: FormattedLabel; + fill?: string | number; + size?: string | number; + style?: React.CSSProperties | React.SVGProps; +}; + +export type LegendShape = + | 'rect' + | 'circle' + | React.FC> + | React.ComponentClass>; export type FillAccessor = ( label: FormattedLabel, diff --git a/packages/vx-legend/src/util/labelTransformFactory.ts b/packages/vx-legend/src/util/labelTransformFactory.ts index cd22fed2d1..0b4922ed9a 100644 --- a/packages/vx-legend/src/util/labelTransformFactory.ts +++ b/packages/vx-legend/src/util/labelTransformFactory.ts @@ -1,17 +1,22 @@ -import { LabelFormatter, ScaleType, ItemTransformer } from '../types'; +import { LabelFormatter, ScaleType, ItemTransformer, BaseInput, BaseOutput } from '../types'; /** Returns a function which takes a Datum and index as input, and returns a formatted label object. */ -export default function labelTransformFactory({ +export default function labelTransformFactory< + Datum extends BaseInput, + Output extends BaseOutput, + Scale extends ScaleType = ScaleType +>({ scale, labelFormat, }: { - scale: ScaleType; + scale: Scale; labelFormat: LabelFormatter; }): ItemTransformer { return (d, i) => ({ datum: d, index: i, text: `${labelFormat(d, i)}`, + // @ts-ignore value: scale(d), }); } diff --git a/packages/vx-legend/src/util/renderShape.ts b/packages/vx-legend/src/util/renderShape.ts index d675332edd..08d122230d 100644 --- a/packages/vx-legend/src/util/renderShape.ts +++ b/packages/vx-legend/src/util/renderShape.ts @@ -1,28 +1,40 @@ import React from 'react'; -import ShapeRect from '../shapes/Rect'; -import ShapeCircle from '../shapes/Circle'; -import valueOrIdentity from './valueOrIdentity'; + +import RectShape from '../shapes/Rect'; +import CircleShape from '../shapes/Circle'; + import { LegendShape, FormattedLabel, FillAccessor, SizeAccessor, ShapeStyleAccessor, + BaseInput, + BaseOutput, } from '../types'; -interface RenderShape { - shape?: LegendShape; +type RenderShapeArgs = { + shape?: LegendShape; label: FormattedLabel; fill?: FillAccessor; size?: SizeAccessor; shapeStyle?: ShapeStyleAccessor; - width?: React.CSSProperties['width']; - height?: React.CSSProperties['height']; -} + width?: string | number; + height?: string | number; +}; + +export type RenderShapeProvidedProps = { + width?: string | number; + height?: string | number; + label?: FormattedLabel; + fill?: string | number; + size?: string | number; + style?: React.CSSProperties | React.SVGProps; +}; const NO_OP = () => undefined; -export default function renderShape({ +export default function renderShape({ shape = 'rect', fill = NO_OP, size = NO_OP, @@ -30,8 +42,8 @@ export default function renderShape({ height, label, shapeStyle = NO_OP, -}: RenderShape) { - const props = { +}: RenderShapeArgs) { + const props: RenderShapeProvidedProps = { width, height, label, @@ -39,17 +51,18 @@ export default function renderShape({ size: size({ ...label }), style: shapeStyle({ ...label }), }; + if (typeof shape === 'string') { if (shape === 'rect') { - return ; + return ; } - return ; + return ; } if (React.isValidElement(shape)) { return React.cloneElement(shape, props); } if (shape) { - return React.createElement(shape, props as React.Attributes); + return React.createElement(shape, props); } return null; } diff --git a/packages/vx-legend/src/util/valueOrIdentity.ts b/packages/vx-legend/src/util/valueOrIdentity.ts index 469c805ae3..ad13cf19a9 100644 --- a/packages/vx-legend/src/util/valueOrIdentity.ts +++ b/packages/vx-legend/src/util/valueOrIdentity.ts @@ -1,6 +1,12 @@ export type ValueOrIdentity = T | { value?: T }; -export default function valueOrIdentity(x: ValueOrIdentity): T { - if (x && 'value' in x && typeof x.value !== 'undefined') return x.value; - return x as T; +/** Returns an object's value if defined, or the object. */ +export default function valueOrIdentity(_: ValueOrIdentity): T { + if (_ && 'value' in _ && typeof _.value !== 'undefined') return _.value; + return _ as T; +} + +/** Returns an object's value if defined, or the object, coerced to a string. */ +export function valueOrIdentityString(_: ValueOrIdentity): string { + return String(valueOrIdentity(_)); } From 01aee0fdb018c508be239ba0587a54558a1e5add Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 23 Oct 2019 17:10:55 -0700 Subject: [PATCH 04/22] typescript(vx-legend): fix shape types --- packages/vx-legend/src/legend/LegendShape.tsx | 4 ++-- packages/vx-legend/src/shapes/Circle.tsx | 2 +- packages/vx-legend/src/shapes/Rect.tsx | 2 +- packages/vx-legend/src/types/index.ts | 9 ++++++--- packages/vx-legend/src/util/renderShape.ts | 15 +++------------ 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/packages/vx-legend/src/legend/LegendShape.tsx b/packages/vx-legend/src/legend/LegendShape.tsx index 5541b1edf1..84d08ec0fe 100644 --- a/packages/vx-legend/src/legend/LegendShape.tsx +++ b/packages/vx-legend/src/legend/LegendShape.tsx @@ -1,12 +1,12 @@ import React from 'react'; import ShapeRect from '../shapes/Rect'; import renderShape from '../util/renderShape'; -import { FormattedLabel, LegendShape, BaseInput, BaseOutput } from '../types'; +import { FormattedLabel, LegendShape as LegendShapeType, BaseInput, BaseOutput } from '../types'; export type LegendShapeProps = { label: FormattedLabel; margin?: string | number; - shape?: LegendShape; + shape?: LegendShapeType; fill?: (label: FormattedLabel) => any; size?: (label: FormattedLabel) => any; shapeStyle?: (label: FormattedLabel) => any; diff --git a/packages/vx-legend/src/shapes/Circle.tsx b/packages/vx-legend/src/shapes/Circle.tsx index f3494deca5..f800ab5e63 100644 --- a/packages/vx-legend/src/shapes/Circle.tsx +++ b/packages/vx-legend/src/shapes/Circle.tsx @@ -16,7 +16,7 @@ export default function ShapeCircle({ fill, width, height, style }: ShapeCircleP return ( - + ); diff --git a/packages/vx-legend/src/shapes/Rect.tsx b/packages/vx-legend/src/shapes/Rect.tsx index a94d7281e2..40693434ad 100644 --- a/packages/vx-legend/src/shapes/Rect.tsx +++ b/packages/vx-legend/src/shapes/Rect.tsx @@ -1,7 +1,7 @@ import React from 'react'; export type ShapeRectProps = { - fill?: string | number; + fill?: string; width?: string | number; height?: string | number; style?: React.CSSProperties; diff --git a/packages/vx-legend/src/types/index.ts b/packages/vx-legend/src/types/index.ts index 7dfcf67fa5..7ae11dac66 100644 --- a/packages/vx-legend/src/types/index.ts +++ b/packages/vx-legend/src/types/index.ts @@ -1,3 +1,5 @@ +// eslint doesn't know about @types/d3-scale +// eslint-disable-next-line import/no-extraneous-dependencies import { ScaleLinear, ScaleOrdinal, ScaleBand, ScaleThreshold, ScaleQuantile } from 'd3-scale'; export { ScaleLinear, ScaleOrdinal, ScaleBand, ScaleThreshold, ScaleQuantile }; @@ -39,9 +41,10 @@ export type RenderShapeProvidedProps = { width?: string | number; height?: string | number; label?: FormattedLabel; - fill?: string | number; + fill?: string; size?: string | number; - style?: React.CSSProperties | React.SVGProps; + // TODO: ideally this would support SVGProps, but this is invalid for Rect/Circle shapes + style?: React.CSSProperties; }; export type LegendShape = @@ -60,4 +63,4 @@ export type SizeAccessor = ( export type ShapeStyleAccessor = ( label: FormattedLabel, -) => React.CSSProperties | React.SVGProps | undefined; +) => React.CSSProperties | undefined; // TODO: ideally this would support SVGProps, but this is invalid for Rect/Circle shapes diff --git a/packages/vx-legend/src/util/renderShape.ts b/packages/vx-legend/src/util/renderShape.ts index 08d122230d..982a45b5b2 100644 --- a/packages/vx-legend/src/util/renderShape.ts +++ b/packages/vx-legend/src/util/renderShape.ts @@ -1,5 +1,4 @@ import React from 'react'; - import RectShape from '../shapes/Rect'; import CircleShape from '../shapes/Circle'; @@ -11,6 +10,7 @@ import { ShapeStyleAccessor, BaseInput, BaseOutput, + RenderShapeProvidedProps, } from '../types'; type RenderShapeArgs = { @@ -23,15 +23,6 @@ type RenderShapeArgs = { height?: string | number; }; -export type RenderShapeProvidedProps = { - width?: string | number; - height?: string | number; - label?: FormattedLabel; - fill?: string | number; - size?: string | number; - style?: React.CSSProperties | React.SVGProps; -}; - const NO_OP = () => undefined; export default function renderShape({ @@ -54,9 +45,9 @@ export default function renderShape; + return React.createElement(RectShape, props); } - return ; + return React.createElement(CircleShape, props); } if (React.isValidElement(shape)) { return React.cloneElement(shape, props); From 85cb81d05873aa4212821f4b89fa99860549c9d0 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Sat, 26 Oct 2019 15:40:59 -0700 Subject: [PATCH 05/22] fix(vx-legend): optional types, import from @vx/scale directly --- packages/vx-legend/src/legend/Legend.tsx | 4 ++-- packages/vx-legend/test/Legend.test.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vx-legend/src/legend/Legend.tsx b/packages/vx-legend/src/legend/Legend.tsx index ecb4853809..fdcdb1848b 100644 --- a/packages/vx-legend/src/legend/Legend.tsx +++ b/packages/vx-legend/src/legend/Legend.tsx @@ -55,9 +55,9 @@ export type LegendProps< /** Margin of legend items. */ itemMargin?: string | number; /** Flex direction of the legend itself. */ - direction: FlexDirection; + direction?: FlexDirection; /** Flex direction of legend items. */ - itemDirection: FlexDirection; + itemDirection?: FlexDirection; /** Legend item fill accessor function. */ fill?: (label: FormattedLabel) => string | number | undefined; /** Legend item size accessor function. */ diff --git a/packages/vx-legend/test/Legend.test.tsx b/packages/vx-legend/test/Legend.test.tsx index d25c509a0a..de29f5f713 100644 --- a/packages/vx-legend/test/Legend.test.tsx +++ b/packages/vx-legend/test/Legend.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { scaleLinear } from '@vx/scale'; import { Legend } from '../src'; -import { scaleLinear } from '../../vx-scale/src/index.ts'; const defaultProps = { - scale: scaleLinear({ + scale: scaleLinear({ rangeRound: [10, 0], domain: [0, 10], }), From cc383eb5636605244e7f5e19f294543342188dfd Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Sat, 26 Oct 2019 15:41:49 -0700 Subject: [PATCH 06/22] deps(vx-legend): add @vx/scale dev dep --- packages/vx-legend/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/vx-legend/package.json b/packages/vx-legend/package.json index c46bff1134..d6ad064da6 100644 --- a/packages/vx-legend/package.json +++ b/packages/vx-legend/package.json @@ -43,5 +43,8 @@ "@vx/group": "0.0.192", "classnames": "^2.2.5", "prop-types": "^15.5.10" + }, + "devDependencies": { + "@vx/scale": "^0.0.192" } } From 8887760c0ea6891b6f511967929a9775b9c1b311 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 30 Oct 2019 19:17:38 -0700 Subject: [PATCH 07/22] scripts(build-one): use --esm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index caa172c553..402c60b178 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "private": true, "scripts": { "build": "yarn run babel && yarn run type:dts", - "build-one": "nimbus babel --clean", + "build-one": "nimbus babel --clean --esm", "babel": "yarn run babel:cjs && yarn run babel:esm", "babel:cjs": "nimbus babel --clean --workspaces=\"@vx/!(demo)\"", "babel:esm": "nimbus babel --clean --workspaces=\"@vx/!(demo)\" --esm", From d095943db82cead177a5cd0bcb23c80284d6d6db Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Wed, 30 Oct 2019 19:19:25 -0700 Subject: [PATCH 08/22] fix(vx-legend) legend/* -> legends/Legend/*, misc fixes --- .../vx-demo/src/components/tiles/legends.js | 2 ++ packages/vx-legend/src/index.ts | 8 ++--- .../{legend => legends/Legend}/LegendItem.tsx | 0 .../Legend}/LegendLabel.tsx | 0 .../Legend}/LegendShape.tsx | 6 ++-- .../Legend.tsx => legends/Legend/index.tsx} | 6 ++-- packages/vx-legend/src/legends/Linear.tsx | 2 +- packages/vx-legend/src/legends/Ordinal.tsx | 2 +- packages/vx-legend/src/legends/Quantile.tsx | 7 ++-- packages/vx-legend/src/legends/Size.tsx | 2 +- packages/vx-legend/src/legends/Threshold.tsx | 32 +++++++++---------- packages/vx-legend/src/types/index.ts | 19 +++++++---- .../vx-legend/src/util/valueOrIdentity.ts | 2 +- 13 files changed, 48 insertions(+), 40 deletions(-) rename packages/vx-legend/src/{legend => legends/Legend}/LegendItem.tsx (100%) rename packages/vx-legend/src/{legend => legends/Legend}/LegendLabel.tsx (100%) rename packages/vx-legend/src/{legend => legends/Legend}/LegendShape.tsx (89%) rename packages/vx-legend/src/{legend/Legend.tsx => legends/Legend/index.tsx} (95%) diff --git a/packages/vx-demo/src/components/tiles/legends.js b/packages/vx-demo/src/components/tiles/legends.js index 9e15c4c49b..4e0ba01200 100644 --- a/packages/vx-demo/src/components/tiles/legends.js +++ b/packages/vx-demo/src/components/tiles/legends.js @@ -40,6 +40,8 @@ const thresholdScale = scaleThreshold({ range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], }); +global.s = thresholdScale; + const ordinalColorScale = scaleOrdinal({ domain: ['a', 'b', 'c', 'd'], range: ['#66d981', '#71f5ef', '#4899f1', '#7d81f6'], diff --git a/packages/vx-legend/src/index.ts b/packages/vx-legend/src/index.ts index a9e1094d39..6797a3e873 100644 --- a/packages/vx-legend/src/index.ts +++ b/packages/vx-legend/src/index.ts @@ -1,9 +1,9 @@ -export { default as Legend } from './legend/Legend'; +export { default as Legend } from './legends/Legend'; export { default as LegendQuantile } from './legends/Quantile'; export { default as LegendLinear } from './legends/Linear'; export { default as LegendOrdinal } from './legends/Ordinal'; export { default as LegendThreshold } from './legends/Threshold'; export { default as LegendSize } from './legends/Size'; -export { default as LegendItem } from './legend/LegendItem'; -export { default as LegendLabel } from './legend/LegendLabel'; -export { default as LegendShape } from './legend/LegendShape'; +export { default as LegendItem } from './legends/Legend/LegendItem'; +export { default as LegendLabel } from './legends/Legend/LegendLabel'; +export { default as LegendShape } from './legends/Legend/LegendShape'; diff --git a/packages/vx-legend/src/legend/LegendItem.tsx b/packages/vx-legend/src/legends/Legend/LegendItem.tsx similarity index 100% rename from packages/vx-legend/src/legend/LegendItem.tsx rename to packages/vx-legend/src/legends/Legend/LegendItem.tsx diff --git a/packages/vx-legend/src/legend/LegendLabel.tsx b/packages/vx-legend/src/legends/Legend/LegendLabel.tsx similarity index 100% rename from packages/vx-legend/src/legend/LegendLabel.tsx rename to packages/vx-legend/src/legends/Legend/LegendLabel.tsx diff --git a/packages/vx-legend/src/legend/LegendShape.tsx b/packages/vx-legend/src/legends/Legend/LegendShape.tsx similarity index 89% rename from packages/vx-legend/src/legend/LegendShape.tsx rename to packages/vx-legend/src/legends/Legend/LegendShape.tsx index 84d08ec0fe..000c35b40a 100644 --- a/packages/vx-legend/src/legend/LegendShape.tsx +++ b/packages/vx-legend/src/legends/Legend/LegendShape.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import ShapeRect from '../shapes/Rect'; -import renderShape from '../util/renderShape'; -import { FormattedLabel, LegendShape as LegendShapeType, BaseInput, BaseOutput } from '../types'; +import ShapeRect from '../../shapes/Rect'; +import renderShape from '../../util/renderShape'; +import { FormattedLabel, LegendShape as LegendShapeType, BaseInput, BaseOutput } from '../../types'; export type LegendShapeProps = { label: FormattedLabel; diff --git a/packages/vx-legend/src/legend/Legend.tsx b/packages/vx-legend/src/legends/Legend/index.tsx similarity index 95% rename from packages/vx-legend/src/legend/Legend.tsx rename to packages/vx-legend/src/legends/Legend/index.tsx index fdcdb1848b..615c5cad87 100644 --- a/packages/vx-legend/src/legend/Legend.tsx +++ b/packages/vx-legend/src/legends/Legend/index.tsx @@ -3,8 +3,8 @@ import cx from 'classnames'; import LegendItem from './LegendItem'; import LegendLabel from './LegendLabel'; import LegendShape from './LegendShape'; -import valueOrIdentity, { valueOrIdentityString } from '../util/valueOrIdentity'; -import labelTransformFactory from '../util/labelTransformFactory'; +import valueOrIdentity, { valueOrIdentityString } from '../../util/valueOrIdentity'; +import labelTransformFactory from '../../util/labelTransformFactory'; import { BaseInput, BaseOutput, @@ -13,7 +13,7 @@ import { LabelFormatter, LabelFormatterFactory, LegendShape as LegendShapeType, -} from '../types'; +} from '../../types'; type FlexDirection = | 'inherit' diff --git a/packages/vx-legend/src/legends/Linear.tsx b/packages/vx-legend/src/legends/Linear.tsx index 9584eb0849..8d00e97c1f 100644 --- a/packages/vx-legend/src/legends/Linear.tsx +++ b/packages/vx-legend/src/legends/Linear.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Legend, { LegendProps } from '../legend/Legend'; +import Legend, { LegendProps } from './Legend'; import { BaseOutput } from '../types'; export type LegendLinearProps = { diff --git a/packages/vx-legend/src/legends/Ordinal.tsx b/packages/vx-legend/src/legends/Ordinal.tsx index 6415c326c6..e1942c4723 100644 --- a/packages/vx-legend/src/legends/Ordinal.tsx +++ b/packages/vx-legend/src/legends/Ordinal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Legend, { LegendProps } from '../legend/Legend'; +import Legend, { LegendProps } from './Legend'; import { BaseOutput } from '../types'; export type LegendOrdinalProps = LegendProps; diff --git a/packages/vx-legend/src/legends/Quantile.tsx b/packages/vx-legend/src/legends/Quantile.tsx index 606a5ba230..7043d16eab 100644 --- a/packages/vx-legend/src/legends/Quantile.tsx +++ b/packages/vx-legend/src/legends/Quantile.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import Legend, { LegendProps } from '../legend/Legend'; +import Legend, { LegendProps } from './Legend'; import { LabelFormatterFactory, ScaleQuantile, BaseOutput } from '../types'; export type LegendQuantileProps = { labelDelimiter?: string; + labelTransform?: LabelFormatterFactory>; scale: ScaleQuantile; - labelTransform: LabelFormatterFactory>; } & Omit, 'scale' | 'labelTransform'>; function labelFormatterFactoryFactory({ @@ -37,7 +37,8 @@ export default function LegendQuantile({ labelDelimiter = '-', ...restProps }: LegendQuantileProps) { - const domain = inputDomain || scale.domain(); + // transform range into input values because it may contain more elements + const domain = inputDomain || scale.range().map(output => scale.invertExtent(output)[0]); const labelTransform = inputLabelTransform || labelFormatterFactoryFactory({ labelDelimiter }); diff --git a/packages/vx-legend/src/legends/Size.tsx b/packages/vx-legend/src/legends/Size.tsx index acc24f6fc6..c38bc6c394 100644 --- a/packages/vx-legend/src/legends/Size.tsx +++ b/packages/vx-legend/src/legends/Size.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Legend, { LegendProps } from '../legend/Legend'; +import Legend, { LegendProps } from './Legend'; import { BaseInput, BaseOutput, ScaleType } from '../types'; import labelTransformFactory from '../util/labelTransformFactory'; diff --git a/packages/vx-legend/src/legends/Threshold.tsx b/packages/vx-legend/src/legends/Threshold.tsx index 669dd5e5d6..e4a7464fe0 100644 --- a/packages/vx-legend/src/legends/Threshold.tsx +++ b/packages/vx-legend/src/legends/Threshold.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Legend, { LegendProps } from '../legend/Legend'; +import Legend, { LegendProps } from './Legend'; import { BaseInput, BaseOutput, ScaleThreshold, LabelFormatterFactory } from '../types'; const formatZero = (label: unknown) => (label === 0 ? '0' : label || ''); @@ -8,34 +8,34 @@ export type LegendThresholdProps>; scale: ScaleThreshold; - labelTransform: LabelFormatterFactory>; } & LegendProps; /** Default transform implicitly assumes that Datum is of type number. */ -function defaultTransform({ +function defaultTransform({ labelDelimiter, labelLower, labelUpper, }: Pick< - LegendThresholdProps, + LegendThresholdProps, 'labelDelimiter' | 'labelLower' | 'labelUpper' ->): LabelFormatterFactory> { +>): LabelFormatterFactory> { return ({ scale, labelFormat }) => (d, i) => { - let [x0, x1] = scale.invertExtent(scale(d)); + let [x0, x1] = scale.invertExtent(scale.range()[i]); let delimiter = ` ${labelDelimiter} `; - let value; + let value: number | Datum | undefined; - if (x0 !== 0 && !x0 && (x1 === 0 || !!x1)) { + if (typeof x1 === 'number' && x0 !== 0 && !x0 && (x1 === 0 || !!x1)) { // lower threshold value = x1 - 1; delimiter = labelLower || delimiter; } else if ((x0 === 0 || !!x0) && (x1 === 0 || !!x1)) { // threshold step value = x0; - } else if (!x1 && (x0 === 0 || !!x0)) { + } else if (typeof x0 === 'number' && !x1 && (x0 === 0 || !!x0)) { // upper threshold - value = x0 + scale.domain()[1]; + value = x0 + (scale.domain()[1] as number); // if x0,x1 are numbers, so is the domain x1 = x0; x0 = undefined; delimiter = labelUpper || delimiter; @@ -43,10 +43,10 @@ function defaultTransform({ return { extent: [x0, x1], - text: `${formatZero(labelFormat(x0 || d, i))}${delimiter}${formatZero( - labelFormat(x1 || d, i), - )}`, - value: scale(value || d), + text: `${x0 == null ? '' : formatZero(labelFormat(x0 || d, i))}${delimiter}${ + x1 == null ? '' : formatZero(labelFormat(x1 || d, i)) + }`, + value: scale((value as Datum) || d), datum: d, index: i, }; @@ -63,11 +63,11 @@ export default function LegendThreshold) { - const domain = inputDomain || (scale.domain() as Datum[]); // this was .range? + const domain = inputDomain || (scale.domain() as Datum[]); const labelTransform = inputLabelTransform || - defaultTransform({ + defaultTransform({ labelDelimiter, labelLower, labelUpper, diff --git a/packages/vx-legend/src/types/index.ts b/packages/vx-legend/src/types/index.ts index 7ae11dac66..ec35d1c30e 100644 --- a/packages/vx-legend/src/types/index.ts +++ b/packages/vx-legend/src/types/index.ts @@ -1,16 +1,21 @@ // eslint doesn't know about @types/d3-scale // eslint-disable-next-line import/no-extraneous-dependencies -import { ScaleLinear, ScaleOrdinal, ScaleBand, ScaleThreshold, ScaleQuantile } from 'd3-scale'; - -export { ScaleLinear, ScaleOrdinal, ScaleBand, ScaleThreshold, ScaleQuantile }; +import * as d3Scale from 'd3-scale'; export type BaseInput = string | number | Date; export type BaseOutput = string | number | Date; -export type ScaleType = - | ScaleLinear // number input, number output - | ScaleOrdinal // string input, any output - | ScaleBand // string input, number output +export type ScaleLinear = d3Scale.ScaleLinear; +export type ScaleOrdinal = d3Scale.ScaleOrdinal; +export type ScaleBand = d3Scale.ScaleBand; +export type ScaleThreshold = d3Scale.ScaleThreshold; +export type ScaleQuantile = d3Scale.ScaleQuantile; + +// @TODO BaseInput only needed for `ScaleThreshold` +export type ScaleType = + | ScaleLinear + | ScaleOrdinal + | ScaleBand | ScaleThreshold | ScaleQuantile; diff --git a/packages/vx-legend/src/util/valueOrIdentity.ts b/packages/vx-legend/src/util/valueOrIdentity.ts index ad13cf19a9..22d4ee9e16 100644 --- a/packages/vx-legend/src/util/valueOrIdentity.ts +++ b/packages/vx-legend/src/util/valueOrIdentity.ts @@ -2,7 +2,7 @@ export type ValueOrIdentity = T | { value?: T }; /** Returns an object's value if defined, or the object. */ export default function valueOrIdentity(_: ValueOrIdentity): T { - if (_ && 'value' in _ && typeof _.value !== 'undefined') return _.value; + if (_ && typeof _ === 'object' && 'value' in _ && typeof _.value !== 'undefined') return _.value; return _ as T; } From aef49d3ecc66ecd6eadd93547c52a7fc7b579eb5 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 2 Jan 2020 16:43:53 -0800 Subject: [PATCH 09/22] typescript(vx-legend): remove BaseInput/BaseOutput constraint --- .../src/legends/Legend/LegendShape.tsx | 4 +- .../vx-legend/src/legends/Legend/index.tsx | 16 +---- packages/vx-legend/src/legends/Linear.tsx | 18 +++-- packages/vx-legend/src/legends/Ordinal.tsx | 10 ++- packages/vx-legend/src/legends/Quantile.tsx | 10 +-- packages/vx-legend/src/legends/Size.tsx | 16 ++--- packages/vx-legend/src/legends/Threshold.tsx | 71 ++++++++++--------- packages/vx-legend/src/types/index.ts | 28 ++++---- .../src/util/labelTransformFactory.ts | 6 +- packages/vx-legend/src/util/renderShape.ts | 4 +- packages/vx-legend/test/Legend.test.tsx | 2 +- 11 files changed, 93 insertions(+), 92 deletions(-) diff --git a/packages/vx-legend/src/legends/Legend/LegendShape.tsx b/packages/vx-legend/src/legends/Legend/LegendShape.tsx index 000c35b40a..a9d3d420f8 100644 --- a/packages/vx-legend/src/legends/Legend/LegendShape.tsx +++ b/packages/vx-legend/src/legends/Legend/LegendShape.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ShapeRect from '../../shapes/Rect'; import renderShape from '../../util/renderShape'; -import { FormattedLabel, LegendShape as LegendShapeType, BaseInput, BaseOutput } from '../../types'; +import { FormattedLabel, LegendShape as LegendShapeType } from '../../types'; export type LegendShapeProps = { label: FormattedLabel; @@ -14,7 +14,7 @@ export type LegendShapeProps = { height?: string | number; }; -export default function LegendShape({ +export default function LegendShape({ shape = ShapeRect, width, height, diff --git a/packages/vx-legend/src/legends/Legend/index.tsx b/packages/vx-legend/src/legends/Legend/index.tsx index 615c5cad87..27a7f6c232 100644 --- a/packages/vx-legend/src/legends/Legend/index.tsx +++ b/packages/vx-legend/src/legends/Legend/index.tsx @@ -6,8 +6,6 @@ import LegendShape from './LegendShape'; import valueOrIdentity, { valueOrIdentityString } from '../../util/valueOrIdentity'; import labelTransformFactory from '../../util/labelTransformFactory'; import { - BaseInput, - BaseOutput, ScaleType, FormattedLabel, LabelFormatter, @@ -25,11 +23,7 @@ type FlexDirection = | 'row' | 'row-reverse'; -export type LegendProps< - Datum extends BaseInput, - Output extends BaseOutput, - Scale extends ScaleType = ScaleType -> = { +export type LegendProps> = { /** Optional render function override. */ children?: (labels: FormattedLabel[]) => React.ReactNode; /** Classname to be applied to legend container. */ @@ -46,7 +40,7 @@ export type LegendProps< shapeMargin?: string | number; /** Flex-box alignment of legend item labels. */ labelAlign?: string; - /** @TODO handle object type? */ + /** @vx/scale or d3-scale object used to generate the legend items. */ scale: Scale; /** Flex-box flex of legend item labels. */ labelFlex?: string | number; @@ -76,11 +70,7 @@ const defaultStyle = { display: 'flex', }; -export default function Legend< - Datum extends BaseInput, - Output extends BaseOutput, - Scale extends ScaleType = ScaleType ->({ +export default function Legend>({ className, style = defaultStyle, scale, diff --git a/packages/vx-legend/src/legends/Linear.tsx b/packages/vx-legend/src/legends/Linear.tsx index 8d00e97c1f..f48de94042 100644 --- a/packages/vx-legend/src/legends/Linear.tsx +++ b/packages/vx-legend/src/legends/Linear.tsx @@ -1,12 +1,12 @@ import React from 'react'; import Legend, { LegendProps } from './Legend'; -import { BaseOutput } from '../types'; +import { ScaleLinear } from '../types'; -export type LegendLinearProps = { +export type LegendLinearProps = { steps?: number; -} & LegendProps; +} & LegendProps>; -function defaultDomain({ +function defaultDomain({ steps = 5, scale, }: Pick, 'steps' | 'scale'>) { @@ -22,12 +22,18 @@ function defaultDomain({ } /** Linear scales map from continuous inputs to continuous outputs. */ -export default function LegendLinear({ +export default function LegendLinear({ scale, domain: inputDomain, steps = 5, ...restProps }: LegendLinearProps) { const domain = inputDomain || defaultDomain({ steps, scale }); - return scale={scale} domain={domain} {...restProps} />; + return ( + > + scale={scale} + domain={domain} + {...restProps} + /> + ); } diff --git a/packages/vx-legend/src/legends/Ordinal.tsx b/packages/vx-legend/src/legends/Ordinal.tsx index e1942c4723..b9e2536a10 100644 --- a/packages/vx-legend/src/legends/Ordinal.tsx +++ b/packages/vx-legend/src/legends/Ordinal.tsx @@ -1,12 +1,10 @@ import React from 'react'; import Legend, { LegendProps } from './Legend'; -import { BaseOutput } from '../types'; +import { ScaleOrdinal } from '../types'; -export type LegendOrdinalProps = LegendProps; +export type LegendOrdinalProps = LegendProps>; /** Ordinal scales map from strings to an Output type. */ -export default function LegendOrdinal( - props: LegendOrdinalProps, -) { - return {...props} />; +export default function LegendOrdinal(props: LegendOrdinalProps) { + return > {...props} />; } diff --git a/packages/vx-legend/src/legends/Quantile.tsx b/packages/vx-legend/src/legends/Quantile.tsx index 7043d16eab..5f78f73c42 100644 --- a/packages/vx-legend/src/legends/Quantile.tsx +++ b/packages/vx-legend/src/legends/Quantile.tsx @@ -5,16 +5,16 @@ import { LabelFormatterFactory, ScaleQuantile, BaseOutput } from '../types'; export type LegendQuantileProps = { labelDelimiter?: string; - labelTransform?: LabelFormatterFactory>; - scale: ScaleQuantile; -} & Omit, 'scale' | 'labelTransform'>; + labelTransform?: LabelFormatterFactory>; + scale: ScaleQuantile; +} & Omit>, 'scale' | 'labelTransform'>; function labelFormatterFactoryFactory({ labelDelimiter, }: Pick, 'labelDelimiter'>): LabelFormatterFactory< number, Output, - ScaleQuantile + ScaleQuantile > { return ({ scale, labelFormat }) => (datum: number, index: number) => { const [x0, x1] = scale.invertExtent(scale(datum)); @@ -43,7 +43,7 @@ export default function LegendQuantile({ inputLabelTransform || labelFormatterFactoryFactory({ labelDelimiter }); return ( - > + > scale={scale} domain={domain} labelFormat={labelFormat} diff --git a/packages/vx-legend/src/legends/Size.tsx b/packages/vx-legend/src/legends/Size.tsx index c38bc6c394..6af0216bda 100644 --- a/packages/vx-legend/src/legends/Size.tsx +++ b/packages/vx-legend/src/legends/Size.tsx @@ -1,18 +1,18 @@ import React from 'react'; import Legend, { LegendProps } from './Legend'; -import { BaseInput, BaseOutput, ScaleType } from '../types'; +import { ScaleType } from '../types'; import labelTransformFactory from '../util/labelTransformFactory'; -export type LegendSizeProps = { +export type LegendSizeProps = { steps?: number; -} & LegendProps; +} & LegendProps>; -function defaultDomain({ +function defaultDomain({ steps, scale, }: { steps: number; - scale: ScaleType; + scale: ScaleType; }) { const domain = scale.domain(); const start = domain[0]; @@ -27,17 +27,17 @@ function defaultDomain({ return []; } -export default function LegendSize({ +export default function LegendSize({ scale, domain: inputDomain, steps = 5, labelFormat = x => x, labelTransform = labelTransformFactory, ...restProps -}: LegendSizeProps) { +}: LegendSizeProps) { const domain = inputDomain || defaultDomain({ steps, scale }); return ( - > scale={scale} domain={domain} labelFormat={labelFormat} diff --git a/packages/vx-legend/src/legends/Threshold.tsx b/packages/vx-legend/src/legends/Threshold.tsx index e4a7464fe0..3518699b7b 100644 --- a/packages/vx-legend/src/legends/Threshold.tsx +++ b/packages/vx-legend/src/legends/Threshold.tsx @@ -1,19 +1,19 @@ import React from 'react'; import Legend, { LegendProps } from './Legend'; -import { BaseInput, BaseOutput, ScaleThreshold, LabelFormatterFactory } from '../types'; +import { StringNumberDate, ScaleThreshold, LabelFormatterFactory } from '../types'; const formatZero = (label: unknown) => (label === 0 ? '0' : label || ''); -export type LegendThresholdProps = { +export type LegendThresholdProps = { labelDelimiter?: string; labelLower?: string; labelUpper?: string; labelTransform?: LabelFormatterFactory>; scale: ScaleThreshold; -} & LegendProps; +} & LegendProps>; /** Default transform implicitly assumes that Datum is of type number. */ -function defaultTransform({ +function defaultTransform({ labelDelimiter, labelLower, labelUpper, @@ -21,39 +21,47 @@ function defaultTransform({ LegendThresholdProps, 'labelDelimiter' | 'labelLower' | 'labelUpper' >): LabelFormatterFactory> { - return ({ scale, labelFormat }) => (d, i) => { - let [x0, x1] = scale.invertExtent(scale.range()[i]); - let delimiter = ` ${labelDelimiter} `; - let value: number | Datum | undefined; + return ({ scale, labelFormat }) => { + const scaleRange = scale.range(); + const scaleDomain = scale.domain(); - if (typeof x1 === 'number' && x0 !== 0 && !x0 && (x1 === 0 || !!x1)) { - // lower threshold - value = x1 - 1; - delimiter = labelLower || delimiter; - } else if ((x0 === 0 || !!x0) && (x1 === 0 || !!x1)) { - // threshold step - value = x0; - } else if (typeof x0 === 'number' && !x1 && (x0 === 0 || !!x0)) { - // upper threshold - value = x0 + (scale.domain()[1] as number); // if x0,x1 are numbers, so is the domain - x1 = x0; - x0 = undefined; - delimiter = labelUpper || delimiter; - } + return (d, i) => { + // d3 docs specify that for n values in a domain, there should be n+1 values in the range + // https://github.com/d3/d3-scale#threshold_domain + // d comes from the domain, therefore there should always be a matching range value in a valid scale + const [x0, x1]: [Datum | undefined, Datum | undefined] = + scaleRange.length >= i ? scale.invertExtent(scale.range()[i]) : [undefined, undefined]; - return { - extent: [x0, x1], - text: `${x0 == null ? '' : formatZero(labelFormat(x0 || d, i))}${delimiter}${ - x1 == null ? '' : formatZero(labelFormat(x1 || d, i)) - }`, - value: scale((value as Datum) || d), - datum: d, - index: i, + let delimiter = ` ${labelDelimiter} `; + let value: number | Datum | undefined; + + if (x0 == null && typeof x1 === 'number') { + // lower threshold e.g., [undefined, number] + value = x1 - 1; + delimiter = labelLower || delimiter; + } else if (x0 != null && x1 != null) { + // threshold step + value = x0; + } else if (typeof x0 === 'number' && x1 == null) { + // upper threshold e.g., [number, undefined] + value = x0 + (scaleDomain[1] as number); // x0,x1 are from the domain, so if the domain is numeric if x0 is + delimiter = labelUpper || delimiter; + } + + return { + extent: [x0, x1], + text: `${x0 == null ? '' : formatZero(labelFormat(x0 || d, i))}${delimiter}${ + x1 == null ? '' : formatZero(labelFormat(x1 || d, i)) + }`, + value: scale((value as Datum) || d), + datum: d, + index: i, + }; }; }; } -export default function LegendThreshold({ +export default function LegendThreshold({ scale, domain: inputDomain, labelFormat = (d: Datum) => d, @@ -64,7 +72,6 @@ export default function LegendThreshold) { const domain = inputDomain || (scale.domain() as Datum[]); - const labelTransform = inputLabelTransform || defaultTransform({ diff --git a/packages/vx-legend/src/types/index.ts b/packages/vx-legend/src/types/index.ts index ec35d1c30e..3e1eaa1797 100644 --- a/packages/vx-legend/src/types/index.ts +++ b/packages/vx-legend/src/types/index.ts @@ -2,26 +2,28 @@ // eslint-disable-next-line import/no-extraneous-dependencies import * as d3Scale from 'd3-scale'; -export type BaseInput = string | number | Date; -export type BaseOutput = string | number | Date; +export type StringNumberDate = string | number | Date; -export type ScaleLinear = d3Scale.ScaleLinear; +export type ScaleLinear<_ImplicitNumberInput, Output> = d3Scale.ScaleLinear; export type ScaleOrdinal = d3Scale.ScaleOrdinal; -export type ScaleBand = d3Scale.ScaleBand; -export type ScaleThreshold = d3Scale.ScaleThreshold; -export type ScaleQuantile = d3Scale.ScaleQuantile; +export type ScaleBand = d3Scale.ScaleBand; +export type ScaleThreshold = d3Scale.ScaleThreshold< + Input, + Output +>; +export type ScaleQuantile<_ImplicitNumberInput, Output> = d3Scale.ScaleQuantile; -// @TODO BaseInput only needed for `ScaleThreshold` -export type ScaleType = +export type ScaleType = | ScaleLinear | ScaleOrdinal - | ScaleBand - | ScaleThreshold - | ScaleQuantile; + | ScaleBand + | ScaleQuantile + // @ts-ignore @TODO Input needs to be restricted to `string | number | Date` + | ScaleThreshold; export type LabelFormatterFactory< - Datum extends BaseInput, - Output extends BaseOutput, + Datum, + Output, Scale extends ScaleType = ScaleType > = (args: { scale: Scale; labelFormat: LabelFormatter }) => ItemTransformer; diff --git a/packages/vx-legend/src/util/labelTransformFactory.ts b/packages/vx-legend/src/util/labelTransformFactory.ts index 0b4922ed9a..82bbd31f0d 100644 --- a/packages/vx-legend/src/util/labelTransformFactory.ts +++ b/packages/vx-legend/src/util/labelTransformFactory.ts @@ -1,9 +1,9 @@ -import { LabelFormatter, ScaleType, ItemTransformer, BaseInput, BaseOutput } from '../types'; +import { LabelFormatter, ScaleType, ItemTransformer } from '../types'; /** Returns a function which takes a Datum and index as input, and returns a formatted label object. */ export default function labelTransformFactory< - Datum extends BaseInput, - Output extends BaseOutput, + Datum, + Output, Scale extends ScaleType = ScaleType >({ scale, diff --git a/packages/vx-legend/src/util/renderShape.ts b/packages/vx-legend/src/util/renderShape.ts index 982a45b5b2..2383d7698f 100644 --- a/packages/vx-legend/src/util/renderShape.ts +++ b/packages/vx-legend/src/util/renderShape.ts @@ -8,8 +8,6 @@ import { FillAccessor, SizeAccessor, ShapeStyleAccessor, - BaseInput, - BaseOutput, RenderShapeProvidedProps, } from '../types'; @@ -25,7 +23,7 @@ type RenderShapeArgs = { const NO_OP = () => undefined; -export default function renderShape({ +export default function renderShape({ shape = 'rect', fill = NO_OP, size = NO_OP, diff --git a/packages/vx-legend/test/Legend.test.tsx b/packages/vx-legend/test/Legend.test.tsx index de29f5f713..7e025b638b 100644 --- a/packages/vx-legend/test/Legend.test.tsx +++ b/packages/vx-legend/test/Legend.test.tsx @@ -16,7 +16,7 @@ describe('', () => { expect(Legend).toBeDefined(); }); - test('it should default style to display: flex, flex-direction: column ', () => { + test('it should default style to display: flex, flex-direction: column', () => { const wrapper = shallow(); expect(wrapper.prop('style')).toEqual({ display: 'flex', From b45b7b49e9fae8a73fabb2b0eb79bbdcf706bd47 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 2 Jan 2020 17:05:18 -0800 Subject: [PATCH 10/22] deps(vx-legend): update react peerDep to ^16.3.0-0 for Fragments --- packages/vx-legend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vx-legend/package.json b/packages/vx-legend/package.json index d6ad064da6..9d79bd5a65 100644 --- a/packages/vx-legend/package.json +++ b/packages/vx-legend/package.json @@ -31,7 +31,7 @@ }, "homepage": "https://github.com/hshoff/vx#readme", "peerDependencies": { - "react": "^15.0.0-0 || ^16.0.0-0" + "react": "^16.3.0-0" }, "publishConfig": { "access": "public" From 1fcdaf3bae88a2295245a8fe949143ad7607f134 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 2 Jan 2020 17:05:48 -0800 Subject: [PATCH 11/22] types(vx-legend): move FlexDirection to types/ for reuse --- packages/vx-legend/src/legends/Legend/LegendItem.tsx | 11 ++--------- packages/vx-legend/src/legends/Legend/index.tsx | 11 +---------- packages/vx-legend/src/types/index.ts | 10 ++++++++++ 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/packages/vx-legend/src/legends/Legend/LegendItem.tsx b/packages/vx-legend/src/legends/Legend/LegendItem.tsx index c6112edb17..1935fd0979 100644 --- a/packages/vx-legend/src/legends/Legend/LegendItem.tsx +++ b/packages/vx-legend/src/legends/Legend/LegendItem.tsx @@ -1,15 +1,8 @@ import React from 'react'; +import { FlexDirection } from '../../types'; export type LegendItemProps = { - flexDirection?: - | 'inherit' - | 'initial' - | 'revert' - | 'unset' - | 'column' - | 'column-reverse' - | 'row' - | 'row-reverse'; + flexDirection?: FlexDirection; alignItems?: string; margin?: string | number; children?: React.ReactNode; diff --git a/packages/vx-legend/src/legends/Legend/index.tsx b/packages/vx-legend/src/legends/Legend/index.tsx index 27a7f6c232..9ef50f8723 100644 --- a/packages/vx-legend/src/legends/Legend/index.tsx +++ b/packages/vx-legend/src/legends/Legend/index.tsx @@ -6,6 +6,7 @@ import LegendShape from './LegendShape'; import valueOrIdentity, { valueOrIdentityString } from '../../util/valueOrIdentity'; import labelTransformFactory from '../../util/labelTransformFactory'; import { + FlexDirection, ScaleType, FormattedLabel, LabelFormatter, @@ -13,16 +14,6 @@ import { LegendShape as LegendShapeType, } from '../../types'; -type FlexDirection = - | 'inherit' - | 'initial' - | 'revert' - | 'unset' - | 'column' - | 'column-reverse' - | 'row' - | 'row-reverse'; - export type LegendProps> = { /** Optional render function override. */ children?: (labels: FormattedLabel[]) => React.ReactNode; diff --git a/packages/vx-legend/src/types/index.ts b/packages/vx-legend/src/types/index.ts index 3e1eaa1797..5defd61b5b 100644 --- a/packages/vx-legend/src/types/index.ts +++ b/packages/vx-legend/src/types/index.ts @@ -71,3 +71,13 @@ export type SizeAccessor = ( export type ShapeStyleAccessor = ( label: FormattedLabel, ) => React.CSSProperties | undefined; // TODO: ideally this would support SVGProps, but this is invalid for Rect/Circle shapes + +export type FlexDirection = + | 'inherit' + | 'initial' + | 'revert' + | 'unset' + | 'column' + | 'column-reverse' + | 'row' + | 'row-reverse'; From 23ea7c3779df86816751b908b5e511818305513b Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 2 Jan 2020 17:09:20 -0800 Subject: [PATCH 12/22] fix(vx-demo/legends): remove global --- packages/vx-demo/src/components/tiles/legends.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/vx-demo/src/components/tiles/legends.js b/packages/vx-demo/src/components/tiles/legends.js index 4e0ba01200..9e15c4c49b 100644 --- a/packages/vx-demo/src/components/tiles/legends.js +++ b/packages/vx-demo/src/components/tiles/legends.js @@ -40,8 +40,6 @@ const thresholdScale = scaleThreshold({ range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], }); -global.s = thresholdScale; - const ordinalColorScale = scaleOrdinal({ domain: ['a', 'b', 'c', 'd'], range: ['#66d981', '#71f5ef', '#4899f1', '#7d81f6'], From 48eceda0ab3f245e40b85d46f6810bb368ecd3c5 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 2 Jan 2020 17:27:32 -0800 Subject: [PATCH 13/22] fix(vx-legend): remove BaseOutput from Quantile generic --- packages/vx-legend/src/legends/Quantile.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vx-legend/src/legends/Quantile.tsx b/packages/vx-legend/src/legends/Quantile.tsx index 5f78f73c42..796aeac579 100644 --- a/packages/vx-legend/src/legends/Quantile.tsx +++ b/packages/vx-legend/src/legends/Quantile.tsx @@ -1,15 +1,15 @@ import React from 'react'; import Legend, { LegendProps } from './Legend'; -import { LabelFormatterFactory, ScaleQuantile, BaseOutput } from '../types'; +import { LabelFormatterFactory, ScaleQuantile } from '../types'; -export type LegendQuantileProps = { +export type LegendQuantileProps = { labelDelimiter?: string; labelTransform?: LabelFormatterFactory>; scale: ScaleQuantile; } & Omit>, 'scale' | 'labelTransform'>; -function labelFormatterFactoryFactory({ +function labelFormatterFactoryFactory({ labelDelimiter, }: Pick, 'labelDelimiter'>): LabelFormatterFactory< number, @@ -29,7 +29,7 @@ function labelFormatterFactoryFactory({ } /** A Quantile scale takes a number input and returns an Output. */ -export default function LegendQuantile({ +export default function LegendQuantile({ domain: inputDomain, scale, labelFormat = x => x, From af196ae27a5359e3df372ae049b7e6b3f7a594fc Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 2 Jan 2020 17:28:20 -0800 Subject: [PATCH 14/22] typescript(vx-legend): add scales test --- packages/vx-legend/test/scales.test.tsx | 90 +++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 packages/vx-legend/test/scales.test.tsx diff --git a/packages/vx-legend/test/scales.test.tsx b/packages/vx-legend/test/scales.test.tsx new file mode 100644 index 0000000000..4698b35602 --- /dev/null +++ b/packages/vx-legend/test/scales.test.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { + scaleLinear, + scaleBand, + scaleOrdinal, + scaleThreshold, + scaleQuantile, + scaleQuantize, +} from '@vx/scale'; + +import { + Legend, + LegendLinear, + LegendOrdinal, + LegendSize, + LegendThreshold, + LegendQuantile, +} from '../src'; + +// const quantileScale = scaleQuantize({ +// domain: [0, 0.15], +// range: ['#eb4d70', '#f19938', '#6ce18b', '#78f6ef', '#9096f8'], +// }); + +// const thresholdScale = scaleThreshold({ +// domain: [0.01, 0.02, 0.04, 0.06, 0.08, 0.1], +// range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], +// }); + +describe('Legend scales', () => { + it('should render with scaleLinear', () => { + const linearScale = scaleLinear({ + domain: [0, 10], + range: ['#ed4fbb', '#e9a039'], + }); + + expect(shallow()).not.toThrow(); + expect(shallow()).not.toThrow(); + expect(shallow()).not.toThrow(); + }); + + it('should render with scaleOrdinal', () => { + const ordinalScale = scaleOrdinal({ + domain: ['a', 'b', 'c', 'd'], + range: ['#66d981', '#71f5ef', '#4899f1', '#7d81f6'], + }); + + expect(shallow()).not.toThrow(); + expect(shallow()).not.toThrow(); + }); + + it('should render with scaleBand', () => { + const bandScale = scaleBand({ + domain: ['a', 'b', 'c', 'd'], + range: [0, 10], + }); + + expect(shallow()).not.toThrow(); + expect(shallow()).not.toThrow(); + }); + + it('should render with scaleThreshold', () => { + const thresholdScale = scaleThreshold({ + domain: [0.01, 0.02, 0.04, 0.06, 0.08, 0.1], + range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], + }); + + expect(shallow()).not.toThrow(); + expect(shallow()).not.toThrow(); + }); + + it('should render with scaleQuantile', () => { + const quantileScale = scaleQuantile({ + domain: [0.01, 0.02, 0.04, 0.06, 0.08, 0.1], + range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], + }); + + expect(shallow()).not.toThrow(); + }); + + it('should render with scaleQuantize', () => { + const quantileScale = scaleQuantize({ + domain: [0, 0.15], + range: ['#eb4d70', '#f19938', '#6ce18b', '#78f6ef', '#9096f8'], + }); + + expect(shallow()).not.toThrow(); + }); +}); From 0ec77540f2cf423b0c2e960867124fcbc9d368bb Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 3 Jan 2020 11:51:47 -0800 Subject: [PATCH 15/22] fix(vx-legend): fix scale test --- packages/vx-legend/test/scales.test.tsx | 62 +++++++------------------ 1 file changed, 17 insertions(+), 45 deletions(-) diff --git a/packages/vx-legend/test/scales.test.tsx b/packages/vx-legend/test/scales.test.tsx index 4698b35602..e9f230e8ce 100644 --- a/packages/vx-legend/test/scales.test.tsx +++ b/packages/vx-legend/test/scales.test.tsx @@ -1,13 +1,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { - scaleLinear, - scaleBand, - scaleOrdinal, - scaleThreshold, - scaleQuantile, - scaleQuantize, -} from '@vx/scale'; +import { scaleLinear, scaleOrdinal, scaleThreshold, scaleQuantile, scaleQuantize } from '@vx/scale'; import { Legend, @@ -17,74 +10,53 @@ import { LegendThreshold, LegendQuantile, } from '../src'; - -// const quantileScale = scaleQuantize({ -// domain: [0, 0.15], -// range: ['#eb4d70', '#f19938', '#6ce18b', '#78f6ef', '#9096f8'], -// }); - -// const thresholdScale = scaleThreshold({ -// domain: [0.01, 0.02, 0.04, 0.06, 0.08, 0.1], -// range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], -// }); +import { ScaleOrdinal, ScaleThreshold, ScaleQuantile } from '../src/types'; describe('Legend scales', () => { it('should render with scaleLinear', () => { - const linearScale = scaleLinear({ + const linearScale = scaleLinear({ domain: [0, 10], - range: ['#ed4fbb', '#e9a039'], + range: [1, 5, 10, 15, 20], }); expect(shallow()).not.toThrow(); - expect(shallow()).not.toThrow(); + expect(shallow( scale={linearScale} />)).not.toThrow(); expect(shallow()).not.toThrow(); }); it('should render with scaleOrdinal', () => { - const ordinalScale = scaleOrdinal({ + const ordinalScale = scaleOrdinal({ domain: ['a', 'b', 'c', 'd'], range: ['#66d981', '#71f5ef', '#4899f1', '#7d81f6'], }); expect(shallow()).not.toThrow(); - expect(shallow()).not.toThrow(); - }); - - it('should render with scaleBand', () => { - const bandScale = scaleBand({ - domain: ['a', 'b', 'c', 'd'], - range: [0, 10], - }); - - expect(shallow()).not.toThrow(); - expect(shallow()).not.toThrow(); + expect( + shallow(> scale={ordinalScale} />), + ).not.toThrow(); }); it('should render with scaleThreshold', () => { - const thresholdScale = scaleThreshold({ + const thresholdScale = scaleThreshold({ domain: [0.01, 0.02, 0.04, 0.06, 0.08, 0.1], range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], }); expect(shallow()).not.toThrow(); - expect(shallow()).not.toThrow(); + expect( + shallow(> scale={thresholdScale} />), + ).not.toThrow(); }); it('should render with scaleQuantile', () => { - const quantileScale = scaleQuantile({ + const quantileScale = scaleQuantile({ domain: [0.01, 0.02, 0.04, 0.06, 0.08, 0.1], range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], }); expect(shallow()).not.toThrow(); - }); - - it('should render with scaleQuantize', () => { - const quantileScale = scaleQuantize({ - domain: [0, 0.15], - range: ['#eb4d70', '#f19938', '#6ce18b', '#78f6ef', '#9096f8'], - }); - - expect(shallow()).not.toThrow(); + expect( + shallow(> scale={quantileScale} />), + ).not.toThrow(); }); }); From fa0c86bad7065079ded2384f8f54e9cf8d0b8e98 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 3 Jan 2020 12:00:39 -0800 Subject: [PATCH 16/22] fix(vx-legend): remove unused variable in tests --- packages/vx-legend/test/scales.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vx-legend/test/scales.test.tsx b/packages/vx-legend/test/scales.test.tsx index e9f230e8ce..b72957bd1a 100644 --- a/packages/vx-legend/test/scales.test.tsx +++ b/packages/vx-legend/test/scales.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { scaleLinear, scaleOrdinal, scaleThreshold, scaleQuantile, scaleQuantize } from '@vx/scale'; +import { scaleLinear, scaleOrdinal, scaleThreshold, scaleQuantile } from '@vx/scale'; import { Legend, From 43f19d57598f626ec299ccf0a4d0a2c70110b338 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 3 Jan 2020 13:52:22 -0800 Subject: [PATCH 17/22] typescript(vx-legend): add fix for Threshold scale type --- packages/vx-legend/src/legends/Threshold.tsx | 13 ++++++++--- packages/vx-legend/src/types/index.ts | 24 ++++++++++++-------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/vx-legend/src/legends/Threshold.tsx b/packages/vx-legend/src/legends/Threshold.tsx index 3518699b7b..ca0c7cb779 100644 --- a/packages/vx-legend/src/legends/Threshold.tsx +++ b/packages/vx-legend/src/legends/Threshold.tsx @@ -4,13 +4,18 @@ import { StringNumberDate, ScaleThreshold, LabelFormatterFactory } from '../type const formatZero = (label: unknown) => (label === 0 ? '0' : label || ''); -export type LegendThresholdProps = { +export type LegendThresholdProps = LegendProps< + Datum, + Output, + // @ts-ignore @TODO fix `Type 'ScaleThreshold' does not satisfy the constraint 'ScaleType'.ts(2344)` + ScaleThreshold +> & { labelDelimiter?: string; labelLower?: string; labelUpper?: string; + // @ts-ignore @TODO fix `Type 'ScaleThreshold' does not satisfy the constraint 'ScaleType'.ts(2344)` labelTransform?: LabelFormatterFactory>; - scale: ScaleThreshold; -} & LegendProps>; +}; /** Default transform implicitly assumes that Datum is of type number. */ function defaultTransform({ @@ -20,6 +25,7 @@ function defaultTransform({ }: Pick< LegendThresholdProps, 'labelDelimiter' | 'labelLower' | 'labelUpper' + // @ts-ignore @TODO fix `Type 'ScaleThreshold' does not satisfy the constraint 'ScaleType'.ts(2344)` >): LabelFormatterFactory> { return ({ scale, labelFormat }) => { const scaleRange = scale.range(); @@ -81,6 +87,7 @@ export default function LegendThreshold( }); return ( + // @ts-ignore @TODO fix `Type 'ScaleThreshold' does not satisfy the constraint 'ScaleType'.ts(2344)` > scale={scale} domain={domain} diff --git a/packages/vx-legend/src/types/index.ts b/packages/vx-legend/src/types/index.ts index 5defd61b5b..a45165f56d 100644 --- a/packages/vx-legend/src/types/index.ts +++ b/packages/vx-legend/src/types/index.ts @@ -4,22 +4,28 @@ import * as d3Scale from 'd3-scale'; export type StringNumberDate = string | number | Date; +export type ScaleTime<_ImplicitDateOrNumber, Output> = d3Scale.ScaleTime; +export type ScaleBand = d3Scale.ScaleBand; export type ScaleLinear<_ImplicitNumberInput, Output> = d3Scale.ScaleLinear; export type ScaleOrdinal = d3Scale.ScaleOrdinal; -export type ScaleBand = d3Scale.ScaleBand; +export type ScaleQuantile<_ImplicitNumberInput, Output> = d3Scale.ScaleQuantile; export type ScaleThreshold = d3Scale.ScaleThreshold< Input, Output >; -export type ScaleQuantile<_ImplicitNumberInput, Output> = d3Scale.ScaleQuantile; -export type ScaleType = - | ScaleLinear - | ScaleOrdinal - | ScaleBand - | ScaleQuantile - // @ts-ignore @TODO Input needs to be restricted to `string | number | Date` - | ScaleThreshold; +export type ScaleType = Input extends StringNumberDate + ? + | ScaleThreshold // StringNumberDate needed for ScaleThreshold only + | ScaleLinear + | ScaleOrdinal + | ScaleBand + | ScaleQuantile + : + | ScaleLinear + | ScaleOrdinal + | ScaleBand + | ScaleQuantile; export type LabelFormatterFactory< Datum, From 3b29e8789a1841c038c10eb93da27578f05b5c7a Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 3 Jan 2020 14:49:17 -0800 Subject: [PATCH 18/22] typescript(vx-legend): refactor Scale type --- packages/vx-legend/src/legends/Legend/index.tsx | 2 +- packages/vx-legend/src/legends/Threshold.tsx | 4 ---- packages/vx-legend/src/types/index.ts | 10 ++++------ packages/vx-legend/test/scales.test.tsx | 15 +++++++++++++-- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/vx-legend/src/legends/Legend/index.tsx b/packages/vx-legend/src/legends/Legend/index.tsx index 9ef50f8723..27ed7f5d64 100644 --- a/packages/vx-legend/src/legends/Legend/index.tsx +++ b/packages/vx-legend/src/legends/Legend/index.tsx @@ -14,7 +14,7 @@ import { LegendShape as LegendShapeType, } from '../../types'; -export type LegendProps> = { +export type LegendProps> = { /** Optional render function override. */ children?: (labels: FormattedLabel[]) => React.ReactNode; /** Classname to be applied to legend container. */ diff --git a/packages/vx-legend/src/legends/Threshold.tsx b/packages/vx-legend/src/legends/Threshold.tsx index ca0c7cb779..d57af6f291 100644 --- a/packages/vx-legend/src/legends/Threshold.tsx +++ b/packages/vx-legend/src/legends/Threshold.tsx @@ -7,13 +7,11 @@ const formatZero = (label: unknown) => (label === 0 ? '0' : label || ''); export type LegendThresholdProps = LegendProps< Datum, Output, - // @ts-ignore @TODO fix `Type 'ScaleThreshold' does not satisfy the constraint 'ScaleType'.ts(2344)` ScaleThreshold > & { labelDelimiter?: string; labelLower?: string; labelUpper?: string; - // @ts-ignore @TODO fix `Type 'ScaleThreshold' does not satisfy the constraint 'ScaleType'.ts(2344)` labelTransform?: LabelFormatterFactory>; }; @@ -25,7 +23,6 @@ function defaultTransform({ }: Pick< LegendThresholdProps, 'labelDelimiter' | 'labelLower' | 'labelUpper' - // @ts-ignore @TODO fix `Type 'ScaleThreshold' does not satisfy the constraint 'ScaleType'.ts(2344)` >): LabelFormatterFactory> { return ({ scale, labelFormat }) => { const scaleRange = scale.range(); @@ -87,7 +84,6 @@ export default function LegendThreshold( }); return ( - // @ts-ignore @TODO fix `Type 'ScaleThreshold' does not satisfy the constraint 'ScaleType'.ts(2344)` > scale={scale} domain={domain} diff --git a/packages/vx-legend/src/types/index.ts b/packages/vx-legend/src/types/index.ts index a45165f56d..29fb0c00d2 100644 --- a/packages/vx-legend/src/types/index.ts +++ b/packages/vx-legend/src/types/index.ts @@ -4,7 +4,6 @@ import * as d3Scale from 'd3-scale'; export type StringNumberDate = string | number | Date; -export type ScaleTime<_ImplicitDateOrNumber, Output> = d3Scale.ScaleTime; export type ScaleBand = d3Scale.ScaleBand; export type ScaleLinear<_ImplicitNumberInput, Output> = d3Scale.ScaleLinear; export type ScaleOrdinal = d3Scale.ScaleOrdinal; @@ -27,11 +26,10 @@ export type ScaleType = Input extends StringNumberDate | ScaleBand | ScaleQuantile; -export type LabelFormatterFactory< - Datum, - Output, - Scale extends ScaleType = ScaleType -> = (args: { scale: Scale; labelFormat: LabelFormatter }) => ItemTransformer; +export type LabelFormatterFactory> = (args: { + scale: Scale; + labelFormat: LabelFormatter; +}) => ItemTransformer; export type LabelFormatter = ( item: Datum, diff --git a/packages/vx-legend/test/scales.test.tsx b/packages/vx-legend/test/scales.test.tsx index b72957bd1a..8b453dca56 100644 --- a/packages/vx-legend/test/scales.test.tsx +++ b/packages/vx-legend/test/scales.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { scaleLinear, scaleOrdinal, scaleThreshold, scaleQuantile } from '@vx/scale'; +import { scaleBand, scaleLinear, scaleOrdinal, scaleThreshold, scaleQuantile } from '@vx/scale'; import { Legend, @@ -10,7 +10,7 @@ import { LegendThreshold, LegendQuantile, } from '../src'; -import { ScaleOrdinal, ScaleThreshold, ScaleQuantile } from '../src/types'; +import { ScaleBand, ScaleOrdinal, ScaleThreshold, ScaleQuantile } from '../src/types'; describe('Legend scales', () => { it('should render with scaleLinear', () => { @@ -36,6 +36,17 @@ describe('Legend scales', () => { ).not.toThrow(); }); + it('should render with scaleBand', () => { + const bandScale = scaleBand({ + domain: ['a', 'b', 'c', 'd'], + range: [1, 10], + }); + + expect( + shallow(> scale={bandScale} />), + ).not.toThrow(); + }); + it('should render with scaleThreshold', () => { const thresholdScale = scaleThreshold({ domain: [0.01, 0.02, 0.04, 0.06, 0.08, 0.1], From 8c47c9c0542f582313cad0e0fa7d72a08773c508 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 3 Jan 2020 14:50:43 -0800 Subject: [PATCH 19/22] fix(vx-scale): make bandScale domain Generic --- packages/vx-scale/src/scales/band.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/vx-scale/src/scales/band.ts b/packages/vx-scale/src/scales/band.ts index 2e7cf1926b..25157be6e1 100644 --- a/packages/vx-scale/src/scales/band.ts +++ b/packages/vx-scale/src/scales/band.ts @@ -1,15 +1,15 @@ import { scaleBand } from 'd3-scale'; -type Domain = string | { valueOf(): string }; -type Range = number | { valueOf(): number }; +type StringLike = string | { valueOf(): string }; +type Numeric = number | { valueOf(): number }; -export type BandConfig = { +export type BandConfig = { /** Sets the output values of the scale, which are numbers for band scales. */ - range?: [Range, Range]; + range?: [Numeric, Numeric]; /** Sets the output values of the scale while setting its interpolator to round. If the elements are not numbers, they will be coerced to numbers. */ - rangeRound?: [Range, Range]; + rangeRound?: [Numeric, Numeric]; /** Sets the input values of the scale, which are strings for band scales. */ - domain?: Domain[]; + domain?: Datum[]; /** 0-1, determines how any leftover unused space in the range is distributed. 0.5 distributes it equally left and right. */ align?: number; /** 0-1, determines the ratio of the range that is reserved for blank space before the first point and after the last. */ @@ -21,7 +21,7 @@ export type BandConfig = { tickFormat?: unknown; }; -export default ({ +export default function bandScale({ range, rangeRound, domain, @@ -30,8 +30,8 @@ export default ({ paddingOuter, align, tickFormat, -}: BandConfig) => { - const scale = scaleBand(); +}: BandConfig) { + const scale = scaleBand(); if (range) scale.range(range); if (rangeRound) scale.rangeRound(rangeRound); @@ -48,4 +48,4 @@ export default ({ scale.type = 'band'; return scale; -}; +} From d0f6f99040595089b8d013c13b88c39eece6e357 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 3 Jan 2020 14:52:34 -0800 Subject: [PATCH 20/22] typescript(vx-legend): omg i hope this is the last scale fix --- packages/vx-legend/src/legends/Legend/index.tsx | 2 +- packages/vx-legend/src/util/labelTransformFactory.ts | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/vx-legend/src/legends/Legend/index.tsx b/packages/vx-legend/src/legends/Legend/index.tsx index 27ed7f5d64..f6365627e2 100644 --- a/packages/vx-legend/src/legends/Legend/index.tsx +++ b/packages/vx-legend/src/legends/Legend/index.tsx @@ -61,7 +61,7 @@ const defaultStyle = { display: 'flex', }; -export default function Legend>({ +export default function Legend>({ className, style = defaultStyle, scale, diff --git a/packages/vx-legend/src/util/labelTransformFactory.ts b/packages/vx-legend/src/util/labelTransformFactory.ts index 82bbd31f0d..199f747ba1 100644 --- a/packages/vx-legend/src/util/labelTransformFactory.ts +++ b/packages/vx-legend/src/util/labelTransformFactory.ts @@ -1,11 +1,7 @@ import { LabelFormatter, ScaleType, ItemTransformer } from '../types'; /** Returns a function which takes a Datum and index as input, and returns a formatted label object. */ -export default function labelTransformFactory< - Datum, - Output, - Scale extends ScaleType = ScaleType ->({ +export default function labelTransformFactory>({ scale, labelFormat, }: { From c579b41be3e0bcb6ce43da8cf664f1118f4f86e2 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 3 Jan 2020 15:01:09 -0800 Subject: [PATCH 21/22] typescript(vx-legend): last scale fix --- packages/vx-legend/src/legends/Legend/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/vx-legend/src/legends/Legend/index.tsx b/packages/vx-legend/src/legends/Legend/index.tsx index f6365627e2..3feedf7529 100644 --- a/packages/vx-legend/src/legends/Legend/index.tsx +++ b/packages/vx-legend/src/legends/Legend/index.tsx @@ -84,7 +84,10 @@ export default function Legend>( children, ...legendItemProps }: LegendProps) { - const domain = inputDomain || (scale.domain() as Datum[]); + // `Scale extends ScaleType` constraint is tricky + // could consider removing `scale` altogether in the future and making `domain: Datum[]` required + // @ts-ignore doesn't like `.domain()` + const domain = inputDomain || (('domain' in scale ? scale.domain() : []) as Datum[]); const labelFormatter = labelTransform({ scale, labelFormat }); const labels = domain.map(labelFormatter); if (children) return <>{children(labels)}; From a215370855c44cbb8e7cdf8a455200b59f29da83 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 3 Jan 2020 15:12:18 -0800 Subject: [PATCH 22/22] test(vx-legend): use functions in expect().not.toThrow() calls --- packages/vx-legend/test/scales.test.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/vx-legend/test/scales.test.tsx b/packages/vx-legend/test/scales.test.tsx index 8b453dca56..b91ea8a62e 100644 --- a/packages/vx-legend/test/scales.test.tsx +++ b/packages/vx-legend/test/scales.test.tsx @@ -19,9 +19,9 @@ describe('Legend scales', () => { range: [1, 5, 10, 15, 20], }); - expect(shallow()).not.toThrow(); - expect(shallow( scale={linearScale} />)).not.toThrow(); - expect(shallow()).not.toThrow(); + expect(() => shallow()).not.toThrow(); + expect(() => shallow( scale={linearScale} />)).not.toThrow(); + expect(() => shallow()).not.toThrow(); }); it('should render with scaleOrdinal', () => { @@ -30,8 +30,8 @@ describe('Legend scales', () => { range: ['#66d981', '#71f5ef', '#4899f1', '#7d81f6'], }); - expect(shallow()).not.toThrow(); - expect( + expect(() => shallow()).not.toThrow(); + expect(() => shallow(> scale={ordinalScale} />), ).not.toThrow(); }); @@ -42,7 +42,7 @@ describe('Legend scales', () => { range: [1, 10], }); - expect( + expect(() => shallow(> scale={bandScale} />), ).not.toThrow(); }); @@ -53,8 +53,8 @@ describe('Legend scales', () => { range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], }); - expect(shallow()).not.toThrow(); - expect( + expect(() => shallow()).not.toThrow(); + expect(() => shallow(> scale={thresholdScale} />), ).not.toThrow(); }); @@ -65,8 +65,8 @@ describe('Legend scales', () => { range: ['#f2f0f7', '#dadaeb', '#bcbddc', '#9e9ac8', '#756bb1', '#54278f'], }); - expect(shallow()).not.toThrow(); - expect( + expect(() => shallow()).not.toThrow(); + expect(() => shallow(> scale={quantileScale} />), ).not.toThrow(); });