victory-core
Version:
477 lines (475 loc) • 18.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.VictoryLabel = void 0;
var _react = _interopRequireDefault(require("react"));
var _defaults = _interopRequireDefault(require("lodash/defaults"));
var _isEmpty = _interopRequireDefault(require("lodash/isEmpty"));
var _victoryPortal = require("../victory-portal/victory-portal");
var _rect = require("../victory-primitives/rect");
var _text = require("../victory-primitives/text");
var _tspan = require("../victory-primitives/tspan");
var Helpers = _interopRequireWildcard(require("../victory-util/helpers"));
var LabelHelpers = _interopRequireWildcard(require("../victory-util/label-helpers"));
var Log = _interopRequireWildcard(require("../victory-util/log"));
var Style = _interopRequireWildcard(require("../victory-util/style"));
var TextSize = _interopRequireWildcard(require("../victory-util/textsize"));
var UserProps = _interopRequireWildcard(require("../victory-util/user-props"));
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; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* eslint no-magic-numbers: ["error", { "ignore": [-0.5, 0.5, 0, 1, 2] }]*/
const defaultStyles = {
fill: "#252525",
fontSize: 14,
fontFamily: "'Gill Sans', 'Gill Sans MT', 'Seravek', 'Trebuchet MS', sans-serif",
stroke: "transparent"
};
const getPosition = (props, dimension) => {
if (!props.datum) {
return 0;
}
const scaledPoint = Helpers.scalePoint(props, props.datum);
return scaledPoint[dimension];
};
const getFontSize = style => {
const baseSize = style && style.fontSize;
if (typeof baseSize === "number") {
return baseSize;
} else if (baseSize === undefined || baseSize === null) {
return defaultStyles.fontSize;
} else if (typeof baseSize === "string") {
const fontSize = Number(baseSize.replace("px", ""));
if (!isNaN(fontSize)) {
return fontSize;
}
Log.warn("fontSize should be expressed as a number of pixels");
return defaultStyles.fontSize;
}
return defaultStyles.fontSize;
};
const getSingleValue = function (prop, index) {
if (index === void 0) {
index = 0;
}
return Array.isArray(prop) ? prop[index] || prop[0] : prop;
};
const shouldUseMultilineBackgrounds = props => {
const {
backgroundStyle,
backgroundPadding
} = props;
return Array.isArray(backgroundStyle) && !(0, _isEmpty.default)(backgroundStyle) || Array.isArray(backgroundPadding) && !(0, _isEmpty.default)(backgroundPadding);
};
const getStyles = (style, props) => {
if (props.disableInlineStyles) {
const baseStyles = Helpers.evaluateStyle(style, props);
return {
// Font size is necessary to calculate the y position of the label
fontSize: getFontSize(baseStyles)
};
}
const getSingleStyle = s => {
const baseStyles = Helpers.evaluateStyle(s ? (0, _defaults.default)({}, s, defaultStyles) : defaultStyles, props);
return Object.assign({}, baseStyles, {
fontSize: getFontSize(baseStyles)
});
};
return Array.isArray(style) && !(0, _isEmpty.default)(style) ? style.map(s => getSingleStyle(s)) : getSingleStyle(style);
};
const getBackgroundStyles = (style, props) => {
if (!style) {
return undefined;
}
return Array.isArray(style) && !(0, _isEmpty.default)(style) ? style.map(s => Helpers.evaluateStyle(s, props)) : Helpers.evaluateStyle(style, props);
};
const getBackgroundPadding = props => {
if (props.backgroundPadding && Array.isArray(props.backgroundPadding)) {
return props.backgroundPadding.map(backgroundPadding => {
const padding = Helpers.evaluateProp(backgroundPadding, props);
return Helpers.getPadding(padding);
});
}
const padding = Helpers.evaluateProp(props.backgroundPadding, props);
return Helpers.getPadding(padding);
};
const getLineHeight = props => {
const lineHeight = Helpers.evaluateProp(props.lineHeight, props);
if (Array.isArray(lineHeight)) {
return (0, _isEmpty.default)(lineHeight) ? [1] : lineHeight;
}
return lineHeight;
};
const getContent = (text, props) => {
if (text === undefined || text === null) {
return undefined;
}
if (Array.isArray(text)) {
return text.map(line => Helpers.evaluateProp(line, props));
}
const child = Helpers.evaluateProp(text, props);
if (child === undefined || child === null) {
return undefined;
}
return Array.isArray(child) ? child : `${child}`.split("\n");
};
const getDy = (props, verticalAnchor, lineHeight) => {
const dy = props.dy ? Helpers.evaluateProp(props.dy, props) : 0;
const length = props.inline ? 1 : props.text.length;
const capHeight = Helpers.evaluateProp(props.capHeight, props);
const anchor = verticalAnchor ? Helpers.evaluateProp(verticalAnchor, props) : "middle";
const fontSizes = [...Array(length).keys()].map(i => getSingleValue(props.style, i).fontSize);
const lineHeights = [...Array(length).keys()].map(i => getSingleValue(lineHeight, i));
if (anchor === "start") {
return dy + (capHeight / 2 + lineHeights[0] / 2) * fontSizes[0];
} else if (props.inline) {
return anchor === "end" ? dy + (capHeight / 2 - lineHeights[0] / 2) * fontSizes[0] : dy + capHeight / 2 * fontSizes[0];
} else if (length === 1) {
return anchor === "end" ? dy + (capHeight / 2 + (0.5 - length) * lineHeights[0]) * fontSizes[0] : dy + (capHeight / 2 + (0.5 - length / 2) * lineHeights[0]) * fontSizes[0];
}
const allHeights = [...Array(length).keys()].reduce((memo, i) => {
return memo + (capHeight / 2 + (0.5 - length) * lineHeights[i]) * fontSizes[i] / length;
}, 0);
return anchor === "end" ? dy + allHeights : dy + allHeights / 2 + capHeight / 2 * lineHeights[length - 1] * fontSizes[length - 1];
};
const getTransform = (props, x, y) => {
const {
polar
} = props;
const style = getSingleValue(props.style);
const defaultAngle = polar ? LabelHelpers.getPolarAngle(props) : 0;
const baseAngle = style.angle === undefined ? Helpers.evaluateProp(props.angle, props) : style.angle;
const angle = baseAngle === undefined ? defaultAngle : baseAngle;
const transform = props.transform || style.transform;
const transformPart = transform && Helpers.evaluateProp(transform, props);
const rotatePart = angle && {
rotate: [angle, x, y]
};
return transformPart || angle ? Style.toTransformString(transformPart, rotatePart) : undefined;
};
const getXCoordinate = (calculatedProps, labelSizeWidth) => {
const {
direction,
textAnchor,
x,
dx
} = calculatedProps;
if (direction === "rtl") {
return x - labelSizeWidth;
}
switch (textAnchor) {
case "middle":
return Math.round(x - labelSizeWidth / 2);
case "end":
return Math.round(x - labelSizeWidth);
default:
// start
return x + (dx || 0);
}
};
const getYCoordinate = (calculatedProps, textHeight) => {
const {
verticalAnchor,
y,
originalDy = 0
} = calculatedProps;
const offset = y + originalDy;
switch (verticalAnchor) {
case "start":
return Math.floor(offset);
case "end":
return Math.ceil(offset - textHeight);
default:
// middle
return Math.floor(offset - textHeight / 2);
}
};
const getFullBackground = (calculatedProps, tspanValues) => {
const {
dx = 0,
transform,
backgroundComponent,
backgroundStyle,
inline,
backgroundPadding,
capHeight
} = calculatedProps;
const textSizes = tspanValues.map(tspan => {
return tspan.textSize;
});
const height = inline ? Math.max(...textSizes.map(size => size.height)) : textSizes.reduce((memo, size, i) => {
const capHeightAdjustment = i ? 0 : capHeight / 2;
return memo + size.height * (tspanValues[i].lineHeight - capHeightAdjustment);
}, 0);
const width = inline ? textSizes.reduce((memo, size, index) => {
const offset = index ? dx : 0;
return memo + size.width + offset;
}, 0) : Math.max(...textSizes.map(size => size.width));
const xCoordinate = getXCoordinate(calculatedProps, width);
const yCoordinate = getYCoordinate(calculatedProps, height);
const backgroundProps = {
key: "background",
height: height + backgroundPadding.top + backgroundPadding.bottom,
style: backgroundStyle,
transform,
width: width + backgroundPadding.left + backgroundPadding.right,
x: inline ? xCoordinate - backgroundPadding.left : xCoordinate + dx - backgroundPadding.left,
y: yCoordinate
};
return /*#__PURE__*/_react.default.cloneElement(backgroundComponent, (0, _defaults.default)({}, backgroundComponent.props, backgroundProps));
};
const getInlineXOffset = (calculatedProps, textElements, index) => {
const {
textAnchor
} = calculatedProps;
const widths = textElements.map(t => t.widthWithPadding);
const totalWidth = widths.reduce((memo, width) => memo + width, 0);
const centerOffset = -totalWidth / 2;
switch (textAnchor) {
case "start":
return widths.reduce((memo, width, i) => i < index ? memo + width : memo, 0);
case "end":
return widths.reduce((memo, width, i) => i > index ? memo - width : memo, 0);
default:
// middle
return widths.reduce((memo, width, i) => {
const offsetWidth = i < index ? width : 0;
return i === index ? memo + width / 2 : memo + offsetWidth;
}, centerOffset);
}
};
const getChildBackgrounds = (calculatedProps, tspanValues) => {
const {
dy,
dx,
transform,
backgroundStyle,
backgroundPadding,
backgroundComponent,
inline,
y
} = calculatedProps;
const textElements = tspanValues.map((current, i) => {
const previous = getSingleValue(tspanValues, i - 1);
const labelSize = current.textSize;
const totalLineHeight = current.fontSize * current.lineHeight;
const textHeight = Math.ceil(totalLineHeight);
const padding = getSingleValue(backgroundPadding, i);
const prevPadding = getSingleValue(backgroundPadding, i - 1);
const xOffset = inline ? dx || 0 : 0;
const childDy = i && !inline ? previous.fontSize * previous.lineHeight + prevPadding.top + prevPadding.bottom : dy - totalLineHeight * 0.5 - (current.fontSize - current.capHeight);
return {
textHeight,
labelSize,
heightWithPadding: textHeight + padding.top + padding.bottom,
widthWithPadding: labelSize.width + padding.left + padding.right + xOffset,
y,
fontSize: current.fontSize,
dy: childDy
};
});
return textElements.map((textElement, i) => {
const xCoordinate = getXCoordinate(calculatedProps, textElement.labelSize.width);
const yCoordinate = textElements.slice(0, i + 1).reduce((prev, curr) => {
return prev + curr.dy;
}, y);
const padding = getSingleValue(backgroundPadding, i);
const height = textElement.heightWithPadding;
const xCoord = inline ? getInlineXOffset(calculatedProps, textElements, i) + xCoordinate - padding.left : xCoordinate;
const yCoord = inline ? getYCoordinate(calculatedProps, height) - padding.top : yCoordinate;
const backgroundProps = {
key: `tspan-background-${i}`,
height,
style: getSingleValue(backgroundStyle, i),
width: textElement.widthWithPadding,
transform,
x: xCoord - padding.left,
y: yCoord
};
return /*#__PURE__*/_react.default.cloneElement(backgroundComponent, (0, _defaults.default)({}, backgroundComponent.props, backgroundProps));
});
};
const getBackgroundElement = (calculatedProps, tspanValues) => {
return shouldUseMultilineBackgrounds(calculatedProps) ? getChildBackgrounds(calculatedProps, tspanValues) : getFullBackground(calculatedProps, tspanValues);
};
const calculateSpanDy = (tspanValues, i, calculatedProps) => {
const current = getSingleValue(tspanValues, i);
const previous = getSingleValue(tspanValues, i - 1);
const previousHeight = previous.fontSize * previous.lineHeight;
const currentHeight = current.fontSize * current.lineHeight;
const previousCaps = previous.fontSize - previous.capHeight;
const currentCaps = current.fontSize - current.capHeight;
const textHeight = previousHeight - previous.fontSize / 2 + current.fontSize / 2 - previousHeight / 2 + currentHeight / 2 - currentCaps / 2 + previousCaps / 2;
return shouldUseMultilineBackgrounds(calculatedProps) ? textHeight + current.backgroundPadding.top + previous.backgroundPadding.bottom : textHeight;
};
const getTSpanDy = (tspanValues, calculatedProps, i) => {
const {
inline
} = calculatedProps;
const current = getSingleValue(tspanValues, i);
if (i && !inline) {
return calculateSpanDy(tspanValues, i, calculatedProps);
} else if (inline) {
return i === 0 ? current.backgroundPadding.top : undefined;
}
return current.backgroundPadding.top;
};
const evaluateProps = props => {
/* Potential evaluated props are
1) text
2) style
3) everything else
*/
const text = getContent(props.text, props);
const style = getStyles(props.style, Object.assign({}, props, {
text
}));
const backgroundStyle = getBackgroundStyles(props.backgroundStyle, Object.assign({}, props, {
text,
style
}));
const backgroundPadding = getBackgroundPadding(Object.assign({}, props, {
text,
style,
backgroundStyle
}));
const id = Helpers.evaluateProp(props.id, props);
return Object.assign({}, props, {
backgroundStyle,
backgroundPadding,
style,
text,
id
});
};
const getCalculatedProps = props => {
const ariaLabel = Helpers.evaluateProp(props.ariaLabel, props);
const style = getSingleValue(props.style);
const lineHeight = getLineHeight(props);
const direction = props.direction ? Helpers.evaluateProp(props.direction, props) : "inherit";
const textAnchor = props.textAnchor ? Helpers.evaluateProp(props.textAnchor, props) : style.textAnchor || "start";
const verticalAnchor = props.verticalAnchor ? Helpers.evaluateProp(props.verticalAnchor, props) : style.verticalAnchor || "middle";
const dx = props.dx ? Helpers.evaluateProp(props.dx, props) : 0;
const dy = getDy(props, verticalAnchor, lineHeight);
const x = props.x !== undefined ? props.x : getPosition(props, "x");
const y = props.y !== undefined ? props.y : getPosition(props, "y");
const transform = getTransform(props, x, y);
return Object.assign({}, props, {
ariaLabel,
lineHeight,
direction,
textAnchor,
verticalAnchor,
dx,
dy,
originalDy: Helpers.evaluateProp(props.dy, props),
transform,
x,
y
});
};
const renderLabel = (calculatedProps, tspanValues) => {
const {
ariaLabel,
inline,
className,
title,
events,
direction,
text,
textAnchor,
dx,
dy,
transform,
x,
y,
desc,
id,
tabIndex,
tspanComponent,
textComponent
} = calculatedProps;
const userProps = UserProps.getSafeUserProps(calculatedProps);
const textProps = {
"aria-label": ariaLabel,
key: "text",
...events,
direction,
dx,
x,
y: y + dy,
transform,
className,
title,
desc: Helpers.evaluateProp(desc, calculatedProps),
tabIndex: Helpers.evaluateProp(tabIndex, calculatedProps),
id,
...userProps
};
const tspans = text.map((line, i) => {
const currentStyle = tspanValues[i].style;
const tspanProps = {
key: `${id}-key-${i}`,
x: !inline ? x : undefined,
dx: inline ? dx + tspanValues[i].backgroundPadding.left : dx,
dy: getTSpanDy(tspanValues, calculatedProps, i),
textAnchor: currentStyle.textAnchor || textAnchor,
style: currentStyle,
children: line
};
return /*#__PURE__*/_react.default.cloneElement(tspanComponent, tspanProps);
});
return /*#__PURE__*/_react.default.cloneElement(textComponent, textProps, tspans);
};
const defaultProps = {
backgroundComponent: /*#__PURE__*/_react.default.createElement(_rect.Rect, null),
groupComponent: /*#__PURE__*/_react.default.createElement("g", null),
direction: "inherit",
textComponent: /*#__PURE__*/_react.default.createElement(_text.Text, null),
tspanComponent: /*#__PURE__*/_react.default.createElement(_tspan.TSpan, null),
capHeight: 0.71,
// Magic number from d3.
lineHeight: 1
};
const VictoryLabel = initialProps => {
const props = evaluateProps((0, _defaults.default)({}, initialProps, defaultProps));
if (props.text === null || props.text === undefined) {
return null;
}
const calculatedProps = getCalculatedProps(props);
const {
text,
style,
capHeight,
backgroundPadding,
lineHeight
} = calculatedProps;
const tspanValues = text.map((line, i) => {
const currentStyle = getSingleValue(style, i);
const capHeightPx = TextSize.convertLengthToPixels(`${capHeight}em`, currentStyle.fontSize);
const currentLineHeight = getSingleValue(lineHeight, i);
return {
style: currentStyle,
fontSize: currentStyle.fontSize || defaultStyles.fontSize,
capHeight: capHeightPx,
text: line,
// TODO: This looks like a bug:
textSize: TextSize.approximateTextSize(line, currentStyle),
lineHeight: currentLineHeight,
backgroundPadding: getSingleValue(backgroundPadding, i)
};
});
const label = renderLabel(calculatedProps, tspanValues);
if (props.backgroundStyle) {
const backgroundElement = getBackgroundElement(calculatedProps, tspanValues);
const children = [backgroundElement, label];
const backgroundWithLabel = /*#__PURE__*/_react.default.cloneElement(props.groupComponent, {}, children);
return props.renderInPortal ? /*#__PURE__*/_react.default.createElement(_victoryPortal.VictoryPortal, null, backgroundWithLabel) : backgroundWithLabel;
}
return props.renderInPortal ? /*#__PURE__*/_react.default.createElement(_victoryPortal.VictoryPortal, null, label) : label;
};
exports.VictoryLabel = VictoryLabel;
VictoryLabel.displayName = "VictoryLabel";
VictoryLabel.role = "label";
VictoryLabel.defaultStyles = defaultStyles;