diff --git a/demo/victory-legend-demo.js b/demo/victory-legend-demo.js
index 02aeef2..09c284f 100644
--- a/demo/victory-legend-demo.js
+++ b/demo/victory-legend-demo.js
@@ -1,5 +1,5 @@
import React from "react";
-import { VictoryLegend } from "../src/index";
+import { VictoryLegend, VictoryLabel, Border } from "../src/index";
const containerStyle = {
display: "flex",
@@ -9,33 +9,45 @@ const containerStyle = {
justifyContent: "center"
};
-const legendStyle = { parent: { border: "1px solid #ccc", margin: "2%", maxHeight: 300 } };
+const legendStyle = {
+ labels: { fontSize: 14, fontFamily: "Palatino" },
+ border: { stroke: "black", strokeWidth: 2 },
+ title: { padding: 5, fill: "red" }
+};
+
+const symbolSize = 5;
+const symbolSpacer = 10;
const data = [{
name: "Series 1",
symbol: {
+ size: symbolSize,
type: "circle",
fill: "green"
}
}, {
- name: "Long Series Name",
+ name: "Long Series Name -- so long",
symbol: {
+ size: symbolSize,
type: "triangleUp",
fill: "blue"
}
}, {
name: "Series 3",
symbol: {
+ size: symbolSize,
type: "diamond",
fill: "pink"
}
}, {
name: "Series 4",
symbol: {
+ size: symbolSize,
type: "plus"
}
}, {
name: "Series 5",
symbol: {
+ size: symbolSize,
type: "star",
fill: "red"
},
@@ -43,8 +55,9 @@ const data = [{
fill: "purple"
}
}, {
- name: "Series 6",
+ name: "Series 6: also quite long",
symbol: {
+ size: symbolSize,
type: "circle",
fill: "orange"
},
@@ -56,14 +69,19 @@ const data = [{
const LegendDemo = () => (
-
}
+ centerTitle
+ title={["TITLE"]}
+ gutter={30}
+ symbolSpacer={symbolSpacer}
+ itemsPerRow={3}
data={data}
- itemsPerRow={4}
- orientation="horizontal"
style={legendStyle}
/>
diff --git a/src/index.js b/src/index.js
index b3c2518..cb4b064 100644
--- a/src/index.js
+++ b/src/index.js
@@ -12,6 +12,7 @@ export { default as Portal } from "./victory-portal/portal";
export { default as Arc } from "./victory-primitives/arc";
export { default as Area } from "./victory-primitives/area";
export { default as Bar } from "./victory-primitives/bar";
+export { default as Border } from "./victory-primitives/border";
export { default as Candle } from "./victory-primitives/candle";
export { default as ClipPath } from "./victory-primitives/clip-path";
export { default as Curve } from "./victory-primitives/curve";
diff --git a/src/victory-label/victory-label.js b/src/victory-label/victory-label.js
index e21d120..c835acb 100644
--- a/src/victory-label/victory-label.js
+++ b/src/victory-label/victory-label.js
@@ -187,7 +187,8 @@ export default class VictoryLabel extends React.Component {
}
getDy(props, style, content, lineHeight) { //eslint-disable-line max-params
- const fontSize = style[0].fontSize;
+ style = Array.isArray(style) ? style[0] : style;
+ const fontSize = style.fontSize;
const datum = props.datum || props.data;
const dy = props.dy ? Helpers.evaluateProp(props.dy, datum) : 0;
const length = content.length;
diff --git a/src/victory-legend/helper-methods.js b/src/victory-legend/helper-methods.js
index 2a90834..7b9347d 100644
--- a/src/victory-legend/helper-methods.js
+++ b/src/victory-legend/helper-methods.js
@@ -1,38 +1,8 @@
-import { defaults, assign, maxBy, sumBy } from "lodash";
+import { defaults, assign, groupBy, keys, sum, range } from "lodash";
import Helpers from "../victory-util/helpers";
import Style from "../victory-util/style";
import TextSize from "../victory-util/textsize";
-
-const calculateLegendHeight = (props, textSizes) => {
- const { gutter, itemsPerRow, padding, isHorizontal } = props;
- const itemCount = textSizes.length;
- const rowCount = itemsPerRow ? Math.ceil(itemCount / itemsPerRow) : 1;
- const contentHeight = isHorizontal
- ? maxBy(textSizes, "height").height * rowCount + gutter * (rowCount - 1)
- : (sumBy(textSizes, "height") + gutter * (itemCount - 1)) / rowCount;
-
- return padding.top + contentHeight + padding.bottom;
-};
-
-const calculateLegendWidth = (props, itemCount, maxTextWidth) => {
- const { gutter, itemsPerRow, symbolSpacer, padding, isHorizontal } = props;
- const rowCount = itemsPerRow ? Math.ceil(itemCount / itemsPerRow) : 1;
- const rowItemCount = itemsPerRow || itemCount;
- let contentWidth;
-
- if (isHorizontal) {
- const gutterWidth = gutter * rowItemCount;
- const symbolWidth = symbolSpacer * 3 * rowItemCount; // eslint-disable-line no-magic-numbers
- const textWidth = maxTextWidth * rowItemCount;
- contentWidth = symbolWidth + textWidth + gutterWidth;
- } else {
- contentWidth = (maxTextWidth + symbolSpacer * 2 + gutter) * rowCount;
- }
-
- return padding.left + contentWidth + padding.right;
-};
-
const getColorScale = (props) => {
const { colorScale } = props;
return typeof colorScale === "string" ? Style.getColorScale(colorScale) : colorScale || [];
@@ -46,70 +16,220 @@ const getLabelStyles = (props) => {
});
};
-const getTextSizes = (props, labelStyles) => {
- return props.data.map((datum, i) => {
- return TextSize.approximateTextSize(datum.name, labelStyles[i]);
- });
+const getStyles = (props, styleObject) => {
+ const style = props.style || {};
+ styleObject = styleObject || {};
+ const parentStyleProps = { height: "100%", width: "100%" };
+ return {
+ parent: defaults(style.parent, styleObject.parent, parentStyleProps),
+ data: defaults({}, style.data, styleObject.data),
+ labels: defaults({}, style.labels, styleObject.labels),
+ border: defaults({}, style.border, styleObject.border),
+ title: defaults({}, style.title, styleObject.title)
+ };
};
const getCalculatedValues = (props) => {
const { orientation, theme } = props;
const defaultStyles = theme && theme.legend && theme.legend.style ? theme.legend.style : {};
- const style = Helpers.getStyles(props.style, defaultStyles);
+ const style = getStyles(props, defaultStyles);
const colorScale = getColorScale(props);
const isHorizontal = orientation === "horizontal";
- const padding = Helpers.getPadding(props);
+ const borderPadding = Helpers.formatPadding(props.borderPadding);
+ return assign({}, props, { style, isHorizontal, colorScale, borderPadding });
+};
- return assign({}, props, { style, isHorizontal, colorScale, padding });
+const getColumn = (props, index) => {
+ const { itemsPerRow, isHorizontal } = props;
+ if (!itemsPerRow) {
+ return isHorizontal ? index : 0;
+ }
+ return isHorizontal ? index % itemsPerRow : Math.floor(index / itemsPerRow);
};
+const getRow = (props, index) => {
+ const { itemsPerRow, isHorizontal } = props;
+ if (!itemsPerRow) {
+ return isHorizontal ? 0 : index;
+ }
+ return isHorizontal ? Math.floor(index / itemsPerRow) : index % itemsPerRow;
+};
const getSymbolSize = (datum, fontSize) => {
// eslint-disable-next-line no-magic-numbers
return datum.symbol && datum.symbol.size ? datum.symbol.size : fontSize / 2.5;
};
+const groupData = (props) => {
+ const { data } = props;
+ const style = props.style && props.style.data || {};
+ const labelStyles = getLabelStyles(props);
+ return data.map((datum, index) => {
+ const { fontSize } = labelStyles[index];
+ const size = style.size || getSymbolSize(datum, fontSize);
+ const symbolSpacer = props.symbolSpacer || Math.max(size, fontSize);
+ return {
+ ...datum, size, symbolSpacer, fontSize,
+ textSize: TextSize.approximateTextSize(datum.name, labelStyles[index]),
+ column: getColumn(props, index),
+ row: getRow(props, index)
+ };
+ });
+};
+
+const getColumnWidths = (props, data) => {
+ const dataByColumn = groupBy(data, "column");
+ const columns = keys(dataByColumn);
+ return columns.reduce((memo, curr, index) => {
+ const gutter = index === columns.length - 1 ? 0 : props.gutter;
+ const lengths = dataByColumn[curr].map((d) => {
+ const symbolWidth = index && index === columns.length - 1 ? 0 : d.size + d.symbolSpacer;
+ return d.textSize.width + gutter + symbolWidth;
+ });
+ memo[index] = Math.max(...lengths);
+ return memo;
+ }, []);
+};
+
+const getRowHeights = (props, data) => {
+ const dataByRow = groupBy(data, "row");
+ return keys(dataByRow).reduce((memo, curr, index) => {
+ const rows = dataByRow[curr];
+ const lengths = rows.map((d) => d.textSize.height + d.symbolSpacer);
+ memo[index] = Math.max(...lengths);
+ return memo;
+ }, []);
+};
+
+const getTitleDimensions = (props) => {
+ const style = props.style && props.style.title || {};
+ const textSize = TextSize.approximateTextSize(props.title, style);
+ const padding = style.padding || 0;
+ return { height: textSize.height + 2 * padding || 0, width: textSize.width + 2 * padding || 0 };
+};
+
+const getOffset = (datum, rowHeights, columnWidths) => {
+ const { column, row } = datum;
+ return {
+ x: range(column).reduce((memo, curr) => {
+ memo += columnWidths[curr];
+ return memo;
+ }, 0),
+ y: range(row).reduce((memo, curr) => {
+ memo += rowHeights[curr];
+ return memo;
+ }, 0)
+ };
+};
+
+const getAnchors = (titleOrientation, centerTitle) => {
+ const standardAnchors = {
+ textAnchor: titleOrientation === "right" ? "end" : "start",
+ verticalAnchor: titleOrientation === "bottom" ? "end" : "start"
+ };
+ if (centerTitle) {
+ const horizontal = titleOrientation === "top" || titleOrientation === "bottom";
+ return {
+ textAnchor: horizontal ? "middle" : standardAnchors.textAnchor,
+ verticalAnchor: horizontal ? standardAnchors.verticalAnchor : "middle"
+ };
+ } else {
+ return standardAnchors;
+ }
+};
+
+const getTitleStyle = (props) => {
+ const { titleOrientation, centerTitle, titleComponent } = props;
+ const baseStyle = props.style && props.style.title || {};
+ const componentStyle = titleComponent.props && titleComponent.props.style || {};
+ const anchors = getAnchors(titleOrientation, centerTitle);
+ return Array.isArray(componentStyle) ?
+ componentStyle.map((obj) => defaults({}, obj, baseStyle, anchors)) :
+ defaults({}, componentStyle, baseStyle, anchors);
+};
+
+// eslint-disable-next-line complexity
+const getTitleProps = (props, borderProps) => {
+ const { title, titleOrientation, centerTitle, borderPadding } = props;
+ const { height, width } = borderProps;
+ const style = getTitleStyle(props);
+ const padding = Array.isArray(style) ? style[0].padding : style.padding;
+ const horizontal = titleOrientation === "top" || titleOrientation === "bottom";
+ const xOrientation = titleOrientation === "bottom" ? "bottom" : "top";
+ const yOrientation = titleOrientation === "right" ? "right" : "left";
+ const standardPadding = {
+ x: centerTitle ? width / 2 : borderPadding[xOrientation] + (padding || 0),
+ y: centerTitle ? height / 2 : borderPadding[yOrientation] + (padding || 0)
+ };
+ const getPadding = () => {
+ return borderPadding[titleOrientation] + (padding || 0);
+ };
+ const xOffset = horizontal ? standardPadding.x : getPadding();
+ const yOffset = horizontal ? getPadding() : standardPadding.y;
+
+ return {
+ x: titleOrientation === "right" ? props.x + width - xOffset : props.x + xOffset,
+ y: titleOrientation === "bottom" ? props.y + height - yOffset : props.y + yOffset,
+ style,
+ text: title
+ };
+};
+
+const getBorderProps = (props, contentHeight, contentWidth) => {
+ const { x, y, borderPadding, style } = props;
+ const height = contentHeight + borderPadding.top + borderPadding.bottom;
+ const width = contentWidth + borderPadding.left + borderPadding.right;
+ return { x, y, height, width, style: style.border };
+};
+
export default (props, fallbackProps) => {
const modifiedProps = Helpers.modifyProps(props, fallbackProps, "legend");
props = assign({}, modifiedProps, getCalculatedValues(modifiedProps));
const {
data, standalone, theme, padding, style, colorScale,
- itemsPerRow, gutter, isHorizontal, symbolSpacer
+ borderPadding, title, titleOrientation, x = 0, y = 0
} = props;
+ const groupedData = groupData(props);
+ const columnWidths = getColumnWidths(props, groupedData);
+ const rowHeights = getRowHeights(props, groupedData);
const labelStyles = getLabelStyles(props);
- const textSizes = getTextSizes(props, labelStyles);
- const maxTextWidth = Math.max(...textSizes.map((text) => text.width));
- const height = props.height || calculateLegendHeight(props, textSizes);
- const width = props.width || calculateLegendWidth(props, textSizes.width, maxTextWidth);
- const initialChildProps = { parent: {
- width, height, data, standalone, theme, padding, style: style.parent
- } };
-
- return data.reduce((childProps, datum, i) => {
- const { fontSize } = labelStyles[i];
- const symbolShift = fontSize / 2;
- const symbolWidth = fontSize + symbolSpacer;
- const rowHeight = fontSize + gutter;
- const itemIndex = itemsPerRow ? i % itemsPerRow : i;
- const rowIndex = itemsPerRow ? Math.floor(i / itemsPerRow) : 0;
- const rowSpacer = itemsPerRow ? rowHeight * rowIndex : 0;
- const eventKey = datum.eventKey || i;
- const y = isHorizontal ?
- padding.top + symbolShift + rowSpacer : padding.top + symbolShift + rowHeight * itemIndex;
+ const titleDimensions = title ? getTitleDimensions(props) : { height: 0, width: 0 };
+ const titleOffset = {
+ x: titleOrientation === "left" ? titleDimensions.width : 0,
+ y: titleOrientation === "top" ? titleDimensions.height : 0
+ };
+
+ const contentHeight = titleOrientation === "left" || titleOrientation === "right" ?
+ Math.max(sum(rowHeights), titleDimensions.height) : sum(rowHeights) + titleDimensions.height;
+ const contentWidth = titleOrientation === "left" || titleOrientation === "right" ?
+ sum(columnWidths) + titleDimensions.width : Math.max(sum(columnWidths), titleDimensions.width);
+
+ const initialProps = {
+ parent: {
+ data, standalone, theme, padding,
+ height: props.height,
+ width: props.width,
+ style: style.parent
+ }
+ };
+ const borderProps = getBorderProps(props, contentHeight, contentWidth);
+ const titleProps = getTitleProps(props, borderProps);
+ return groupedData.reduce((childProps, datum, i) => {
const color = colorScale[i % colorScale.length];
const dataStyle = defaults({}, datum.symbol, style.data, { fill: color });
-
+ const eventKey = datum.eventKey || i;
+ const offset = getOffset(datum, rowHeights, columnWidths);
+ const originY = y + borderPadding.top + datum.symbolSpacer;
+ const originX = x + borderPadding.left + datum.symbolSpacer;
const dataProps = {
index: i,
data, datum,
key: `legend-symbol-${i}`,
- symbol: dataStyle.type || "circle",
- size: getSymbolSize(datum, fontSize),
+ symbol: dataStyle.type || dataStyle.symbol || "circle",
+ size: datum.size,
style: dataStyle,
- y,
- x: isHorizontal ?
- padding.left + symbolShift + (fontSize + symbolSpacer + maxTextWidth + gutter) * itemIndex :
- padding.left + symbolShift + (rowHeight + maxTextWidth) * rowIndex
+ y: originY + offset.y + titleOffset.y,
+ x: originX + offset.x + titleOffset.x
};
const labelProps = {
@@ -117,15 +237,14 @@ export default (props, fallbackProps) => {
key: `legend-label-${i}`,
text: datum.name,
style: labelStyles[i],
- y,
- x: isHorizontal ?
- padding.left + symbolWidth * (itemIndex + 1) + (maxTextWidth + gutter) * itemIndex :
- padding.left + symbolWidth + (rowHeight + maxTextWidth) * rowIndex
+ y: originY + offset.y + titleOffset.y,
+ x: originX + offset.x + titleOffset.x + datum.symbolSpacer + (datum.size / 2)
};
-
- childProps[eventKey] = { data: dataProps, labels: labelProps };
+ childProps[eventKey] = eventKey === 0 ?
+ { data: dataProps, labels: labelProps, border: borderProps, title: titleProps } :
+ { data: dataProps, labels: labelProps };
return childProps;
- }, initialChildProps);
+ }, initialProps);
};
diff --git a/src/victory-legend/victory-legend.js b/src/victory-legend/victory-legend.js
index d07270b..c616b27 100644
--- a/src/victory-legend/victory-legend.js
+++ b/src/victory-legend/victory-legend.js
@@ -9,9 +9,12 @@ import VictoryLabel from "../victory-label/victory-label";
import VictoryContainer from "../victory-container/victory-container";
import VictoryTheme from "../victory-theme/victory-theme";
import Point from "../victory-primitives/point";
+import Border from "../victory-primitives/border";
const fallbackProps = {
orientation: "vertical",
+ width: 450,
+ height: 300,
x: 0,
y: 0
};
@@ -27,6 +30,17 @@ class VictoryLegend extends React.Component {
static role = "legend";
static propTypes = {
+ borderComponent: PropTypes.element,
+ borderPadding: PropTypes.oneOfType([
+ PropTypes.number,
+ PropTypes.shape({
+ top: PropTypes.number,
+ bottom: PropTypes.number,
+ left: PropTypes.number,
+ right: PropTypes.number
+ })
+ ]),
+ centerTitle: PropTypes.bool,
colorScale: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.oneOf([
@@ -58,10 +72,7 @@ class VictoryLegend extends React.Component {
})),
groupComponent: PropTypes.element,
gutter: PropTypes.number,
- height: PropTypes.oneOfType([
- CustomPropTypes.nonNegative,
- PropTypes.func
- ]),
+ height: CustomPropTypes.nonNegative,
itemsPerRow: PropTypes.number,
labelComponent: PropTypes.element,
orientation: PropTypes.oneOf(["horizontal", "vertical"]),
@@ -80,40 +91,72 @@ class VictoryLegend extends React.Component {
}),
standalone: PropTypes.bool,
style: PropTypes.shape({
+ border: PropTypes.object,
data: PropTypes.object,
labels: PropTypes.object,
- parent: PropTypes.object
+ parent: PropTypes.object,
+ title: PropTypes.object
}),
symbolSpacer: PropTypes.number,
theme: PropTypes.object,
- width: PropTypes.oneOfType([
- CustomPropTypes.nonNegative,
- PropTypes.func
- ]),
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
+ titleComponent: PropTypes.element,
+ titleOrientation: PropTypes.oneOf(["top", "bottom", "left", "right"]),
+ width: CustomPropTypes.nonNegative,
x: PropTypes.number,
y: PropTypes.number
};
static defaultProps = {
+ borderComponent: ,
data: defaultLegendData,
containerComponent: ,
dataComponent: ,
groupComponent: ,
labelComponent: ,
standalone: true,
- theme: VictoryTheme.grayscale
+ theme: VictoryTheme.grayscale,
+ titleComponent:
};
static getBaseProps = partialRight(getBaseProps, fallbackProps);
static expectedComponents = [
- "dataComponent", "labelComponent", "groupComponent", "containerComponent"
+ "borderComponent", "containerComponent", "dataComponent",
+ "groupComponent", "labelComponent", "titleComponent"
];
+ renderChildren(props) {
+ const { dataComponent, labelComponent, title } = props;
+ const dataComponents = this.dataKeys.map((_dataKey, index) => {
+ const dataProps = this.getComponentProps(dataComponent, "data", index);
+ return React.cloneElement(dataComponent, dataProps);
+ });
+
+ const labelComponents = this.dataKeys.map((_dataKey, index) => {
+ const labelProps = this.getComponentProps(labelComponent, "labels", index);
+ if (typeof labelProps.text !== "undefined" && labelProps.text !== null) {
+ return React.cloneElement(labelComponent, labelProps);
+ }
+ return undefined;
+ }).filter(Boolean);
+
+ const borderProps = this.getComponentProps(props.borderComponent, "border", 0);
+ const borderComponent = React.cloneElement(props.borderComponent, borderProps);
+ if (title) {
+ const titleProps = this.getComponentProps(props.title, "title", 0);
+ const titleComponent = React.cloneElement(props.titleComponent, titleProps);
+ return [borderComponent, ...dataComponents, titleComponent, ...labelComponents];
+ }
+ return [borderComponent, ...dataComponents, ...labelComponents];
+ }
+
render() {
const { role } = this.constructor;
const props = Helpers.modifyProps((this.props), fallbackProps, role);
- const children = this.renderData(props);
- return props.standalone ? this.renderContainer(props.containerComponent, children) : children;
+ const children = [this.renderChildren(props)];
+ return props.standalone ?
+ this.renderContainer(props.containerComponent, children) :
+ React.cloneElement(props.groupComponent, {}, children);
}
}
diff --git a/src/victory-primitives/border.js b/src/victory-primitives/border.js
new file mode 100644
index 0000000..7742e71
--- /dev/null
+++ b/src/victory-primitives/border.js
@@ -0,0 +1,61 @@
+import React from "react";
+import PropTypes from "prop-types";
+import Helpers from "../victory-util/helpers";
+import Collection from "../victory-util/collection";
+import { assign } from "lodash";
+import CommonProps from "./common-props";
+
+export default class Line extends React.Component {
+ static propTypes = {
+ ...CommonProps,
+ height: PropTypes.number,
+ width: PropTypes.number,
+ x: PropTypes.number,
+ y: PropTypes.number
+ };
+
+ componentWillMount() {
+ this.style = this.getStyle(this.props);
+ }
+
+ shouldComponentUpdate(nextProps) {
+ const { className, x, y } = this.props;
+ const style = this.getStyle(nextProps);
+ if (!Collection.allSetsEqual([
+ [className, nextProps.className],
+ [x, nextProps.x],
+ [y, nextProps.y],
+ [style, this.style]
+ ])) {
+ this.style = style;
+ return true;
+ }
+ return false;
+ }
+
+ getStyle(props) {
+ const { style, datum, active } = props;
+ return Helpers.evaluateStyle(assign({ fill: "none" }, style), datum, active);
+ }
+
+ // Overridden in victory-core-native
+ renderBorder(props, style, events) {
+ const { role, shapeRendering, className } = this.props;
+ return (
+
+ );
+ }
+
+ render() {
+ const { x, y, width, height, events } = this.props;
+ return this.renderBorder({ x, y, width, height }, this.style, events);
+ }
+}
diff --git a/src/victory-primitives/index.js b/src/victory-primitives/index.js
deleted file mode 100644
index cdb713d..0000000
--- a/src/victory-primitives/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import Area from "./area";
-import Arc from "./arc";
-import Bar from "./bar";
-import Candle from "./candle";
-import ClipPath from "./clip-path";
-import Curve from "./curve";
-import ErrorBar from "./error-bar";
-import Line from "./line";
-import Point from "./point";
-import Slice from "./slice";
-import Voronoi from "./voronoi";
-import Flyout from "./flyout";
-
-
-export {
- Arc, Area, Bar, Candle, ClipPath, Curve, ErrorBar, Line, Point, Slice, Voronoi, Flyout
-};
diff --git a/src/victory-theme/grayscale.js b/src/victory-theme/grayscale.js
index 496f4ee..b6079c5 100644
--- a/src/victory-theme/grayscale.js
+++ b/src/victory-theme/grayscale.js
@@ -187,12 +187,13 @@ export default {
colorScale: colors,
gutter: 10,
orientation: "vertical",
+ titleOrientation: "top",
style: {
data: {
type: "circle"
},
- labels: baseLabelStyles
- },
- symbolSpacer: 8
+ labels: baseLabelStyles,
+ title: assign({}, baseLabelStyles, { padding: 5 })
+ }
}
};
diff --git a/src/victory-theme/material.js b/src/victory-theme/material.js
index 53862d9..8c283d4 100644
--- a/src/victory-theme/material.js
+++ b/src/victory-theme/material.js
@@ -204,12 +204,13 @@ export default {
colorScale: colors,
gutter: 10,
orientation: "vertical",
+ titleOrientation: "top",
style: {
data: {
type: "circle"
},
- labels: baseLabelStyles
- },
- symbolSpacer: 8
+ labels: baseLabelStyles,
+ title: assign({}, baseLabelStyles, { padding: 5 })
+ }
}
};
diff --git a/src/victory-util/helpers.js b/src/victory-util/helpers.js
index 52b7a37..bbbfcc6 100644
--- a/src/victory-util/helpers.js
+++ b/src/victory-util/helpers.js
@@ -32,17 +32,21 @@ export default {
};
},
- getPadding(props) {
- const padding = typeof props.padding === "number" ? props.padding : 0;
- const paddingObj = typeof props.padding === "object" ? props.padding : {};
+ formatPadding(padding) {
+ const paddingVal = typeof padding === "number" ? padding : 0;
+ const paddingObj = typeof padding === "object" ? padding : {};
return {
- top: paddingObj.top || padding,
- bottom: paddingObj.bottom || padding,
- left: paddingObj.left || padding,
- right: paddingObj.right || padding
+ top: paddingObj.top || paddingVal,
+ bottom: paddingObj.bottom || paddingVal,
+ left: paddingObj.left || paddingVal,
+ right: paddingObj.right || paddingVal
};
},
+ getPadding(props) {
+ return this.formatPadding(props.padding);
+ },
+
getStyles(style, defaultStyles) {
const width = "100%";
const height = "100%";
diff --git a/src/victory-util/textsize.js b/src/victory-util/textsize.js
index 3cc5339..162e51f 100644
--- a/src/victory-util/textsize.js
+++ b/src/victory-util/textsize.js
@@ -84,7 +84,7 @@ const convertLengthToPixels = (length, fontSize) => {
if (absoluteMeasurementUnitsToPixels.hasOwnProperty(attribute)) {
result = value * absoluteMeasurementUnitsToPixels[attribute];
} else if (relativeMeasurementUnitsCoef.hasOwnProperty(attribute)) {
- result = (fontSize ? value * fontSize : value * coefficients.defaultFontSize)
+ result = (fontSize ? value * fontSize : value * defaultStyle.fontSize)
* relativeMeasurementUnitsCoef[attribute];
} else {
result = value;
@@ -116,7 +116,9 @@ const _approximateTextWidthInternal = (text, style) => {
const _approximateTextHeightInternal = (text, style) => {
return _splitToLines(text).reduce((total, line, index) => {
const lineStyle = _prepareParams(style, index);
- const height = lineStyle.fontSize * coefficients.lineCapitalCoef;
+ const containsCaps = line.match(/[(A-Z)(0-9)]/);
+ const height = containsCaps ?
+ lineStyle.fontSize * coefficients.lineCapitalCoef : lineStyle.fontSize;
const emptySpace = index === 0 ? 0 : lineStyle.fontSize * coefficients.lineSpaceHeightCoef;
return total + lineStyle.lineHeight * (height + emptySpace);
}, 0);
diff --git a/test/client/spec/victory-legend/victory-legend.spec.js b/test/client/spec/victory-legend/victory-legend.spec.js
index 11071c1..e53240b 100644
--- a/test/client/spec/victory-legend/victory-legend.spec.js
+++ b/test/client/spec/victory-legend/victory-legend.spec.js
@@ -33,40 +33,40 @@ describe("components/victory-legend", () => {
const wrappedLegend = shallow();
const output = wrappedLegend.find("Point");
- expect(output.at(0).prop("x")).to.equal(7);
- expect(output.at(1).prop("x")).to.equal(95.68016194331983);
- expect(output.at(0).prop("y")).to.equal(7);
- expect(output.at(1).prop("y")).to.equal(7);
+ expect(output.at(0).prop("x")).to.equal(14);
+ expect(output.at(1).prop("x")).to.equal(100.28016194331983);
+ expect(output.at(0).prop("y")).to.equal(14);
+ expect(output.at(1).prop("y")).to.equal(14);
});
it("has expected vertical symbol position", () => {
const wrappedLegend = shallow();
const output = wrappedLegend.find("Point");
- expect(output.at(0).prop("x")).to.equal(7);
- expect(output.at(1).prop("x")).to.equal(7);
- expect(output.at(0).prop("y")).to.equal(7);
- expect(output.at(1).prop("y")).to.equal(31);
+ expect(output.at(0).prop("x")).to.equal(14);
+ expect(output.at(1).prop("x")).to.equal(14);
+ expect(output.at(0).prop("y")).to.equal(14);
+ expect(output.at(1).prop("y")).to.equal(44.905);
});
it("has expected horizontal legend labels position", () => {
const wrappedLegend = render();
const output = wrappedLegend.find("text");
- expect(output.eq(0).prop("x")).to.equal("22");
- expect(output.eq(1).prop("x")).to.equal("110.68016194331983");
- expect(output.eq(0).prop("y")).to.equal("7");
- expect(output.eq(1).prop("y")).to.equal("7");
+ expect(output.eq(0).prop("x")).to.equal("30.8");
+ expect(output.eq(1).prop("x")).to.equal("117.08016194331982");
+ expect(output.eq(0).prop("y")).to.equal("14");
+ expect(output.eq(1).prop("y")).to.equal("14");
});
it("has expected vertical legend labels position", () => {
const wrappedLegend = render();
const output = wrappedLegend.find("text");
- expect(output.eq(0).prop("x")).to.equal("22");
- expect(output.eq(1).prop("x")).to.equal("22");
- expect(output.eq(0).prop("y")).to.equal("7");
- expect(output.eq(1).prop("y")).to.equal("31");
+ expect(output.eq(0).prop("x")).to.equal("30.8");
+ expect(output.eq(1).prop("x")).to.equal("30.8");
+ expect(output.eq(0).prop("y")).to.equal("14");
+ expect(output.eq(1).prop("y")).to.equal("44.905");
});
describe("symbols", () => {
diff --git a/test/client/spec/victory-util/textsize.spec.js b/test/client/spec/victory-util/textsize.spec.js
index 8a69c7a..8d427e3 100644
--- a/test/client/spec/victory-util/textsize.spec.js
+++ b/test/client/spec/victory-util/textsize.spec.js
@@ -1,6 +1,8 @@
/* eslint no-unused-expressions: 0 */
-
import { TextSize } from "src/index";
+
+const testString = "ABC";
+
describe("helpers/textsize", () => {
describe("convertLengthToPixels", () => {
it("translate pixels as number of pixels", () => {
@@ -16,106 +18,109 @@ describe("helpers/textsize", () => {
describe("approximateWidth", () => {
it("return zero width when no style", () => {
- expect(TextSize.approximateTextSize("abc").width).to.eql(0);
+ expect(TextSize.approximateTextSize(testString).width).to.eql(0);
});
it("return correct width with signed angle", () => {
expect(
- TextSize.approximateTextSize("abc", { angle: -45, fontSize: 14 }).width.toFixed(2)
+ TextSize.approximateTextSize(testString, { angle: -45, fontSize: 14 }).width.toFixed(2)
).to.be.eql("31.36");
});
it("return correct width with pixel fontsize", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: "14px" }).width.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: "14px" }).width.toFixed(2)
).to.be.eql("24.22");
});
it("return appropriate width with defined fontSize", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12 }).width.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: 12 }).width.toFixed(2)
).to.be.eql("20.76");
});
it("consider font", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 16 }).width.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: 16 }).width.toFixed(2)
).to.be.eql("27.68");
});
it("consider letterSpacing", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12, letterSpacing: "1px" }).width.toFixed(2)
+ TextSize.approximateTextSize(
+ testString,
+ { fontSize: 12, letterSpacing: "1px" }
+ ).width.toFixed(2)
).to.be.eql("23.26");
});
it("consider angle", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12, angle: 30 }).width.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: 12, angle: 30 }).width.toFixed(2)
).to.be.eql("26.60");
});
it("not consider lineHeight without angle", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12, lineHeight: 2 }).width.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: 12, lineHeight: 2 }).width.toFixed(2)
).to.eql("20.76");
});
it("consider lineHeight with angle", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12, lineHeight: 2, angle: 30 }
+ TextSize.approximateTextSize(testString, { fontSize: 12, lineHeight: 2, angle: 30 }
).width.toFixed(2)
).to.eql("35.23");
});
it("return width of widest string in text", () => {
expect(
- TextSize.approximateTextSize("abc\ndefgh\nijk", { fontSize: 12 }).width.toFixed(2)
+ TextSize.approximateTextSize("ABC\nDEFGH\nIJK", { fontSize: 12 }).width.toFixed(2)
).to.eql("34.60");
});
});
describe("approximateHeight", () => {
it("return zero width when no style", () => {
- expect(TextSize.approximateTextSize("abc").height).to.eql(0);
+ expect(TextSize.approximateTextSize(testString).height).to.eql(0);
});
it("return correct height with signed angle", () => {
expect(
- TextSize.approximateTextSize("abc",
+ TextSize.approximateTextSize(testString,
{ angle: -45, fontSize: 14 }).height.toFixed(2)
).to.be.eql("26.34");
});
it("return correct height with pixel fontsize", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: "14px" }).height.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: "14px" }).height.toFixed(2)
).to.be.eql("16.90");
});
it("return appropriate height with expected precision", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12 }).height.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: 12 }).height.toFixed(2)
).to.be.eql("14.49");
});
it("consider font", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 16 }).height.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: 16 }).height.toFixed(2)
).to.be.eql("19.32");
});
it("consider angle", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12, angle: 30 }).height.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: 12, angle: 30 }).height.toFixed(2)
).to.be.eql("21.27");
});
it("not consider letterSpacing without angle", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12, letterSpacing: "1px" })
+ TextSize.approximateTextSize(testString, { fontSize: 12, letterSpacing: "1px" })
.height.toFixed(2)
).to.eql("14.49");
});
it("consider letterSpacing with angle", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12, angle: 30, letterSpacing: "1px" })
+ TextSize.approximateTextSize(testString, { fontSize: 12, angle: 30, letterSpacing: "1px" })
.height.toFixed(2)
).to.be.eql("22.32");
});
it("consider lineHeight", () => {
expect(
- TextSize.approximateTextSize("abc", { fontSize: 12, lineHeight: 2 }).height.toFixed(2)
+ TextSize.approximateTextSize(testString, { fontSize: 12, lineHeight: 2 }).height.toFixed(2)
).to.be.eql("28.98");
});
it("consider multiLines text", () => {
expect(
- TextSize.approximateTextSize(`abc\n${"dbcdefg"}\n123`, { fontSize: 12 }).height.toFixed(2)
+ TextSize.approximateTextSize(`ABC\n${"DBCDEFG"}\n123`, { fontSize: 12 }).height.toFixed(2)
).to.be.eql("48.51");
});
});