diff --git a/.eslintignore b/.eslintignore index 65b660c23..e6c21f22a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,6 +2,10 @@ node_modules build dist +test +rollup.config.js +vx-demo +scripts bundle.js test/tmp *.log diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..e2169831d --- /dev/null +++ b/.eslintrc @@ -0,0 +1,37 @@ +{ + "extends": "airbnb", + "env": { + "node": true, + "es6": true, + "browser": true + }, + "rules": { + "linebreak-style": 0, + "operator-linebreak": 0, + "comma-dangle": 0, + "no-use-before-define": 0, + "object-curly-newline": 0, + "indent": 0, + "arrow-parens": 0, + "consistent-return": 0, + "no-restricted-syntax": 0, + "no-nested-ternary": 0, + "no-bitwise": 0, + "no-unused-vars": 0, + "no-mixed-operators": 0, + "no-param-reassign": 0, + "no-extra-boolean-cast": 0, + "prefer-destructuring": 0, + "lines-between-class-members": 0, + "arrow-body-style": 0, + "import/prefer-default-export": 0, + "import/order": 0, + "react/sort-comp": 0, + "react/jsx-filename-extension": 0, + "react/require-default-props": 0, + "react/forbid-prop-types": 0, + "react/destructuring-assignment": 0, + "react/no-array-index-key": 0, + "react/no-children-prop": 0 + } +} diff --git a/package.json b/package.json index 72c9ac035..4c9891119 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "docs": "node ./scripts/docs/index.js", "prepare-release": "git checkout master && git pull --rebase origin master && npm run docs && lerna updated", "release": "npm run prepare-release && lerna publish --exact", - "lint": "eslint \"{packages,scripts}/**/*.js\"", - "lint:fix": "eslint \"{packages,scripts}/**/*.js\" --fix", - "format": "prettier-eslint \"{packages,scripts}/**/*.js\" --write", + "lint": "eslint \"{packages}/**/*.js\" --config \"./.eslintrc\"", + "lint:fix": "eslint \"{packages}/**/*.js\" --config \"./.eslintrc\" --fix", + "format": "prettier-eslint \"{packages}/**/*.js\" --config \"./.eslintrc\" --write", "precommit": "lint-staged" }, "eslintConfig": { diff --git a/packages/vx-annotation/src/annotations/LinePath.js b/packages/vx-annotation/src/annotations/LinePath.js index 2d707310a..be2edbf61 100644 --- a/packages/vx-annotation/src/annotations/LinePath.js +++ b/packages/vx-annotation/src/annotations/LinePath.js @@ -15,7 +15,6 @@ LinePathAnnotation.propTypes = { className: PropTypes.string, label: PropTypes.string, labelAnchor: PropTypes.oneOf(['start', 'middle', 'end']), - labelOrientation: PropTypes.string, labelDx: PropTypes.number, labelDy: PropTypes.number, labelFill: PropTypes.string, @@ -34,9 +33,6 @@ export default function LinePathAnnotation({ className, label, labelAnchor = 'middle', - labelOrientation = 'horizontal', - labelVerticalAlign = 'top', - labelHorizontalAlign = 'right', labelDx = 0, labelDy = 0, labelFill, diff --git a/packages/vx-axis/src/utils/center.js b/packages/vx-axis/src/utils/center.js index 13adb009e..dd033fff1 100644 --- a/packages/vx-axis/src/utils/center.js +++ b/packages/vx-axis/src/utils/center.js @@ -1,7 +1,7 @@ export default function center(scale) { - var offset = scale.bandwidth() / 2; + let offset = scale.bandwidth() / 2; if (scale.round()) offset = Math.round(offset); - return function(d) { + return d => { return scale(d) + offset; }; } diff --git a/packages/vx-axis/src/utils/labelTransform.js b/packages/vx-axis/src/utils/labelTransform.js index 421a1319c..dd11fe0bf 100644 --- a/packages/vx-axis/src/utils/labelTransform.js +++ b/packages/vx-axis/src/utils/labelTransform.js @@ -10,9 +10,10 @@ export default function labelTransform({ }) { const sign = orientation === ORIENT.left || orientation === ORIENT.top ? -1 : 1; - let x, - y, - transform = null; + let x; + let y; + let transform = null; + if (orientation === ORIENT.top || orientation === ORIENT.bottom) { x = (range[0] + range[range.length - 1]) / 2; y = diff --git a/packages/vx-brush/src/utils/getCoordsFromEvent.js b/packages/vx-brush/src/utils/getCoordsFromEvent.js index 928d5678b..14cbfed0b 100644 --- a/packages/vx-brush/src/utils/getCoordsFromEvent.js +++ b/packages/vx-brush/src/utils/getCoordsFromEvent.js @@ -11,7 +11,7 @@ export default function getCoordsFromEvent(node, event) { y: point.y }; } - let rect = node.getBoundingClientRect(); + const rect = node.getBoundingClientRect(); return { x: event.clientX - rect.left - node.clientLeft, y: event.clientY - rect.top - node.clientTop diff --git a/packages/vx-drag/src/Drag.js b/packages/vx-drag/src/Drag.js index bda04dc7d..63a4b1913 100644 --- a/packages/vx-drag/src/Drag.js +++ b/packages/vx-drag/src/Drag.js @@ -73,7 +73,7 @@ export default class Drag extends React.Component { onMouseUp={this.dragEnd} fill="transparent" /> - )} + )} {children({ x, y, @@ -94,7 +94,10 @@ Drag.propTypes = { width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, captureDragArea: PropTypes.bool, - resetOnStart: PropTypes.bool + resetOnStart: PropTypes.bool, + onDragEnd: PropTypes.func, + onDragMove: PropTypes.func, + onDragStart: PropTypes.func }; Drag.defaultProps = { diff --git a/packages/vx-event/src/localPoint.js b/packages/vx-event/src/localPoint.js index 83fea179d..864568c1f 100644 --- a/packages/vx-event/src/localPoint.js +++ b/packages/vx-event/src/localPoint.js @@ -1,47 +1,47 @@ -import { Point } from '@vx/point'; - -export default function localPoint(node, event) { - // called with no args - if (!node) return; - - // called with localPoint(event) - if (node.target) { - event = node; - - // set node to targets owner svg - node = event.target.ownerSVGElement; - - // find the outermost svg - while (node.ownerSVGElement) { - node = node.ownerSVGElement; - } - } - - // default to mouse event - let { clientX, clientY } = event; - - // support touch event - if (event.changedTouches) { - clientX = event.changedTouches[0].clientX; - clientY = event.changedTouches[0].clientY; - } - - // calculate coordinates from svg - if (node.createSVGPoint) { - let point = node.createSVGPoint(); - point.x = clientX; - point.y = clientY; - point = point.matrixTransform(node.getScreenCTM().inverse()); - return new Point({ - x: point.x, - y: point.y - }); - } - - // fallback to calculating position from non-svg dom node - let rect = node.getBoundingClientRect(); - return new Point({ - x: clientX - rect.left - node.clientLeft, - y: clientY - rect.top - node.clientTop - }); -} +import { Point } from '@vx/point'; + +export default function localPoint(node, event) { + // called with no args + if (!node) return; + + // called with localPoint(event) + if (node.target) { + event = node; + + // set node to targets owner svg + node = event.target.ownerSVGElement; + + // find the outermost svg + while (node.ownerSVGElement) { + node = node.ownerSVGElement; + } + } + + // default to mouse event + let { clientX, clientY } = event; + + // support touch event + if (event.changedTouches) { + clientX = event.changedTouches[0].clientX; + clientY = event.changedTouches[0].clientY; + } + + // calculate coordinates from svg + if (node.createSVGPoint) { + let point = node.createSVGPoint(); + point.x = clientX; + point.y = clientY; + point = point.matrixTransform(node.getScreenCTM().inverse()); + return new Point({ + x: point.x, + y: point.y + }); + } + + // fallback to calculating position from non-svg dom node + const rect = node.getBoundingClientRect(); + return new Point({ + x: clientX - rect.left - node.clientLeft, + y: clientY - rect.top - node.clientTop + }); +} diff --git a/packages/vx-event/src/touchPoint.js b/packages/vx-event/src/touchPoint.js index be38c85be..efc0fea2e 100644 --- a/packages/vx-event/src/touchPoint.js +++ b/packages/vx-event/src/touchPoint.js @@ -1,20 +1,21 @@ -import { Point } from '@vx/point'; -export default function touchPoint(node, event) { - if (!node) return; - const svg = node.ownerSVGElement || node; - if (svg.createSVGPoint) { - let point = svg.createSVGPoint(); - point.x = event.changedTouches[0].clientX; - point.y = event.changedTouches[0].clientY; - point = point.matrixTransform(node.getScreenCTM().inverse()); - return new Point({ - x: point.x, - y: point.y - }); - } - const rect = node.getBoundingClientRect(); - return new Point({ - x: event.changedTouches[0].clientX - rect.left - node.clientLeft, - y: event.changedTouches[0].clientY - rect.top - node.clientTop - }); -} +import { Point } from '@vx/point'; + +export default function touchPoint(node, event) { + if (!node) return; + const svg = node.ownerSVGElement || node; + if (svg.createSVGPoint) { + let point = svg.createSVGPoint(); + point.x = event.changedTouches[0].clientX; + point.y = event.changedTouches[0].clientY; + point = point.matrixTransform(node.getScreenCTM().inverse()); + return new Point({ + x: point.x, + y: point.y + }); + } + const rect = node.getBoundingClientRect(); + return new Point({ + x: event.changedTouches[0].clientX - rect.left - node.clientLeft, + y: event.changedTouches[0].clientY - rect.top - node.clientTop + }); +} diff --git a/packages/vx-geo/src/graticule/Graticule.js b/packages/vx-geo/src/graticule/Graticule.js index 52c4d4850..85b8d2a32 100644 --- a/packages/vx-geo/src/graticule/Graticule.js +++ b/packages/vx-geo/src/graticule/Graticule.js @@ -28,7 +28,7 @@ export default function Graticule({ if (precision) currGraticule.stepMinor(precision); return ( - + {graticule && ( )} diff --git a/packages/vx-geo/src/projections/Projection.js b/packages/vx-geo/src/projections/Projection.js index 6aa8a2e70..eb8916928 100644 --- a/packages/vx-geo/src/projections/Projection.js +++ b/packages/vx-geo/src/projections/Projection.js @@ -14,6 +14,23 @@ const projectionMapping = { naturalEarth: () => geoNaturalEarth1() }; +Projection.propTypes = { + data: PropTypes.array.isRequired, + projection: PropTypes.string, + projectionFunc: PropTypes.func, + clipAngle: PropTypes.number, + clipExtent: PropTypes.array, + scale: PropTypes.number, + translate: PropTypes.array, + center: PropTypes.array, + rotate: PropTypes.array, + precision: PropTypes.number, + fitExtent: PropTypes.array, + fitSize: PropTypes.array, + centroid: PropTypes.func, + className: PropTypes.string +}; + /** * Component for all projections. */ @@ -56,7 +73,7 @@ export default function Projection({ if (pointRadius) path.pointRadius(pointRadius); return ( - + {graticule && !graticule.foreground && path(g)} {...graticule} />} {graticuleLines && !graticuleLines.foreground && path(g)} {...graticuleLines} />} @@ -93,20 +110,3 @@ export default function Projection({ ); } - -Projection.propTypes = { - data: PropTypes.array.isRequired, - projection: PropTypes.string, - projectionFunc: PropTypes.func, - clipAngle: PropTypes.number, - clipExtent: PropTypes.array, - scale: PropTypes.number, - translate: PropTypes.array, - center: PropTypes.array, - rotate: PropTypes.array, - precision: PropTypes.number, - fitExtent: PropTypes.array, - fitSize: PropTypes.array, - centroid: PropTypes.func, - className: PropTypes.string -}; diff --git a/packages/vx-glyph/package.json b/packages/vx-glyph/package.json index 58a411c68..d92a4d2fe 100644 --- a/packages/vx-glyph/package.json +++ b/packages/vx-glyph/package.json @@ -65,7 +65,8 @@ "dependencies": { "@vx/group": "0.0.170", "classnames": "^2.2.5", - "d3-shape": "^1.2.0" + "d3-shape": "^1.2.0", + "prop-types": "^15.6.2" }, "jest": { "setupFiles": [ diff --git a/packages/vx-glyph/src/glyphs/Glyph.js b/packages/vx-glyph/src/glyphs/Glyph.js index 2cac2106e..7dadf2af2 100644 --- a/packages/vx-glyph/src/glyphs/Glyph.js +++ b/packages/vx-glyph/src/glyphs/Glyph.js @@ -1,6 +1,14 @@ import React from 'react'; -import { Group } from '@vx/group'; import cx from 'classnames'; +import PropTypes from 'prop-types'; +import { Group } from '@vx/group'; + +Glyph.propTypes = { + top: PropTypes.number, + left: PropTypes.number, + className: PropTypes.string, + children: PropTypes.any +}; export default function Glyph({ top = 0, left = 0, className, children }) { return ( diff --git a/packages/vx-grid/package.json b/packages/vx-grid/package.json index f14bf3c70..a25167792 100644 --- a/packages/vx-grid/package.json +++ b/packages/vx-grid/package.json @@ -63,7 +63,8 @@ "@vx/group": "0.0.170", "@vx/point": "0.0.165", "@vx/shape": "0.0.178", - "classnames": "^2.2.5" + "classnames": "^2.2.5", + "prop-types": "^15.6.2" }, "publishConfig": { "access": "public" diff --git a/packages/vx-grid/src/grids/Columns.js b/packages/vx-grid/src/grids/Columns.js index 426536216..52425a296 100644 --- a/packages/vx-grid/src/grids/Columns.js +++ b/packages/vx-grid/src/grids/Columns.js @@ -1,9 +1,24 @@ import React from 'react'; import cx from 'classnames'; +import PropTypes from 'prop-types'; import { Line } from '@vx/shape'; import { Group } from '@vx/group'; import { Point } from '@vx/point'; +Columns.propTypes = { + top: PropTypes.number, + left: PropTypes.number, + className: PropTypes.string, + stroke: PropTypes.string, + strokeWidth: PropTypes.string, + strokeDasharray: PropTypes.string, + numTicks: PropTypes.number, + lineStyle: PropTypes.object, + offset: PropTypes.number, + scale: PropTypes.func.isRequired, + height: PropTypes.number.isRequired +}; + export default function Columns({ top = 0, left = 0, diff --git a/packages/vx-grid/src/grids/Grid.js b/packages/vx-grid/src/grids/Grid.js index a351dee4d..546a28c71 100644 --- a/packages/vx-grid/src/grids/Grid.js +++ b/packages/vx-grid/src/grids/Grid.js @@ -1,9 +1,29 @@ import React from 'react'; import cx from 'classnames'; +import PropTypes from 'prop-types'; import { Group } from '@vx/group'; import Rows from './Rows'; import Columns from './Columns'; +Grid.propTypes = { + top: PropTypes.number, + left: PropTypes.number, + className: PropTypes.string, + stroke: PropTypes.string, + strokeWidth: PropTypes.string, + strokeDasharray: PropTypes.string, + numTicksRows: PropTypes.number, + numTicksColumns: PropTypes.number, + rowLineStyle: PropTypes.object, + columnLineStyle: PropTypes.object, + xOffset: PropTypes.number, + yOffset: PropTypes.number, + xScale: PropTypes.func.isRequired, + yScale: PropTypes.func.isRequired, + height: PropTypes.number.isRequired, + width: PropTypes.number.isRequired +}; + export default function Grid({ top, left, diff --git a/packages/vx-grid/src/grids/Rows.js b/packages/vx-grid/src/grids/Rows.js index 8c94e13a5..53a89908f 100644 --- a/packages/vx-grid/src/grids/Rows.js +++ b/packages/vx-grid/src/grids/Rows.js @@ -1,9 +1,24 @@ import React from 'react'; import cx from 'classnames'; +import PropTypes from 'prop-types'; import { Line } from '@vx/shape'; import { Group } from '@vx/group'; import { Point } from '@vx/point'; +Rows.propTypes = { + top: PropTypes.number, + left: PropTypes.number, + className: PropTypes.string, + stroke: PropTypes.string, + strokeWidth: PropTypes.string, + strokeDasharray: PropTypes.string, + numTicks: PropTypes.number, + lineStyle: PropTypes.object, + offset: PropTypes.number, + scale: PropTypes.func.isRequired, + width: PropTypes.number.isRequired +}; + export default function Rows({ top = 0, left = 0, diff --git a/packages/vx-heatmap/src/heatmaps/circle.js b/packages/vx-heatmap/src/heatmaps/circle.js index f134a27ab..9be6eaff1 100644 --- a/packages/vx-heatmap/src/heatmaps/circle.js +++ b/packages/vx-heatmap/src/heatmaps/circle.js @@ -13,7 +13,8 @@ HeatmapCircle.propTypes = { colorScale: PropTypes.func, opacityScale: PropTypes.func, bins: PropTypes.func, - count: PropTypes.func + count: PropTypes.func, + className: PropTypes.string }; export default function HeatmapCircle({ diff --git a/packages/vx-heatmap/src/heatmaps/rect.js b/packages/vx-heatmap/src/heatmaps/rect.js index 69998e100..2d4d91a78 100644 --- a/packages/vx-heatmap/src/heatmaps/rect.js +++ b/packages/vx-heatmap/src/heatmaps/rect.js @@ -15,7 +15,8 @@ HeatmapRect.propTypes = { colorScale: PropTypes.func, opacityScale: PropTypes.func, bins: PropTypes.func, - count: PropTypes.func + count: PropTypes.func, + className: PropTypes.string }; export default function HeatmapRect({ diff --git a/packages/vx-hierarchy/src/HierarchyDefaultLink.js b/packages/vx-hierarchy/src/HierarchyDefaultLink.js index 5e26e79ad..22737ca1b 100644 --- a/packages/vx-hierarchy/src/HierarchyDefaultLink.js +++ b/packages/vx-hierarchy/src/HierarchyDefaultLink.js @@ -1,4 +1,9 @@ import React from 'react'; +import PropTypes from 'prop-types'; + +HierarchyDefaultLink.propTypes = { + link: PropTypes.object +}; export default function HierarchyDefaultLink({ link }) { return ( diff --git a/packages/vx-hierarchy/src/HierarchyDefaultNode.js b/packages/vx-hierarchy/src/HierarchyDefaultNode.js index a3bdc3ab7..35540a2c9 100644 --- a/packages/vx-hierarchy/src/HierarchyDefaultNode.js +++ b/packages/vx-hierarchy/src/HierarchyDefaultNode.js @@ -1,4 +1,9 @@ import React from 'react'; +import PropTypes from 'prop-types'; + +HierarchyDefaultNode.propTypes = { + node: PropTypes.object +}; export default function HierarchyDefaultNode({ node }) { return ; diff --git a/packages/vx-hierarchy/src/hierarchies/Cluster.js b/packages/vx-hierarchy/src/hierarchies/Cluster.js index 8f0275230..476379c07 100644 --- a/packages/vx-hierarchy/src/hierarchies/Cluster.js +++ b/packages/vx-hierarchy/src/hierarchies/Cluster.js @@ -8,7 +8,15 @@ import DefaultNode from '../HierarchyDefaultNode'; Cluster.propTypes = { root: PropTypes.object.isRequired, - children: PropTypes.func + children: PropTypes.func, + top: PropTypes.number, + left: PropTypes.number, + className: PropTypes.string, + size: PropTypes.arrayOf(PropTypes.number), + nodeSize: PropTypes.arrayOf(PropTypes.number), + separation: PropTypes.func, + linkComponent: PropTypes.any, + nodeComponent: PropTypes.any }; export default function Cluster({ @@ -25,6 +33,7 @@ export default function Cluster({ ...restProps }) { const cluster = d3cluster(); + if (size) cluster.size(size); if (nodeSize) cluster.nodeSize(nodeSize); if (separation) cluster.separation(separation); diff --git a/packages/vx-hierarchy/src/hierarchies/Pack.js b/packages/vx-hierarchy/src/hierarchies/Pack.js index e805290f9..6918fe995 100644 --- a/packages/vx-hierarchy/src/hierarchies/Pack.js +++ b/packages/vx-hierarchy/src/hierarchies/Pack.js @@ -7,7 +7,14 @@ import DefaultNode from '../HierarchyDefaultNode'; Pack.propTypes = { root: PropTypes.object.isRequired, - children: PropTypes.func + children: PropTypes.func, + top: PropTypes.number, + left: PropTypes.number, + className: PropTypes.string, + radius: PropTypes.func, + size: PropTypes.arrayOf(PropTypes.number), + padding: PropTypes.number, + nodeComponent: PropTypes.any }; export default function Pack({ @@ -23,8 +30,9 @@ export default function Pack({ ...restProps }) { const pack = d3pack(); + if (size) pack.size(size); - if (radius) pack.radius(radius); + if (radius !== undefined) pack.radius(radius); if (padding) pack.padding(padding); const data = pack(root); diff --git a/packages/vx-hierarchy/src/hierarchies/Partition.js b/packages/vx-hierarchy/src/hierarchies/Partition.js index 9261b9102..b8f456779 100644 --- a/packages/vx-hierarchy/src/hierarchies/Partition.js +++ b/packages/vx-hierarchy/src/hierarchies/Partition.js @@ -7,7 +7,14 @@ import DefaultNode from '../HierarchyDefaultNode'; Partition.propTypes = { root: PropTypes.object.isRequired, - children: PropTypes.func + children: PropTypes.func, + top: PropTypes.number, + left: PropTypes.number, + className: PropTypes.string, + size: PropTypes.arrayOf(PropTypes.number), + round: PropTypes.bool, + padding: PropTypes.number, + nodeComponent: PropTypes.any }; export default function Partition({ diff --git a/packages/vx-hierarchy/src/hierarchies/Tree.js b/packages/vx-hierarchy/src/hierarchies/Tree.js index cc95db1b0..4f20c0be2 100644 --- a/packages/vx-hierarchy/src/hierarchies/Tree.js +++ b/packages/vx-hierarchy/src/hierarchies/Tree.js @@ -8,7 +8,15 @@ import DefaultNode from '../HierarchyDefaultNode'; Tree.propTypes = { root: PropTypes.object.isRequired, - children: PropTypes.func + children: PropTypes.func, + top: PropTypes.number, + left: PropTypes.number, + className: PropTypes.string, + size: PropTypes.arrayOf(PropTypes.number), + nodeSize: PropTypes.arrayOf(PropTypes.number), + separation: PropTypes.func, + linkComponent: PropTypes.any, + nodeComponent: PropTypes.any }; export default function Tree({ diff --git a/packages/vx-hierarchy/src/hierarchies/Treemap.js b/packages/vx-hierarchy/src/hierarchies/Treemap.js index 82165e070..789aa7a31 100644 --- a/packages/vx-hierarchy/src/hierarchies/Treemap.js +++ b/packages/vx-hierarchy/src/hierarchies/Treemap.js @@ -7,7 +7,21 @@ import DefaultNode from '../HierarchyDefaultNode'; Treemap.propTypes = { root: PropTypes.object.isRequired, - children: PropTypes.func + children: PropTypes.func, + top: PropTypes.number, + left: PropTypes.number, + className: PropTypes.string, + tile: PropTypes.func, + size: PropTypes.arrayOf(PropTypes.number), + round: PropTypes.bool, + padding: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + paddingInner: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + paddingOuter: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + paddingTop: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + paddingRight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + paddingBottom: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + paddingLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + nodeComponent: PropTypes.any }; export default function Treemap({ @@ -30,6 +44,7 @@ export default function Treemap({ ...restProps }) { const treemap = d3treemap(); + if (tile) treemap.tile(tile); if (size) treemap.size(size); if (round) treemap.round(round); diff --git a/packages/vx-legend/src/legends/Legend.js b/packages/vx-legend/src/legends/Legend.js index c3a604e6d..bc2c89327 100644 --- a/packages/vx-legend/src/legends/Legend.js +++ b/packages/vx-legend/src/legends/Legend.js @@ -8,19 +8,22 @@ import valueOrIdentity from '../util/valueOrIdentity'; Legend.propTypes = { className: PropTypes.string, - style: PropTypes.object, + 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.string, + shapeMargin: PropTypes.any, labelAlign: PropTypes.string, labelFlex: PropTypes.string, labelMargin: PropTypes.string, itemMargin: PropTypes.string, direction: PropTypes.string, itemDirection: PropTypes.string, - fill: PropTypes.func, - shape: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + fill: PropTypes.any, + size: PropTypes.any, + shape: PropTypes.any, + shapeStyle: PropTypes.any, labelFormat: PropTypes.func, labelTransform: PropTypes.func }; diff --git a/packages/vx-legend/src/legends/LegendShape.js b/packages/vx-legend/src/legends/LegendShape.js index 7c6514849..32ce9ed4b 100644 --- a/packages/vx-legend/src/legends/LegendShape.js +++ b/packages/vx-legend/src/legends/LegendShape.js @@ -3,6 +3,17 @@ 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, diff --git a/packages/vx-legend/src/legends/Threshold.js b/packages/vx-legend/src/legends/Threshold.js index 1cf959013..3e62752f4 100644 --- a/packages/vx-legend/src/legends/Threshold.js +++ b/packages/vx-legend/src/legends/Threshold.js @@ -1,78 +1,78 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Legend from './Legend'; - -LegendThreshold.propTypes = { - scale: PropTypes.func.isRequired, - domain: PropTypes.array, - labelTransform: PropTypes.func, - labelFormat: PropTypes.func, - labelDelimiter: PropTypes.string, - labelLower: PropTypes.string, - labelUpper: PropTypes.string -}; - -export default function LegendThreshold({ - scale, - domain, - labelFormat = x => x, - labelTransform, - labelDelimiter = 'to', - labelLower = 'Less than ', - labelUpper = 'More than ', - ...restProps -}) { - domain = domain || scale.range(); - labelTransform = - labelTransform || - defaultTransform({ - labelDelimiter, - labelLower, - labelUpper - }); - return ( - - ); -} - -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 - }; - }; - }; -} +import React from 'react'; +import PropTypes from 'prop-types'; +import Legend from './Legend'; + +LegendThreshold.propTypes = { + scale: PropTypes.func.isRequired, + domain: PropTypes.array, + labelTransform: PropTypes.func, + labelFormat: PropTypes.func, + labelDelimiter: PropTypes.string, + labelLower: PropTypes.string, + labelUpper: PropTypes.string +}; + +export default function LegendThreshold({ + scale, + domain, + labelFormat = x => x, + labelTransform, + labelDelimiter = 'to', + labelLower = 'Less than ', + labelUpper = 'More than ', + ...restProps +}) { + domain = domain || scale.range(); + labelTransform = + labelTransform || + defaultTransform({ + labelDelimiter, + labelLower, + labelUpper + }); + return ( + + ); +} + +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.js b/packages/vx-legend/src/shapes/Circle.js index 44d81b774..fb81ec4ce 100644 --- a/packages/vx-legend/src/shapes/Circle.js +++ b/packages/vx-legend/src/shapes/Circle.js @@ -1,6 +1,14 @@ 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 }) { if (typeof width === 'string') width = 0; if (typeof height === 'string') height = 0; diff --git a/packages/vx-legend/src/shapes/Rect.js b/packages/vx-legend/src/shapes/Rect.js index 34ac17c0e..ee8ef9c2d 100644 --- a/packages/vx-legend/src/shapes/Rect.js +++ b/packages/vx-legend/src/shapes/Rect.js @@ -1,4 +1,12 @@ 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-marker/package.json b/packages/vx-marker/package.json index a620655f9..b465bb884 100644 --- a/packages/vx-marker/package.json +++ b/packages/vx-marker/package.json @@ -59,7 +59,8 @@ "dependencies": { "@vx/group": "0.0.170", "@vx/shape": "0.0.178", - "classnames": "^2.2.5" + "classnames": "^2.2.5", + "prop-types": "^15.6.2" }, "peerDependencies": { "react": "^15.0.0-0 || ^16.0.0-0" diff --git a/packages/vx-marker/src/markers/Marker.js b/packages/vx-marker/src/markers/Marker.js index e2aa8b71d..11a5a0365 100644 --- a/packages/vx-marker/src/markers/Marker.js +++ b/packages/vx-marker/src/markers/Marker.js @@ -1,8 +1,30 @@ import React from 'react'; import cx from 'classnames'; +import PropTypes from 'prop-types'; import { Line } from '@vx/shape'; import { Group } from '@vx/group'; +Marker.propTypes = { + top: PropTypes.number, + left: PropTypes.number, + from: PropTypes.object, + to: PropTypes.object, + stroke: PropTypes.string, + strokeWidth: PropTypes.number, + strokeDasharray: PropTypes.string, + transform: PropTypes.string, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + labelAnchor: PropTypes.string, + labelDx: PropTypes.number, + labelDy: PropTypes.number, + labelFill: PropTypes.string, + labelStroke: PropTypes.string, + labelStrokeWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + labelFontSize: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + labelPaintOrder: PropTypes.string, + className: PropTypes.string +}; + export default function Marker({ top = 0, left = 0, @@ -11,13 +33,9 @@ export default function Marker({ stroke = 'magenta', strokeWidth = 2, strokeDasharray, - fill, transform, label, labelAnchor = 'left', - labelOrientation = 'horizontal', - labelVerticalAlign = 'top', - labelHorizontalAlign = 'right', labelDx = 0, labelDy = 0, labelFill, diff --git a/packages/vx-mock-data/src/generators/genStats.js b/packages/vx-mock-data/src/generators/genStats.js index 6ecc4cf05..3eb42be17 100644 --- a/packages/vx-mock-data/src/generators/genStats.js +++ b/packages/vx-mock-data/src/generators/genStats.js @@ -1,66 +1,66 @@ -import { randomNormal } from 'd3-random'; - -const random = randomNormal(4, 3); -const randomOffset = () => Math.random() * 10; -const sampleSize = 1000; - -export default function genStats(number) { - const data = []; - let i; - for (i = 0; i < number; i += 1) { - const points = []; - let j; - const offset = randomOffset(); - for (j = 0; j < sampleSize; j += 1) { - points.push(offset + random()); - } - - points.sort((a, b) => a - b); - - const firstQuartile = points[Math.round(sampleSize / 4)]; - const thirdQuartile = points[Math.round(3 * sampleSize / 4)]; - const IQR = thirdQuartile - firstQuartile; - - const min = firstQuartile - 1.5 * IQR; - const max = thirdQuartile + 1.5 * IQR; - - const outliers = points.filter(p => p < min || p > max); - const binWidth = 2 * IQR * (sampleSize - outliers.length) ** (-1 / 3); - const binNum = Math.round((max - min) / binWidth); - const actualBinWidth = (max - min) / binNum; - - const bins = Array(binNum + 2).fill(0); - const values = Array(binNum + 2).fill(min); - - for (let i = 1; i <= binNum; i += 1) { - values[i] += actualBinWidth * (i - 0.5); - } - - values[values.length - 1] = max; - - points.filter(p => p >= min && p <= max).forEach(p => { - bins[Math.floor((p - min) / actualBinWidth) + 1] += 1; - }); - - const binData = values.map((v, i) => ({ - value: v, - count: bins[i] - })); - - const boxPlot = { - x: `Statistics ${i}`, - min, - firstQuartile, - median: points[Math.round(sampleSize / 2)], - thirdQuartile, - max, - outliers - }; - - data.push({ - boxPlot, - binData - }); - } - return data; -} +import { randomNormal } from 'd3-random'; + +const random = randomNormal(4, 3); +const randomOffset = () => Math.random() * 10; +const sampleSize = 1000; + +export default function genStats(number) { + const data = []; + let i; + for (i = 0; i < number; i += 1) { + const points = []; + let j; + const offset = randomOffset(); + for (j = 0; j < sampleSize; j += 1) { + points.push(offset + random()); + } + + points.sort((a, b) => a - b); + + const firstQuartile = points[Math.round(sampleSize / 4)]; + const thirdQuartile = points[Math.round((3 * sampleSize) / 4)]; + const IQR = thirdQuartile - firstQuartile; + + const min = firstQuartile - 1.5 * IQR; + const max = thirdQuartile + 1.5 * IQR; + + const outliers = points.filter(p => p < min || p > max); + const binWidth = 2 * IQR * (sampleSize - outliers.length) ** (-1 / 3); + const binNum = Math.round((max - min) / binWidth); + const actualBinWidth = (max - min) / binNum; + + const bins = Array(binNum + 2).fill(0); + const values = Array(binNum + 2).fill(min); + + for (let ii = 1; ii <= binNum; ii += 1) { + values[ii] += actualBinWidth * (ii - 0.5); + } + + values[values.length - 1] = max; + + points.filter(p => p >= min && p <= max).forEach(p => { + bins[Math.floor((p - min) / actualBinWidth) + 1] += 1; + }); + + const binData = values.map((v, index) => ({ + value: v, + count: bins[index] + })); + + const boxPlot = { + x: `Statistics ${i}`, + min, + firstQuartile, + median: points[Math.round(sampleSize / 2)], + thirdQuartile, + max, + outliers + }; + + data.push({ + boxPlot, + binData + }); + } + return data; +} diff --git a/packages/vx-network/package.json b/packages/vx-network/package.json index 8768a71cf..cff2ebe8f 100644 --- a/packages/vx-network/package.json +++ b/packages/vx-network/package.json @@ -51,7 +51,8 @@ "dependencies": { "@vx/group": "0.0.170", "classnames": "^2.2.5", - "d3-force": "^1.0.6" + "d3-force": "^1.0.6", + "prop-types": "^15.6.2" }, "peerDependencies": { "react": "^15.0.0-0 || ^16.0.0-0" diff --git a/packages/vx-network/src/DefaultLink.js b/packages/vx-network/src/DefaultLink.js index 301f17e05..01b8c6ddc 100644 --- a/packages/vx-network/src/DefaultLink.js +++ b/packages/vx-network/src/DefaultLink.js @@ -1,4 +1,9 @@ import React from 'react'; +import PropTypes from 'prop-types'; + +DefaultLink.propTypes = { + link: PropTypes.object +}; export default function DefaultLink({ link }) { return ( diff --git a/packages/vx-network/src/Graph.js b/packages/vx-network/src/Graph.js index ba6b704c8..972962430 100644 --- a/packages/vx-network/src/Graph.js +++ b/packages/vx-network/src/Graph.js @@ -1,15 +1,22 @@ -import React from 'react'; -import { Group } from '@vx/group'; -import Links from './Links'; -import Nodes from './Nodes'; -import DefaultLink from './DefaultLink'; -import DefaultNode from './DefaultNode'; - -export default function Graph({ graph, linkComponent = DefaultLink, nodeComponent = DefaultNode }) { - return ( - - - - - ); -} +import React from 'react'; +import PropTypes from 'prop-types'; +import { Group } from '@vx/group'; +import Links from './Links'; +import Nodes from './Nodes'; +import DefaultLink from './DefaultLink'; +import DefaultNode from './DefaultNode'; + +Graph.propTypes = { + graph: PropTypes.object, + linkComponent: PropTypes.any, + nodeComponent: PropTypes.any +}; + +export default function Graph({ graph, linkComponent = DefaultLink, nodeComponent = DefaultNode }) { + return ( + + + + + ); +} diff --git a/packages/vx-network/src/Links.js b/packages/vx-network/src/Links.js index c9796ceb6..0c9146332 100644 --- a/packages/vx-network/src/Links.js +++ b/packages/vx-network/src/Links.js @@ -1,6 +1,13 @@ -import { Group } from '@vx/group'; -import cx from 'classnames'; import React from 'react'; +import cx from 'classnames'; +import PropTypes from 'prop-types'; +import { Group } from '@vx/group'; + +Links.propTypes = { + links: PropTypes.array, + linkComponent: PropTypes.any, + className: PropTypes.string +}; export default function Links({ links, linkComponent, className }) { return ( diff --git a/packages/vx-network/src/Nodes.js b/packages/vx-network/src/Nodes.js index 20460df5d..4469fbdc1 100644 --- a/packages/vx-network/src/Nodes.js +++ b/packages/vx-network/src/Nodes.js @@ -1,6 +1,13 @@ -import { Group } from '@vx/group'; -import cx from 'classnames'; import React from 'react'; +import cx from 'classnames'; +import PropTypes from 'prop-types'; +import { Group } from '@vx/group'; + +Nodes.propTypes = { + nodes: PropTypes.array, + nodeComponent: PropTypes.any, + className: PropTypes.string +}; export default function Nodes({ nodes, nodeComponent, className }) { return ( diff --git a/packages/vx-pattern/src/patterns/Circles.js b/packages/vx-pattern/src/patterns/Circles.js index 77f8a5786..6b9238899 100644 --- a/packages/vx-pattern/src/patterns/Circles.js +++ b/packages/vx-pattern/src/patterns/Circles.js @@ -1,90 +1,91 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cxx from 'classnames'; -import Pattern from './Pattern'; - -/** - * Creates an array of cirlces for a list of corners - * in the format [[cornerX, cornerY], ...] - */ -export function createCircles({ - corners, - id, - radius, - fill, - stroke, - strokeWidth, - strokeDasharray, - className -}) { - return corners.map(([cornerX, cornerY]) => ( - - )); -} - -export default function PatternCircles({ - id, - width, - height, - radius = 2, - fill, - stroke, - strokeWidth, - strokeDasharray, - background, - complement = false, - className -}) { - let corners; - if (complement) { - corners = [[0, 0], [0, height], [width, 0], [width, height]]; - } - return ( - - {!!background && } - - {complement && - createCircles({ - corners, - id, - radius, - fill, - stroke, - strokeWidth, - strokeDasharray - })} - - ); -} - -PatternCircles.propTypes = { - id: PropTypes.string.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - radius: PropTypes.number, - fill: PropTypes.string, - className: PropTypes.string, - stroke: PropTypes.string, - strokeWidth: PropTypes.number, - strokeDasharray: PropTypes.string, - complement: PropTypes.bool -}; +import React from 'react'; +import PropTypes from 'prop-types'; +import cxx from 'classnames'; +import Pattern from './Pattern'; + +/** + * Creates an array of cirlces for a list of corners + * in the format [[cornerX, cornerY], ...] + */ +export function createCircles({ + corners, + id, + radius, + fill, + stroke, + strokeWidth, + strokeDasharray, + className +}) { + return corners.map(([cornerX, cornerY]) => ( + + )); +} + +PatternCircles.propTypes = { + id: PropTypes.string.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + radius: PropTypes.number, + fill: PropTypes.string, + className: PropTypes.string, + stroke: PropTypes.string, + strokeWidth: PropTypes.number, + strokeDasharray: PropTypes.string, + complement: PropTypes.bool, + background: PropTypes.string +}; + +export default function PatternCircles({ + id, + width, + height, + radius = 2, + fill, + stroke, + strokeWidth, + strokeDasharray, + background, + complement = false, + className +}) { + let corners; + if (complement) { + corners = [[0, 0], [0, height], [width, 0], [width, height]]; + } + return ( + + {!!background && } + + {complement && + createCircles({ + corners, + id, + radius, + fill, + stroke, + strokeWidth, + strokeDasharray + })} + + ); +} diff --git a/packages/vx-pattern/src/patterns/Hexagons.js b/packages/vx-pattern/src/patterns/Hexagons.js index b3125173c..1716d8c08 100644 --- a/packages/vx-pattern/src/patterns/Hexagons.js +++ b/packages/vx-pattern/src/patterns/Hexagons.js @@ -1,54 +1,55 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import Path from './Path'; - -export default function PatternHexagons({ - id, - width, - height, - path, - fill, - stroke, - strokeWidth, - strokeDasharray, - strokeLinecap, - shapeRendering, - background, - className, - size = 3 -}) { - const s = Math.sqrt(size); - return ( - - ); -} - -PatternHexagons.propTypes = { - id: PropTypes.string.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - size: PropTypes.number, - fill: PropTypes.string, - className: PropTypes.string, - background: PropTypes.string, - stroke: PropTypes.string, - strokeWidth: PropTypes.number, - strokeDasharray: PropTypes.string -}; +import React from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import Path from './Path'; + +PatternHexagons.propTypes = { + id: PropTypes.string.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + size: PropTypes.number, + fill: PropTypes.string, + className: PropTypes.string, + background: PropTypes.string, + stroke: PropTypes.string, + strokeWidth: PropTypes.number, + strokeDasharray: PropTypes.string, + strokeLinecap: PropTypes.string, + shapeRendering: PropTypes.string +}; + +export default function PatternHexagons({ + id, + width, + height, + fill, + stroke, + strokeWidth, + strokeDasharray, + strokeLinecap, + shapeRendering, + background, + className, + size = 3 +}) { + const s = Math.sqrt(size); + return ( + + ); +} diff --git a/packages/vx-pattern/src/patterns/Lines.js b/packages/vx-pattern/src/patterns/Lines.js index 532786554..8a1e9e018 100644 --- a/packages/vx-pattern/src/patterns/Lines.js +++ b/packages/vx-pattern/src/patterns/Lines.js @@ -1,82 +1,84 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import Pattern from './Pattern'; -import Orientation from '../constants'; - -function pathForOrientation({ height, orientation }) { - let path; - - switch (orientation) { - case Orientation.vertical: - path = `M ${height / 2}, 0 l 0, ${height}`; - break; - case Orientation.horizontal: - path = `M 0,${height / 2} l ${height},0`; - break; - case Orientation.diagonal: - path = `M 0,${height} l ${height},${-height} M ${-height / 4},${height / 4} l ${height / - 2},${-height / 2} - M ${3 / 4 * height},${5 / 4 * height} l ${height / 2},${-height / 2}`; - break; - default: - path = `M ${height / 2}, 0 l 0, ${height}`; - } - - return path; -} - -export default function PatternLines({ - id, - width, - height, - path, - stroke, - strokeWidth, - strokeDasharray, - strokeLinecap = 'square', - shapeRendering = 'auto', - orientation = ['vertical'], - background, - className -}) { - if (!Array.isArray(orientation)) orientation = [orientation]; - - return ( - - {!!background && ( - - )} - {orientation.map((o, i) => { - return ( - - ); - })} - - ); -} - -PatternLines.propTypes = { - id: PropTypes.string.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - background: PropTypes.string, - stroke: PropTypes.string.isRequired, - strokeWidth: PropTypes.number.isRequired, - strokeDasharray: PropTypes.string, - className: PropTypes.string -}; +import React from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import Pattern from './Pattern'; +import Orientation from '../constants'; + +function pathForOrientation({ height, orientation }) { + let path; + + switch (orientation) { + case Orientation.vertical: + path = `M ${height / 2}, 0 l 0, ${height}`; + break; + case Orientation.horizontal: + path = `M 0,${height / 2} l ${height},0`; + break; + case Orientation.diagonal: + path = `M 0,${height} l ${height},${-height} M ${-height / 4},${height / 4} l ${height / + 2},${-height / 2} + M ${(3 / 4) * height},${(5 / 4) * height} l ${height / 2},${-height / 2}`; + break; + default: + path = `M ${height / 2}, 0 l 0, ${height}`; + } + + return path; +} + +export default function PatternLines({ + id, + width, + height, + stroke, + strokeWidth, + strokeDasharray, + strokeLinecap = 'square', + shapeRendering = 'auto', + orientation = ['vertical'], + background, + className +}) { + if (!Array.isArray(orientation)) orientation = [orientation]; + + return ( + + {!!background && ( + + )} + {orientation.map((o, i) => { + return ( + + ); + })} + + ); +} + +PatternLines.propTypes = { + id: PropTypes.string.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + background: PropTypes.string, + stroke: PropTypes.string.isRequired, + strokeWidth: PropTypes.number.isRequired, + strokeDasharray: PropTypes.string, + className: PropTypes.string, + strokeLinecap: PropTypes.string, + shapeRendering: PropTypes.string, + orientation: PropTypes.array +}; diff --git a/packages/vx-pattern/src/patterns/Path.js b/packages/vx-pattern/src/patterns/Path.js index 26707d152..81d8a2541 100644 --- a/packages/vx-pattern/src/patterns/Path.js +++ b/packages/vx-pattern/src/patterns/Path.js @@ -1,47 +1,50 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import Pattern from './Pattern'; - -export default function PatternPath({ - id, - width, - height, - path, - fill = 'transparent', - stroke, - strokeWidth, - strokeDasharray, - strokeLinecap = 'square', - shapeRendering = 'auto', - background, - className -}) { - return ( - - {!!background && } - - - ); -} - -PatternPath.propTypes = { - id: PropTypes.string.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - fill: PropTypes.string, - className: PropTypes.string, - background: PropTypes.string, - stroke: PropTypes.string, - strokeWidth: PropTypes.number, - strokeDasharray: PropTypes.string -}; +import React from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import Pattern from './Pattern'; + +export default function PatternPath({ + id, + width, + height, + path, + fill = 'transparent', + stroke, + strokeWidth, + strokeDasharray, + strokeLinecap = 'square', + shapeRendering = 'auto', + background, + className +}) { + return ( + + {!!background && } + + + ); +} + +PatternPath.propTypes = { + id: PropTypes.string.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + path: PropTypes.string, + fill: PropTypes.string, + className: PropTypes.string, + background: PropTypes.string, + stroke: PropTypes.string, + strokeWidth: PropTypes.number, + strokeDasharray: PropTypes.string, + strokeLinecap: PropTypes.string, + shapeRendering: PropTypes.string +}; diff --git a/packages/vx-pattern/src/patterns/Waves.js b/packages/vx-pattern/src/patterns/Waves.js index f09a7159f..f6d60b835 100644 --- a/packages/vx-pattern/src/patterns/Waves.js +++ b/packages/vx-pattern/src/patterns/Waves.js @@ -1,54 +1,55 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import Path from './Path'; - -export default function PatternWaves({ - id, - width, - height, - path, - fill, - stroke, - strokeWidth, - strokeDasharray, - strokeLinecap, - shapeRendering, - background, - className -}) { - return ( - - ); -} - -PatternWaves.propTypes = { - id: PropTypes.string.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - fill: PropTypes.string, - className: PropTypes.string, - background: PropTypes.string, - stroke: PropTypes.string, - strokeWidth: PropTypes.number, - strokeDasharray: PropTypes.string -}; +import React from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import Path from './Path'; + +export default function PatternWaves({ + id, + width, + height, + fill, + stroke, + strokeWidth, + strokeDasharray, + strokeLinecap, + shapeRendering, + background, + className +}) { + return ( + + ); +} + +PatternWaves.propTypes = { + id: PropTypes.string.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + fill: PropTypes.string, + className: PropTypes.string, + background: PropTypes.string, + stroke: PropTypes.string, + strokeWidth: PropTypes.number, + strokeDasharray: PropTypes.string, + strokeLinecap: PropTypes.string, + shapeRendering: PropTypes.string +}; diff --git a/packages/vx-responsive/src/components/ScaleSVG.js b/packages/vx-responsive/src/components/ScaleSVG.js index a49bf66ee..3eba28389 100644 --- a/packages/vx-responsive/src/components/ScaleSVG.js +++ b/packages/vx-responsive/src/components/ScaleSVG.js @@ -1,29 +1,39 @@ -import React from 'react'; - -export default function ResponsiveSVG({ - children, - width, - height, - xOrigin = 0, - yOrigin = 0, - preserveAspectRatio = 'xMinYMin meet' -}) { - return ( -
- - {children} - -
- ); -} +import React from 'react'; +import PropTypes from 'prop-types'; + +ResponsiveSVG.propTypes = { + children: PropTypes.func, + width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + xOrigin: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + yOrigin: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + preserveAspectRatio: PropTypes.string +}; + +export default function ResponsiveSVG({ + children, + width, + height, + xOrigin = 0, + yOrigin = 0, + preserveAspectRatio = 'xMinYMin meet' +}) { + return ( +
+ + {children} + +
+ ); +} diff --git a/packages/vx-scale/src/util/updateScale.js b/packages/vx-scale/src/util/updateScale.js index 0c605a54f..7f253bd78 100644 --- a/packages/vx-scale/src/util/updateScale.js +++ b/packages/vx-scale/src/util/updateScale.js @@ -1,7 +1,9 @@ -export default function updateScale(scale, { ...args }) { - const nextScale = scale.copy(); - Object.keys(args).forEach(key => { - if (nextScale.hasOwnProperty(key)) nextScale[key](args[key]); - }); - return nextScale; -} +const has = Object.prototype.hasOwnProperty; + +export default function updateScale(scale, { ...args }) { + const nextScale = scale.copy(); + Object.keys(args).forEach(key => { + if (has.call(nextScale, key)) nextScale[key](args[key]); + }); + return nextScale; +} diff --git a/packages/vx-shape/src/shapes/Arc.js b/packages/vx-shape/src/shapes/Arc.js index 85c46937c..0814e466e 100644 --- a/packages/vx-shape/src/shapes/Arc.js +++ b/packages/vx-shape/src/shapes/Arc.js @@ -1,8 +1,22 @@ import React from 'react'; import cx from 'classnames'; +import PropTypes from 'prop-types'; import { arc as d3Arc } from 'd3-shape'; import additionalProps from '../util/additionalProps'; +Arc.propTypes = { + className: PropTypes.string, + data: PropTypes.any, + centroid: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + innerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + outerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + cornerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + startAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + endAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + padAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + padRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]) +}; + export default function Arc({ className, data, diff --git a/packages/vx-shape/src/shapes/AreaStack.js b/packages/vx-shape/src/shapes/AreaStack.js index 173539b72..dc1748ebe 100644 --- a/packages/vx-shape/src/shapes/AreaStack.js +++ b/packages/vx-shape/src/shapes/AreaStack.js @@ -1,12 +1,29 @@ import React from 'react'; import cx from 'classnames'; +import PropTypes from 'prop-types'; import additionalProps from '../util/additionalProps'; import { area, stack as d3stack } from 'd3-shape'; +AreaStack.propTypes = { + className: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number, + keys: PropTypes.array, + data: PropTypes.array, + curve: PropTypes.func, + defined: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), + x: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + x0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + x1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + y: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + y0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + y1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + glyph: PropTypes.bool, + reverse: PropTypes.bool +}; + export default function AreaStack({ className, - top = 0, - left = 0, keys, data, curve, diff --git a/packages/vx-shape/src/shapes/BarGroup.js b/packages/vx-shape/src/shapes/BarGroup.js index 4dd2e5210..2348dbe96 100644 --- a/packages/vx-shape/src/shapes/BarGroup.js +++ b/packages/vx-shape/src/shapes/BarGroup.js @@ -3,7 +3,20 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import { Group } from '@vx/group'; import Bar from './Bar'; -import additionalProps from '../util/additionalProps'; + +BarGroup.propTypes = { + data: PropTypes.array.isRequired, + x0: PropTypes.func.isRequired, + x0Scale: PropTypes.func.isRequired, + x1Scale: PropTypes.func.isRequired, + yScale: PropTypes.func.isRequired, + zScale: PropTypes.func.isRequired, + keys: PropTypes.array.isRequired, + height: PropTypes.number.isRequired, + className: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number +}; export default function BarGroup({ data, @@ -27,11 +40,11 @@ export default function BarGroup({ return ( {keys && - keys.map((key, i) => { + keys.map((key, j) => { const value = d[key]; return ( ); } - -BarGroup.propTypes = { - data: PropTypes.array.isRequired, - x0: PropTypes.func.isRequired, - x0Scale: PropTypes.func.isRequired, - x1Scale: PropTypes.func.isRequired, - yScale: PropTypes.func.isRequired, - zScale: PropTypes.func.isRequired, - keys: PropTypes.array.isRequired, - height: PropTypes.number.isRequired, - className: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number -}; diff --git a/packages/vx-shape/src/shapes/BarGroupHorizontal.js b/packages/vx-shape/src/shapes/BarGroupHorizontal.js index 52958cbed..ca586fcfd 100644 --- a/packages/vx-shape/src/shapes/BarGroupHorizontal.js +++ b/packages/vx-shape/src/shapes/BarGroupHorizontal.js @@ -3,7 +3,20 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import { Group } from '@vx/group'; import Bar from './Bar'; -import additionalProps from '../util/additionalProps'; + +BarGroupHorizontal.propTypes = { + data: PropTypes.array.isRequired, + y0: PropTypes.func.isRequired, + y0Scale: PropTypes.func.isRequired, + y1Scale: PropTypes.func.isRequired, + xScale: PropTypes.func.isRequired, + zScale: PropTypes.func.isRequired, + keys: PropTypes.array.isRequired, + width: PropTypes.number.isRequired, + className: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number +}; export default function BarGroupHorizontal({ data, @@ -27,11 +40,11 @@ export default function BarGroupHorizontal({ return ( {keys && - keys.map((key, i) => { + keys.map((key, j) => { const value = d[key]; return ( ); } - -BarGroupHorizontal.propTypes = { - data: PropTypes.array.isRequired, - y0: PropTypes.func.isRequired, - y0Scale: PropTypes.func.isRequired, - y1Scale: PropTypes.func.isRequired, - xScale: PropTypes.func.isRequired, - zScale: PropTypes.func.isRequired, - keys: PropTypes.array.isRequired, - width: PropTypes.number.isRequired, - className: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number -}; diff --git a/packages/vx-shape/src/shapes/BarStack.js b/packages/vx-shape/src/shapes/BarStack.js index 871e778ce..cf6889bf4 100644 --- a/packages/vx-shape/src/shapes/BarStack.js +++ b/packages/vx-shape/src/shapes/BarStack.js @@ -6,6 +6,20 @@ import Bar from './Bar'; import { stack as d3stack } from 'd3-shape'; import objHasMethod from '../util/objHasMethod'; +BarStack.propTypes = { + data: PropTypes.array.isRequired, + x: PropTypes.func.isRequired, + xScale: PropTypes.func.isRequired, + yScale: PropTypes.func.isRequired, + zScale: PropTypes.func.isRequired, + keys: PropTypes.array.isRequired, + className: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number, + width: PropTypes.number, + height: PropTypes.number +}; + export default function BarStack({ data, className, @@ -71,15 +85,3 @@ export default function BarStack({ ); } - -BarStack.propTypes = { - data: PropTypes.array.isRequired, - x: PropTypes.func.isRequired, - xScale: PropTypes.func.isRequired, - yScale: PropTypes.func.isRequired, - zScale: PropTypes.func.isRequired, - keys: PropTypes.array.isRequired, - className: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number -}; diff --git a/packages/vx-shape/src/shapes/BarStackHorizontal.js b/packages/vx-shape/src/shapes/BarStackHorizontal.js index dce567866..159c0591b 100644 --- a/packages/vx-shape/src/shapes/BarStackHorizontal.js +++ b/packages/vx-shape/src/shapes/BarStackHorizontal.js @@ -6,6 +6,20 @@ import Bar from './Bar'; import { stack as d3stack } from 'd3-shape'; import objHasMethod from '../util/objHasMethod'; +BarStackHorizontal.propTypes = { + data: PropTypes.array.isRequired, + y: PropTypes.func.isRequired, + xScale: PropTypes.func.isRequired, + yScale: PropTypes.func.isRequired, + zScale: PropTypes.func.isRequired, + keys: PropTypes.array.isRequired, + className: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number, + width: PropTypes.number, + height: PropTypes.number +}; + export default function BarStackHorizontal({ data, className, @@ -71,15 +85,3 @@ export default function BarStackHorizontal({ ); } - -BarStackHorizontal.propTypes = { - data: PropTypes.array.isRequired, - y: PropTypes.func.isRequired, - xScale: PropTypes.func.isRequired, - yScale: PropTypes.func.isRequired, - zScale: PropTypes.func.isRequired, - keys: PropTypes.array.isRequired, - className: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number -}; diff --git a/packages/vx-shape/src/shapes/Pie.js b/packages/vx-shape/src/shapes/Pie.js index 781cdc636..59d167b99 100644 --- a/packages/vx-shape/src/shapes/Pie.js +++ b/packages/vx-shape/src/shapes/Pie.js @@ -1,9 +1,29 @@ import React from 'react'; import cx from 'classnames'; +import PropTypes from 'prop-types'; import { Group } from '@vx/group'; import { arc as d3Arc, pie as d3Pie } from 'd3-shape'; import additionalProps from '../util/additionalProps'; +Pie.propTypes = { + className: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number, + data: PropTypes.array, + centroid: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + innerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + outerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + cornerRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + startAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + endAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + padAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + padRadius: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + pieSort: PropTypes.func, + pieSortValues: PropTypes.func, + pieValue: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + children: PropTypes.func +}; + export default function Pie({ className = '', top = 0, @@ -54,14 +74,14 @@ export default function Pie({ {children ? children(renderFunctionArg) : arcs.map((arc, i) => { - const pathProps = renderFunctionArg.generatePathProps(arc, i); - return ( - - - {renderFunctionArg.generateCentroid(arc)} - - ); - })} + const pathProps = renderFunctionArg.generatePathProps(arc, i); + return ( + + + {renderFunctionArg.generateCentroid(arc)} + + ); + })}
); } diff --git a/packages/vx-shape/src/shapes/Polygon.js b/packages/vx-shape/src/shapes/Polygon.js index 634d052a9..5a7803bad 100644 --- a/packages/vx-shape/src/shapes/Polygon.js +++ b/packages/vx-shape/src/shapes/Polygon.js @@ -8,30 +8,30 @@ Polygon.propTypes = { sides: PropTypes.number.isRequired, size: PropTypes.number.isRequired, className: PropTypes.string, - rotate: PropTypes.number, + rotate: PropTypes.number }; -export const getPoint = ({ - sides, size, center, rotate, side, -}) => { - const degrees = 360 / sides * side - rotate; +export const getPoint = ({ sides, size, center, rotate, side }) => { + const degrees = (360 / sides) * side - rotate; const radians = degreesToRadians(degrees); return new Point({ x: center.x + size * Math.cos(radians), - y: center.y + size * Math.sin(radians), + y: center.y + size * Math.sin(radians) }); }; -export const getPoints = ({ - sides, size, center, rotate, -}) => [...Array(sides).keys()].map(side => getPoint({ - sides, - size, - center, - rotate, - side, -})); +export const getPoints = ({ sides, size, center, rotate }) => { + return [...Array(sides).keys()].map(side => { + return getPoint({ + sides, + size, + center, + rotate, + side + }); + }); +}; export default function Polygon({ sides, @@ -45,10 +45,10 @@ export default function Polygon({ sides, size, center, - rotate, + rotate }) .map(p => p.toArray()) .join(' '); - return ; + return ; } diff --git a/packages/vx-shape/src/shapes/Stack.js b/packages/vx-shape/src/shapes/Stack.js index 0405d342e..f269d3cac 100644 --- a/packages/vx-shape/src/shapes/Stack.js +++ b/packages/vx-shape/src/shapes/Stack.js @@ -1,11 +1,33 @@ import React from 'react'; import cx from 'classnames'; +import PropTypes from 'prop-types'; import { Group } from '@vx/group'; import additionalProps from '../util/additionalProps'; import stackOrder from '../util/stackOrder'; import stackOffset from '../util/stackOffset'; import { area, stack as d3stack } from 'd3-shape'; +Stack.propTypes = { + className: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number, + keys: PropTypes.array, + data: PropTypes.array, + curve: PropTypes.func, + defined: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), + x: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + x0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + x1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + y: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + y0: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + y1: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + value: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + order: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), + offset: PropTypes.oneOfType([PropTypes.func, PropTypes.array, PropTypes.string]), + render: PropTypes.func, + reverse: PropTypes.bool +}; + export default function Stack({ className, top = 0, @@ -44,12 +66,13 @@ export default function Stack({ const seriesData = stack(data); if (reverse) seriesData.reverse(); - if (render) + if (render) { return ( {render({ seriesData, path })} ); + } return ( diff --git a/packages/vx-shape/test/Pie.test.js b/packages/vx-shape/test/Pie.test.js index 3b4defa12..b7caa886f 100644 --- a/packages/vx-shape/test/Pie.test.js +++ b/packages/vx-shape/test/Pie.test.js @@ -6,6 +6,10 @@ import { browserUsage } from '../../vx-mock-data'; const PieWrapper = ({ ...restProps }) => shallow(); describe('', () => { + beforeEach(() => { + global.console.error = jest.fn(); + }); + test('it should be defined', () => { expect(Pie).toBeDefined(); }); @@ -40,6 +44,8 @@ describe('', () => { test('it should break on invalid sort callbacks', () => { expect(() => PieWrapper({ pieSort: 12 })).toThrow(); expect(() => PieWrapper({ pieSortValues: 12 })).toThrow(); + expect(console.error).toBeCalled(); + expect(console.error.mock.calls.length).toEqual(2); }); test('it should have the .vx-pie-arcs-group class', () => { diff --git a/packages/vx-shape/test/Polygon.test.js b/packages/vx-shape/test/Polygon.test.js index 528d67396..82d41fe7d 100644 --- a/packages/vx-shape/test/Polygon.test.js +++ b/packages/vx-shape/test/Polygon.test.js @@ -22,7 +22,7 @@ describe('', () => { it('should add classname', () => { const wrapper = PolygonWrapper({ sides: 6, size: 25, className: 'a-polygon' }); - expect(wrapper.prop('className')).toBe('a-polygon'); + expect(wrapper.prop('className')).toBe('vx-polygon a-polygon'); }); it('should add onClick handler', () => { @@ -32,7 +32,7 @@ describe('', () => { sides: 6, size: 25, className: 'a-polygon', - onClick: fn, + onClick: fn }); wrapper.simulate('click'); diff --git a/packages/vx-stats/src/util/computeStats.js b/packages/vx-stats/src/util/computeStats.js index 0675bf2b1..7eb4af478 100644 --- a/packages/vx-stats/src/util/computeStats.js +++ b/packages/vx-stats/src/util/computeStats.js @@ -1,8 +1,8 @@ -export default function(numericalArray) { +export default function computeStats(numericalArray) { const points = [...numericalArray].sort((a, b) => a - b); const sampleSize = points.length; const firstQuartile = points[Math.round(sampleSize / 4)]; - const thirdQuartile = points[Math.round(3 * sampleSize / 4)]; + const thirdQuartile = points[Math.round((3 * sampleSize) / 4)]; const IQR = thirdQuartile - firstQuartile; const min = firstQuartile - 1.5 * IQR; diff --git a/packages/vx-text/package.json b/packages/vx-text/package.json index 0c415737c..9003f5743 100644 --- a/packages/vx-text/package.json +++ b/packages/vx-text/package.json @@ -37,6 +37,7 @@ "babel-plugin-lodash": "^3.3.2", "classnames": "^2.2.5", "lodash": "^4.17.4", + "prop-types": "^15.6.2", "reduce-css-calc": "^1.3.0" }, "devDependencies": { diff --git a/packages/vx-text/src/Text.js b/packages/vx-text/src/Text.js index ba8c78433..b04b39ac8 100644 --- a/packages/vx-text/src/Text.js +++ b/packages/vx-text/src/Text.js @@ -1,165 +1,170 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import reduceCSSCalc from 'reduce-css-calc'; -import getStringWidth from './util/getStringWidth'; - -class Text extends Component { - constructor(props) { - super(props); - this.state = { - wordsByLines: [] - }; - } - - componentWillMount() { - this.updateWordsByLines(this.props, true); - } - - componentWillReceiveProps(nextProps) { - const needCalculate = - this.props.children !== nextProps.children || this.props.style !== nextProps.style; - this.updateWordsByLines(nextProps, needCalculate); - } - - updateWordsByLines(props, needCalculate) { - // Only perform calculations if using features that require them (multiline, scaleToFit) - if (props.width || props.scaleToFit) { - if (needCalculate) { - const words = props.children ? props.children.toString().split(/\s+/) : []; - - this.wordsWithComputedWidth = words.map(word => ({ - word, - width: getStringWidth(word, props.style) - })); - this.spaceWidth = getStringWidth('\u00A0', props.style); - } - - const wordsByLines = this.calculateWordsByLines( - this.wordsWithComputedWidth, - this.spaceWidth, - props.width - ); - this.setState({ wordsByLines }); - } else { - this.updateWordsWithoutCalculate(props); - } - } - - updateWordsWithoutCalculate(props) { - const words = props.children ? props.children.toString().split(/\s+/) : []; - this.setState({ wordsByLines: [{ words }] }); - } - - calculateWordsByLines(wordsWithComputedWidth, spaceWidth, lineWidth) { - const { scaleToFit } = this.props; - return wordsWithComputedWidth.reduce((result, { word, width }) => { - const currentLine = result[result.length - 1]; - - if ( - currentLine && - (lineWidth == null || scaleToFit || currentLine.width + width + spaceWidth < lineWidth) - ) { - // Word can be added to an existing line - currentLine.words.push(word); - currentLine.width += width + spaceWidth; - } else { - // Add first word to line or word is too long to scaleToFit on existing line - const newLine = { words: [word], width }; - result.push(newLine); - } - - return result; - }, []); - } - - render() { - const { - dx, - dy, - textAnchor, - verticalAnchor, - scaleToFit, - angle, - lineHeight, - capHeight, - innerRef, - ...textProps - } = this.props; - const { wordsByLines } = this.state; - - const x = textProps.x; - const y = textProps.y; - - let startDy; - switch (verticalAnchor) { - case 'start': - startDy = reduceCSSCalc(`calc(${capHeight})`); - break; - case 'middle': - startDy = reduceCSSCalc( - `calc(${(wordsByLines.length - 1) / 2} * -${lineHeight} + (${capHeight} / 2))` - ); - break; - default: - startDy = reduceCSSCalc(`calc(${wordsByLines.length - 1} * -${lineHeight})`); - break; - } - - const transforms = []; - if (scaleToFit && wordsByLines.length) { - const lineWidth = wordsByLines[0].width; - const sx = this.props.width / lineWidth; - const sy = sx; - const originX = x - sx * x; - const originY = y - sy * y; - transforms.push(`matrix(${sx}, 0, 0, ${sy}, ${originX}, ${originY})`); - } - if (angle) { - transforms.push(`rotate(${angle}, ${x}, ${y})`); - } - if (transforms.length) { - textProps.transform = transforms.join(' '); - } - - return ( - - - {wordsByLines.map((line, index) => ( - - {line.words.join(' ')} - - ))} - - - ); - } -} - -Text.defaultProps = { - x: 0, - y: 0, - dx: 0, - dy: 0, - lineHeight: '1em', - capHeight: '0.71em', // Magic number from d3 - scaleToFit: false, - textAnchor: 'start', - verticalAnchor: 'end' // default SVG behavior -}; - -Text.propTypes = { - scaleToFit: PropTypes.bool, - angle: PropTypes.number, - textAnchor: PropTypes.oneOf(['start', 'middle', 'end', 'inherit']), - verticalAnchor: PropTypes.oneOf(['start', 'middle', 'end']), - style: PropTypes.object, - innerRef: PropTypes.func -}; - -export default Text; +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import reduceCSSCalc from 'reduce-css-calc'; +import getStringWidth from './util/getStringWidth'; + +class Text extends Component { + constructor(props) { + super(props); + this.state = { + wordsByLines: [] + }; + } + + componentWillMount() { + this.updateWordsByLines(this.props, true); + } + + componentWillReceiveProps(nextProps) { + const needCalculate = + this.props.children !== nextProps.children || this.props.style !== nextProps.style; + this.updateWordsByLines(nextProps, needCalculate); + } + + updateWordsByLines(props, needCalculate) { + // Only perform calculations if using features that require them (multiline, scaleToFit) + if (props.width || props.scaleToFit) { + if (needCalculate) { + const words = props.children ? props.children.toString().split(/\s+/) : []; + + this.wordsWithComputedWidth = words.map(word => ({ + word, + width: getStringWidth(word, props.style) + })); + this.spaceWidth = getStringWidth('\u00A0', props.style); + } + + const wordsByLines = this.calculateWordsByLines( + this.wordsWithComputedWidth, + this.spaceWidth, + props.width + ); + this.setState({ wordsByLines }); + } else { + this.updateWordsWithoutCalculate(props); + } + } + + updateWordsWithoutCalculate(props) { + const words = props.children ? props.children.toString().split(/\s+/) : []; + this.setState({ wordsByLines: [{ words }] }); + } + + calculateWordsByLines(wordsWithComputedWidth, spaceWidth, lineWidth) { + const { scaleToFit } = this.props; + return wordsWithComputedWidth.reduce((result, { word, width }) => { + const currentLine = result[result.length - 1]; + + if ( + currentLine && + (lineWidth == null || scaleToFit || currentLine.width + width + spaceWidth < lineWidth) + ) { + // Word can be added to an existing line + currentLine.words.push(word); + currentLine.width += width + spaceWidth; + } else { + // Add first word to line or word is too long to scaleToFit on existing line + const newLine = { words: [word], width }; + result.push(newLine); + } + + return result; + }, []); + } + + render() { + const { + dx, + dy, + textAnchor, + verticalAnchor, + scaleToFit, + angle, + lineHeight, + capHeight, + innerRef, + ...textProps + } = this.props; + const { wordsByLines } = this.state; + + const { x, y } = textProps; + + let startDy; + switch (verticalAnchor) { + case 'start': + startDy = reduceCSSCalc(`calc(${capHeight})`); + break; + case 'middle': + startDy = reduceCSSCalc( + `calc(${(wordsByLines.length - 1) / 2} * -${lineHeight} + (${capHeight} / 2))` + ); + break; + default: + startDy = reduceCSSCalc(`calc(${wordsByLines.length - 1} * -${lineHeight})`); + break; + } + + const transforms = []; + if (scaleToFit && wordsByLines.length) { + const lineWidth = wordsByLines[0].width; + const sx = this.props.width / lineWidth; + const sy = sx; + const originX = x - sx * x; + const originY = y - sy * y; + transforms.push(`matrix(${sx}, 0, 0, ${sy}, ${originX}, ${originY})`); + } + if (angle) { + transforms.push(`rotate(${angle}, ${x}, ${y})`); + } + if (transforms.length) { + textProps.transform = transforms.join(' '); + } + + return ( + + + {wordsByLines.map((line, index) => ( + + {line.words.join(' ')} + + ))} + + + ); + } +} + +Text.defaultProps = { + x: 0, + y: 0, + dx: 0, + dy: 0, + lineHeight: '1em', + capHeight: '0.71em', // Magic number from d3 + scaleToFit: false, + textAnchor: 'start', + verticalAnchor: 'end' // default SVG behavior +}; + +Text.propTypes = { + scaleToFit: PropTypes.bool, + angle: PropTypes.number, + textAnchor: PropTypes.oneOf(['start', 'middle', 'end', 'inherit']), + verticalAnchor: PropTypes.oneOf(['start', 'middle', 'end']), + style: PropTypes.object, + innerRef: PropTypes.func, + x: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + dx: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + dy: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + lineHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + capHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]) +}; + +export default Text; diff --git a/packages/vx-tooltip/src/tooltips/Tooltip.js b/packages/vx-tooltip/src/tooltips/Tooltip.js index eb44515c8..3bb55d48f 100644 --- a/packages/vx-tooltip/src/tooltips/Tooltip.js +++ b/packages/vx-tooltip/src/tooltips/Tooltip.js @@ -2,6 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; +Tooltip.propTypes = { + left: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + top: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.any +}; + export default function Tooltip({ className, top, left, style, children, ...restProps }) { return (
); } - -Tooltip.propTypes = { - left: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - top: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - className: PropTypes.string, - style: PropTypes.object, - children: PropTypes.any -}; diff --git a/packages/vx-tooltip/src/tooltips/TooltipWithBounds.js b/packages/vx-tooltip/src/tooltips/TooltipWithBounds.js index b9a4e4117..7b7932247 100644 --- a/packages/vx-tooltip/src/tooltips/TooltipWithBounds.js +++ b/packages/vx-tooltip/src/tooltips/TooltipWithBounds.js @@ -1,13 +1,35 @@ -/* eslint react/forbid-prop-types: 0 */ -import PropTypes from 'prop-types'; import React from 'react'; -import { withBoundingRects, withBoundingRectsProps } from '@vx/bounds'; +import PropTypes from 'prop-types'; +import { withBoundingRects } from '@vx/bounds'; import Tooltip from './Tooltip'; +const rectShape = PropTypes.shape({ + top: PropTypes.number.isRequired, + right: PropTypes.number.isRequired, + bottom: PropTypes.number.isRequired, + left: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired +}); + +const withBoundingRectsProps = { + getRects: PropTypes.func, + rect: rectShape, + parentRect: rectShape +}; + +const tooltipProps = { + left: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + top: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.any +}; + const propTypes = { ...withBoundingRectsProps, - ...Tooltip.propTypes, + ...tooltipProps, offsetLeft: PropTypes.number, offsetTop: PropTypes.number };