victory-bar
Version:
Bar Component for Victory
443 lines (442 loc) • 16.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getVerticalPolarBarPath = exports.getVerticalBarPath = exports.getPolarBarPath = exports.getHorizontalBarPath = exports.getCustomBarPath = exports.getBarPath = void 0;
var d3Shape = _interopRequireWildcard(require("victory-vendor/d3-shape"));
var _geometryHelperMethods = require("./geometry-helper-methods");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const getPosition = (props, width) => {
const {
x,
x0,
y,
y0,
horizontal
} = props;
const alignment = props.alignment || "middle";
const size = alignment === "middle" ? width / 2 : width;
const sign = horizontal ? -1 : 1;
if (horizontal) {
return {
x0,
x1: x,
y0: alignment === "start" ? y : y - sign * size,
y1: alignment === "end" ? y : y + sign * size
};
}
return {
x0: alignment === "start" ? x : x - sign * size,
x1: alignment === "end" ? x : x + sign * size,
y0,
y1: y
};
};
const getAngle = (props, index) => {
const {
data,
scale
} = props;
const x = data[index]._x1 === undefined ? "_x" : "_x1";
return scale.x(data[index][x]);
};
const getAngularWidth = (props, width) => {
const {
scale
} = props;
const range = scale.y.range();
const r = Math.max(...range);
const angularRange = Math.abs(scale.x.range()[1] - scale.x.range()[0]);
return width / (2 * Math.PI * r) * angularRange;
};
const transformAngle = angle => {
return -1 * angle + Math.PI / 2;
};
const getCustomBarPath = (props, width) => {
const {
getPath
} = props;
if (typeof getPath === "function") {
const propsWithCalculatedValues = {
...props,
...getPosition(props, width)
};
return getPath(propsWithCalculatedValues);
}
};
exports.getCustomBarPath = getCustomBarPath;
const getStartAngle = (props, index) => {
const {
data,
scale,
alignment
} = props;
const currentAngle = getAngle(props, index);
const angularRange = Math.abs(scale.x.range()[1] - scale.x.range()[0]);
const previousAngle = index === 0 ? getAngle(props, data.length - 1) - Math.PI * 2 : getAngle(props, index - 1);
if (index === 0 && angularRange < 2 * Math.PI) {
return scale.x.range()[0];
} else if (alignment === "start" || alignment === "end") {
return alignment === "start" ? previousAngle : currentAngle;
}
return (currentAngle + previousAngle) / 2;
};
const getEndAngle = (props, index) => {
const {
data,
scale,
alignment
} = props;
const currentAngle = getAngle(props, index);
const angularRange = Math.abs(scale.x.range()[1] - scale.x.range()[0]);
const lastAngle = scale.x.range()[1] === 2 * Math.PI ? getAngle(props, 0) + Math.PI * 2 : scale.x.range()[1];
const nextAngle = index === data.length - 1 ? getAngle(props, 0) + Math.PI * 2 : getAngle(props, index + 1);
if (index === data.length - 1 && angularRange < 2 * Math.PI) {
return lastAngle;
} else if (alignment === "start" || alignment === "end") {
return alignment === "start" ? currentAngle : nextAngle;
}
return (currentAngle + nextAngle) / 2;
};
const mapPointsToPath = (coords, cornerRadius, direction) => {
const topLeftPath = `${cornerRadius.topLeft} ${cornerRadius.topLeft} ${direction}`;
const topRightPath = `${cornerRadius.topRight} ${cornerRadius.topRight} ${direction}`;
const bottomLeftPath = `${cornerRadius.bottomLeft} ${cornerRadius.bottomLeft} ${direction}`;
const bottomRightPath = `${cornerRadius.bottomRight} ${cornerRadius.bottomRight} ${direction}`;
const commands = ["M", `A ${bottomLeftPath},`, "L", `A ${topLeftPath},`, "L", `A ${topRightPath},`, "L", `A ${bottomRightPath},`];
const path = commands.reduce((acc, command, i) => `${acc}${command} ${coords[i].x}, ${coords[i].y} \n`, "");
return `${path} z`;
};
const getVerticalBarPoints = (position, sign, cr) => {
const {
x0,
x1,
y0,
y1
} = position;
const getHalfPoints = side => {
const isLeft = side === "Left";
const signL = isLeft ? 1 : -1;
const x = isLeft ? x0 : x1;
let bottomPoint = {
x: x + signL * cr[`bottom${side}`],
y: y0
};
let bottomMiddlePoint = {
x,
y: y0 - sign * cr[`bottom${side}`]
};
let topMiddlePoint = {
x,
y: y1 + sign * cr[`top${side}`]
};
let topPoint = {
x: x + signL * cr[`top${side}`],
y: y1
};
const hasIntersection = sign === 1 ? y0 - cr[`bottom${side}`] < y1 + cr[`top${side}`] : y0 + cr[`bottom${side}`] > y1 - cr[`top${side}`];
if (hasIntersection) {
const topCenter = (0, _geometryHelperMethods.point)(x + signL * cr[`top${side}`], y1 + sign * cr[`top${side}`]);
const topCircle = (0, _geometryHelperMethods.circle)(topCenter, cr[`top${side}`]);
const bottomCenter = (0, _geometryHelperMethods.point)(x + signL * cr[`bottom${side}`], y0 - sign * cr[`bottom${side}`]);
const bottomCircle = (0, _geometryHelperMethods.circle)(bottomCenter, cr[`bottom${side}`]);
const circleIntersection = topCircle.intersection(bottomCircle);
const hasArcIntersection = circleIntersection.length > 0;
if (hasArcIntersection) {
const arcIntersection = circleIntersection[isLeft ? 0 : 1];
bottomMiddlePoint = {
x: arcIntersection.x,
y: arcIntersection.y
};
topMiddlePoint = {
x: arcIntersection.x,
y: arcIntersection.y
};
} else {
const hasBottomLineTopArcIntersection = cr[`top${side}`] > cr[`bottom${side}`];
if (hasBottomLineTopArcIntersection) {
const newX = topCircle.solveX(y0)[isLeft ? 0 : 1];
bottomPoint = {
x: newX,
y: y0
};
bottomMiddlePoint = {
x: newX,
y: y0
};
topMiddlePoint = {
x: newX,
y: y0
};
} else {
const newX = bottomCircle.solveX(y1)[isLeft ? 0 : 1];
bottomMiddlePoint = {
x: newX,
y: y1
};
topMiddlePoint = {
x: newX,
y: y1
};
topPoint = {
x: newX,
y: y1
};
}
}
}
const points = [bottomPoint, bottomMiddlePoint, topMiddlePoint, topPoint];
return isLeft ? points : points.reverse();
};
return getHalfPoints("Left").concat(getHalfPoints("Right"));
};
const getHorizontalBarPoints = (position, sign, cr) => {
const {
y0,
y1
} = position;
const x0 = position.x0 < position.x1 ? position.x0 : position.x1;
const x1 = position.x0 < position.x1 ? position.x1 : position.x0;
const getHalfPoints = side => {
const isTop = side === "top";
const signL = isTop ? -1 : 1;
const y = isTop ? y1 : y0;
let leftPoint = {
x: x0,
y: y - signL * cr[`${side}Left`]
};
let leftMiddlePoint = {
x: x0 + cr[`${side}Left`],
y
};
let rightMiddlePoint = {
x: x1 - cr[`${side}Right`],
y
};
let rightPoint = {
x: x1,
y: y - signL * cr[`${side}Right`]
};
const hasIntersection = leftMiddlePoint.x > rightMiddlePoint.x;
if (hasIntersection) {
const leftCenter = (0, _geometryHelperMethods.point)(x0 + cr[`${side}Left`], y - signL * cr[`${side}Left`]);
const leftCircle = (0, _geometryHelperMethods.circle)(leftCenter, cr[`${side}Left`]);
const rightCenter = (0, _geometryHelperMethods.point)(x1 - cr[`${side}Right`], y - signL * cr[`${side}Right`]);
const rightCircle = (0, _geometryHelperMethods.circle)(rightCenter, cr[`${side}Right`]);
const circleIntersection = leftCircle.intersection(rightCircle);
const hasArcIntersection = circleIntersection.length > 0;
if (hasArcIntersection) {
const arcIntersection = circleIntersection[sign > 0 ? 1 : 0];
leftMiddlePoint = {
x: arcIntersection.x,
y: arcIntersection.y
};
rightMiddlePoint = {
x: arcIntersection.x,
y: arcIntersection.y
};
} else {
const hasLeftLineRightArcIntersection = cr[`${side}Right`] > cr[`${side}Left`];
if (hasLeftLineRightArcIntersection) {
const newY = rightCircle.solveY(x0)[isTop ? 0 : 1];
leftPoint = {
x: x0,
y: newY
};
leftMiddlePoint = {
x: x0,
y: newY
};
rightMiddlePoint = {
x: x0,
y: newY
};
} else {
const newY = leftCircle.solveY(x1)[isTop ? 0 : 1];
rightPoint = {
x: x1,
y: newY
};
rightMiddlePoint = {
x: x1,
y: newY
};
leftMiddlePoint = {
x: x1,
y: newY
};
}
}
}
return [leftPoint, leftMiddlePoint, rightMiddlePoint, rightPoint];
};
const topPoints = getHalfPoints("top");
const bottomPoints = getHalfPoints("bottom");
return [bottomPoints[1], bottomPoints[0], ...topPoints,
// eslint-disable-next-line no-magic-numbers
bottomPoints[3], bottomPoints[2]];
};
const getVerticalBarPath = (props, width, cornerRadius) => {
const position = getPosition(props, width);
const sign = position.y0 > position.y1 ? 1 : -1;
const direction = sign > 0 ? "0 0 1" : "0 0 0";
const points = getVerticalBarPoints(position, sign, cornerRadius);
return mapPointsToPath(points, cornerRadius, direction);
};
exports.getVerticalBarPath = getVerticalBarPath;
const getHorizontalBarPath = (props, width, cornerRadius) => {
const position = getPosition(props, width);
const sign = position.x0 < position.x1 ? 1 : -1;
const direction = "0 0 1";
const cr = {
topRight: sign > 0 ? cornerRadius.topLeft : cornerRadius.bottomLeft,
bottomRight: sign > 0 ? cornerRadius.topRight : cornerRadius.bottomRight,
bottomLeft: sign > 0 ? cornerRadius.bottomRight : cornerRadius.topRight,
topLeft: sign > 0 ? cornerRadius.bottomLeft : cornerRadius.topLeft
};
const points = getHorizontalBarPoints(position, sign, cr);
return mapPointsToPath(points, cr, direction);
};
exports.getHorizontalBarPath = getHorizontalBarPath;
const getVerticalPolarBarPath = (props, cornerRadius) => {
const {
datum,
scale,
index,
alignment,
style
} = props;
const r1 = scale.y(datum._y0 || 0);
const r2 = scale.y(datum._y1 !== undefined ? datum._y1 : datum._y);
const currentAngle = scale.x(datum._x1 !== undefined ? datum._x1 : datum._x);
let start;
let end;
if (style.width) {
const width = getAngularWidth(props, style.width);
const size = alignment === "middle" ? width / 2 : width;
start = alignment === "start" ? currentAngle : currentAngle - size;
end = alignment === "end" ? currentAngle : currentAngle + size;
} else {
start = getStartAngle(props, Number(index));
end = getEndAngle(props, Number(index));
}
const getPath = edge => {
const pathFunction = d3Shape.arc().innerRadius(r1).outerRadius(r2).startAngle(transformAngle(start)).endAngle(transformAngle(end)).cornerRadius(cornerRadius[edge]);
return pathFunction();
};
const getPathData = edge => {
const rightPath = getPath(`${edge}Right`);
const rightMoves = rightPath.match(/[A-Z]/g) || [];
const rightCoords = rightPath.split(/[A-Z]/).slice(1);
const rightMiddle = rightMoves.indexOf("L");
const leftPath = getPath(`${edge}Left`);
const leftMoves = leftPath.match(/[A-Z]/g) || [];
const leftCoords = leftPath.split(/[A-Z]/).slice(1);
const leftMiddle = leftMoves.indexOf("L");
return {
rightMoves,
rightCoords,
rightMiddle,
leftMoves,
leftCoords,
leftMiddle
};
};
const getTopPath = () => {
const {
topRight,
topLeft
} = cornerRadius;
const arcLength = r2 * Math.abs(end - start);
const {
rightMoves,
rightCoords,
rightMiddle,
leftMoves,
leftCoords,
leftMiddle
} = getPathData("top");
let moves;
let coords;
if (topRight === topLeft || arcLength < 2 * topRight + 2 * topLeft) {
moves = topRight > topLeft ? rightMoves : leftMoves;
coords = topRight > topLeft ? rightCoords : leftCoords;
} else {
// eslint-disable-next-line no-magic-numbers
const isShort = middle => middle < 3;
const rightOffset = topLeft > topRight && isShort(rightMiddle) ? 1 : 2;
let leftOffset;
if (topRight > topLeft) {
const defaultOffset = isShort(rightMiddle) ? leftMiddle : leftMiddle - 2;
leftOffset = isShort(leftMiddle) ? leftMiddle - 1 : defaultOffset;
} else {
const defaultOffset = isShort(leftMiddle) ? 1 : 2;
leftOffset = isShort(rightMiddle) ? defaultOffset : leftMiddle - 2;
}
moves = [...rightMoves.slice(0, rightOffset), ...leftMoves.slice(leftOffset)];
coords = [...rightCoords.slice(0, rightOffset), ...leftCoords.slice(leftOffset)];
}
const middle = moves.indexOf("L");
const subMoves = moves.slice(0, middle);
const subCoords = coords.slice(0, middle);
return subMoves.map((m, i) => ({
command: m,
coords: subCoords[i].split(",")
}));
};
const getBottomPath = () => {
const {
bottomRight,
bottomLeft
} = cornerRadius;
const arcLength = r1 * Math.abs(end - start);
const {
rightMoves,
rightCoords,
rightMiddle,
leftMoves,
leftCoords,
leftMiddle
} = getPathData("bottom");
let moves;
let coords;
if (bottomRight === bottomLeft || arcLength < 2 * bottomRight + 2 * bottomLeft) {
moves = bottomRight > bottomLeft ? rightMoves : leftMoves;
coords = bottomRight > bottomLeft ? rightCoords : leftCoords;
} else {
// eslint-disable-next-line no-magic-numbers
const isShort = (m, middle) => m.length - middle < 4;
const shortPath = bottomRight > bottomLeft ? isShort(rightMoves, rightMiddle) : isShort(leftMoves, leftMiddle);
// eslint-disable-next-line no-magic-numbers
const rightOffset = shortPath ? -1 : -3;
moves = [...leftMoves.slice(0, leftMiddle + 2), ...rightMoves.slice(rightOffset)];
coords = [...leftCoords.slice(0, leftMiddle + 2), ...rightCoords.slice(rightOffset)];
}
const middle = moves.indexOf("L");
const subMoves = moves.slice(middle, -1);
const subCoords = coords.slice(middle, -1);
return subMoves.map((m, i) => ({
command: m,
coords: subCoords[i].split(",")
}));
};
const topPath = getTopPath();
const bottomPath = getBottomPath();
const moves = [...topPath, ...bottomPath];
const path = moves.reduce((memo, move) => `${memo}${move.command} ${move.coords.join()}`, "");
return `${path} z`;
};
exports.getVerticalPolarBarPath = getVerticalPolarBarPath;
const getBarPath = (props, width, cornerRadius) => {
if (props.getPath) {
return getCustomBarPath(props, width);
}
return props.horizontal ? getHorizontalBarPath(props, width, cornerRadius) : getVerticalBarPath(props, width, cornerRadius);
};
exports.getBarPath = getBarPath;
const getPolarBarPath = (props, cornerRadius) => {
// TODO Radial bars
return getVerticalPolarBarPath(props, cornerRadius);
};
exports.getPolarBarPath = getPolarBarPath;