victory-axis
Version:
Axis Component for Victory
169 lines (166 loc) • 6.78 kB
JavaScript
import React from "react";
import isEmpty from "lodash/isEmpty";
import { VictoryLabel, VictoryContainer, VictoryTheme, LineSegment, TextSize, addEvents, Axis, UserProps } from "victory-core";
import { getBaseProps, getStyles } from "./helper-methods";
const fallbackProps = {
width: 450,
height: 300,
padding: 50
};
const options = {
components: [{
name: "axis",
index: 0
}, {
name: "axisLabel",
index: 0
}, {
name: "grid"
}, {
name: "parent",
index: "parent"
}, {
name: "ticks"
}, {
name: "tickLabels"
}]
};
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
class VictoryAxisBase extends React.Component {
static animationWhitelist = ["style", "domain", "range", "tickCount", "tickValues", "offsetX", "offsetY", "padding", "width", "height"];
static displayName = "VictoryAxis";
static role = "axis";
static defaultTransitions = {
onExit: {
duration: 500
},
onEnter: {
duration: 500
}
};
static defaultProps = {
axisComponent: /*#__PURE__*/React.createElement(LineSegment, null),
axisLabelComponent: /*#__PURE__*/React.createElement(VictoryLabel, null),
tickLabelComponent: /*#__PURE__*/React.createElement(VictoryLabel, null),
tickComponent: /*#__PURE__*/React.createElement(LineSegment, null),
gridComponent: /*#__PURE__*/React.createElement(LineSegment, null),
standalone: true,
theme: VictoryTheme.grayscale,
containerComponent: /*#__PURE__*/React.createElement(VictoryContainer, null),
groupComponent: /*#__PURE__*/React.createElement("g", {
role: "presentation"
}),
fixLabelOverlap: false
};
static getDomain = Axis.getDomain;
static getAxis = Axis.getAxis;
static getStyles(props) {
return getStyles(props);
}
static getBaseProps(props) {
return getBaseProps(props, fallbackProps);
}
static expectedComponents = ["axisComponent", "axisLabelComponent", "groupComponent", "containerComponent", "tickComponent", "tickLabelComponent", "gridComponent"];
renderLine(props) {
const {
axisComponent
} = props;
const axisProps = this.getComponentProps(axisComponent, "axis", 0);
return /*#__PURE__*/React.cloneElement(axisComponent, axisProps);
}
renderLabel(props) {
const {
axisLabelComponent,
label
} = props;
if (!label) {
return null;
}
const axisLabelProps = this.getComponentProps(axisLabelComponent, "axisLabel", 0);
return /*#__PURE__*/React.cloneElement(axisLabelComponent, axisLabelProps);
}
renderGridAndTicks(props) {
const {
tickComponent,
tickLabelComponent,
gridComponent,
name
} = props;
const shouldRender = componentProps => {
const {
style = {},
events = {}
} = componentProps;
const visible = style.stroke !== "transparent" && style.stroke !== "none" && style.strokeWidth !== 0;
return visible || !isEmpty(events);
};
return this.dataKeys.map((key, index) => {
const tickProps = this.getComponentProps(tickComponent, "ticks", index);
const BaseTickComponent = /*#__PURE__*/React.cloneElement(tickComponent, tickProps);
const TickComponent = shouldRender(BaseTickComponent.props) ? BaseTickComponent : undefined;
const gridProps = this.getComponentProps(gridComponent, "grid", index);
const BaseGridComponent = /*#__PURE__*/React.cloneElement(gridComponent, gridProps);
const GridComponent = shouldRender(BaseGridComponent.props) ? BaseGridComponent : undefined;
const tickLabelProps = this.getComponentProps(tickLabelComponent, "tickLabels", index);
const TickLabel = /*#__PURE__*/React.cloneElement(tickLabelComponent, tickLabelProps);
const children = [GridComponent, TickComponent, TickLabel].filter(Boolean);
return /*#__PURE__*/React.cloneElement(props.groupComponent, {
key: `${name}-tick-group-${key}`
}, children);
});
}
fixLabelOverlap(gridAndTicks, props) {
const isVertical = Axis.isVertical(props);
const size = isVertical ? props.height : props.width;
const isVictoryLabel = child => child.type && child.type.role === "label";
const labels = gridAndTicks.map(gridAndTick => gridAndTick.props.children).reduce((accumulator, childArr) => accumulator.concat(childArr), []).filter(isVictoryLabel).map(child => child.props);
const paddingToObject = padding => typeof padding === "object" ? Object.assign({}, {
top: 0,
right: 0,
bottom: 0,
left: 0
}, padding) : {
top: padding,
right: padding,
bottom: padding,
left: padding
};
const labelsSumSize = labels.reduce((sum, label) => {
const padding = paddingToObject(label.style.padding);
const labelSize = TextSize.approximateTextSize(label.text, {
angle: label.angle,
fontSize: label.style.fontSize,
letterSpacing: label.style.letterSpacing,
fontFamily: label.style.fontFamily
});
return sum + (isVertical ? labelSize.height + padding.top + padding.bottom : labelSize.width + padding.right + padding.left);
}, 0);
const availiableLabelCount = Math.floor(size * gridAndTicks.length / labelsSumSize);
const divider = Math.ceil(gridAndTicks.length / availiableLabelCount) || 1;
const getLabelCoord = gridAndTick => gridAndTick.props.children.filter(isVictoryLabel).reduce((prev, child) => (isVertical ? child.props.y : child.props.x) || 0, 0);
const sorted = gridAndTicks.sort((a, b) => isVertical ? getLabelCoord(b) - getLabelCoord(a) // ordinary axis has top-bottom orientation
: getLabelCoord(a) - getLabelCoord(b) // ordinary axis has left-right orientation
);
return sorted.filter((gridAndTick, index) => index % divider === 0);
}
// Overridden in native versions
shouldAnimate() {
return !!this.props.animate;
}
render() {
const {
animationWhitelist
} = VictoryAxis;
const props = Axis.modifyProps(this.props, fallbackProps);
const userProps = UserProps.getSafeUserProps(this.props);
if (this.shouldAnimate()) {
return this.animateComponent(props, animationWhitelist);
}
const gridAndTicks = this.renderGridAndTicks(props);
const modifiedGridAndTicks = props.fixLabelOverlap ? this.fixLabelOverlap(gridAndTicks, props) : gridAndTicks;
const children = [this.renderLine(props), this.renderLabel(props), ...modifiedGridAndTicks];
const container = /*#__PURE__*/React.cloneElement(props.containerComponent, userProps);
return props.standalone ? this.renderContainer(container, children) : /*#__PURE__*/React.cloneElement(props.groupComponent, userProps, children);
}
}
export const VictoryAxis = addEvents(VictoryAxisBase, options);