diff --git a/src/victory-primitives/bar.js b/src/victory-primitives/bar.js index dd4257d..f9a3fa6 100644 --- a/src/victory-primitives/bar.js +++ b/src/victory-primitives/bar.js @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; import Helpers from "../victory-util/helpers"; -import { assign } from "lodash"; +import { assign, isObject } from "lodash"; import CommonProps from "./common-props"; import Path from "./path"; import * as d3Shape from "d3-shape"; @@ -12,7 +12,14 @@ export default class Bar extends React.Component { ...CommonProps, alignment: PropTypes.oneOf(["start", "middle", "end"]), barRatio: PropTypes.number, - cornerRadius: PropTypes.number, + cornerRadius: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.func, + PropTypes.shape({ + top: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + bottom: PropTypes.oneOfType([PropTypes.number, PropTypes.func]) + }) + ]), datum: PropTypes.object, horizontal: PropTypes.bool, padding: PropTypes.oneOfType([ @@ -43,35 +50,37 @@ export default class Bar extends React.Component { }; } - getVerticalBarPath(props, width) { + getVerticalBarPath(props, width, cornerRadius) { const { x0, x1, y0, y1 } = this.getPosition(props, width); - const cornerRadius = props.cornerRadius || 0; const sign = y0 > y1 ? 1 : -1; const direction = sign > 0 ? "0 0 1" : "0 0 0"; - const arc = `${cornerRadius} ${cornerRadius} ${direction}`; - return `M ${x0}, ${y0} - L ${x0}, ${y1 + sign * cornerRadius} - A ${arc}, ${x0 + cornerRadius}, ${y1} - L ${x1 - cornerRadius}, ${y1} - A ${arc}, ${x1}, ${y1 + sign * cornerRadius} - L ${x1}, ${y0} - L ${x0}, ${y0} + const topArc = `${cornerRadius.top} ${cornerRadius.top} ${direction}`; + const bottomArc = `${cornerRadius.bottom} ${cornerRadius.bottom} ${direction}`; + return `M ${x0 + cornerRadius.bottom}, ${y0} + A ${bottomArc}, ${x0}, ${y0 - sign * cornerRadius.bottom} + L ${x0}, ${y1 + sign * cornerRadius.top} + A ${topArc}, ${x0 + cornerRadius.top}, ${y1} + L ${x1 - cornerRadius.top}, ${y1} + A ${topArc}, ${x1}, ${y1 + sign * cornerRadius.top} + L ${x1}, ${y0 - sign * cornerRadius.bottom} + A ${bottomArc}, ${x1 - cornerRadius.bottom}, ${y0} z`; } - getHorizontalBarPath(props, width) { + getHorizontalBarPath(props, width, cornerRadius) { const { x0, x1, y0, y1 } = this.getPosition(props, width); - const cornerRadius = props.cornerRadius || 0; const sign = y1 > y0 ? 1 : -1; const direction = sign > 0 ? "0 0 1" : "0 0 0"; - const arc = `${cornerRadius} ${cornerRadius} ${direction}`; - return `M ${y0}, ${x0} - L ${y0}, ${x1} - L ${y1 - sign * cornerRadius}, ${x1} - A ${arc}, ${y1}, ${x1 + cornerRadius} - L ${y1}, ${x0 - cornerRadius} - A ${arc}, ${y1 - sign * cornerRadius}, ${x0} - L ${y0}, ${x0} + const topArc = `${cornerRadius.top} ${cornerRadius.top} ${direction}`; + const bottomArc = `${cornerRadius.bottom} ${cornerRadius.bottom} ${direction}`; + return `M ${y0}, ${x1 + sign * cornerRadius.bottom} + A ${bottomArc}, ${y0 + cornerRadius.bottom}, ${x1} + L ${y1 - sign * cornerRadius.top}, ${x1} + A ${topArc}, ${y1}, ${x1 + cornerRadius.top} + L ${y1}, ${x0 - cornerRadius.top} + A ${topArc}, ${y1 - sign * cornerRadius.top}, ${x0} + L ${y0 + cornerRadius.bottom}, ${x0 } + A ${bottomArc}, ${y0}, ${x1 + sign * cornerRadius.bottom} z`; } @@ -126,13 +135,7 @@ export default class Bar extends React.Component { } } - getPathMoves(path) { - const moves = path.match(/[A-Z]/g); - const coords = path.split(/[A-Z]/); - return moves.map((m, i) => ({ command: m, coords: coords[i + 1].split(",") })); - } - - getVerticalPolarBarPath(props) { // eslint-disable-line max-statements + getVerticalPolarBarPath(props, cornerRadius) { // eslint-disable-line max-statements const { datum, scale, style, index, alignment } = props; const r1 = scale.y(datum._y0 || 0); const r2 = scale.y(datum._y1 !== undefined ? datum._y1 : datum._y); @@ -148,39 +151,39 @@ export default class Bar extends React.Component { start = this.getStartAngle(props, index); end = this.getEndAngle(props, index); } - const cornerRadius = props.cornerRadius || 0; - const path = d3Shape.arc() + + const getPath = (edge) => { + const pathFunction = d3Shape.arc() .innerRadius(r1) .outerRadius(r2) .startAngle(this.transformAngle(start)) - .endAngle(this.transformAngle(end)); - if (cornerRadius) { - const withCorners = d3Shape.arc() - .innerRadius(r1) - .outerRadius(r2) - .startAngle(this.transformAngle(start)) - .endAngle(this.transformAngle(end)) - .cornerRadius(cornerRadius); - const moves = this.getPathMoves(path()); - const cornerMoves = this.getPathMoves(withCorners()); - // eslint-disable-next-line no-magic-numbers - const totalMoves = cornerMoves.slice(0, 4).concat(moves.slice(2)); - return totalMoves.reduce((memo, move) => { - memo += `${move.command} ${move.coords.join()}`; - return memo; - }, ""); - } - return path(); + .endAngle(this.transformAngle(end)) + .cornerRadius(cornerRadius[edge]); + const path = pathFunction(); + const moves = path.match(/[A-Z]/g); + const middle = moves.indexOf("L"); + const coords = path.split(/[A-Z]/).slice(1); + const subMoves = edge === "top" ? moves.slice(0, middle) : moves.slice(middle); + const subCoords = edge === "top" ? coords.slice(0, middle) : coords.slice(middle); + return subMoves.map((m, i) => ({ command: m, coords: subCoords[i].split(",") })); + }; + + const moves = getPath("top").concat(getPath("bottom")); + return moves.reduce((memo, move) => { + memo += `${move.command} ${move.coords.join()}`; + return memo; + }, ""); } - getBarPath(props, width) { + getBarPath(props, width, cornerRadius) { return this.props.horizontal ? - this.getHorizontalBarPath(props, width) : this.getVerticalBarPath(props, width); + this.getHorizontalBarPath(props, width, cornerRadius) : + this.getVerticalBarPath(props, width, cornerRadius); } - getPolarBarPath(props) { + getPolarBarPath(props, cornerRadius) { // TODO Radial bars - return this.getVerticalPolarBarPath(props); + return this.getVerticalPolarBarPath(props, cornerRadius); } getBarWidth(props, style) { @@ -197,6 +200,23 @@ export default class Bar extends React.Component { return Math.max(1, defaultWidth); } + getCornerRadius(props) { + const { cornerRadius, datum, active } = props; + if (!cornerRadius) { + return { top: 0, bottom: 0 }; + } else if (isObject(cornerRadius)) { + return { + top: Helpers.evaluateProp(cornerRadius.top, datum, active), + bottom: Helpers.evaluateProp(cornerRadius.bottom, datum, active) + }; + } else { + return { + top: Helpers.evaluateProp(cornerRadius, datum, active), + bottom: 0 + }; + } + } + render() { const { role, datum, active, shapeRendering, className, origin, polar, pathComponent, events @@ -205,8 +225,10 @@ export default class Bar extends React.Component { const baseStyle = { fill: "black", stroke }; const style = Helpers.evaluateStyle(assign(baseStyle, this.props.style), datum, active); const width = this.getBarWidth(this.props, style); + const cornerRadius = this.getCornerRadius(this.props); const path = polar ? - this.getPolarBarPath(this.props, width) : this.getBarPath(this.props, width); + this.getPolarBarPath(this.props, cornerRadius) : + this.getBarPath(this.props, width, cornerRadius); const transform = polar && origin ? `translate(${origin.x}, ${origin.y})` : undefined; return React.cloneElement(pathComponent, { d: path, transform, className, style, role, shapeRendering, events