UNPKG

victory-chart

Version:
237 lines (217 loc) 8.95 kB
import React, { PropTypes } from "react"; import { assign, partialRight } from "lodash"; import { PropTypes as CustomPropTypes, Helpers, VictoryTransition, VictoryLabel, VictoryContainer, VictoryTheme, Line, TextSize, addEvents } from "victory-core"; import AxisHelpers from "./helper-methods"; import Axis from "../../helpers/axis"; const fallbackProps = { width: 450, height: 300, padding: 50 }; class VictoryAxis extends React.Component { static displayName = "VictoryAxis"; static role = "axis"; static defaultTransitions = { onExit: { duration: 500 }, onEnter: { duration: 500 } }; static propTypes = { animate: PropTypes.object, axisComponent: PropTypes.element, axisLabelComponent: PropTypes.element, containerComponent: PropTypes.element, crossAxis: PropTypes.bool, dependentAxis: PropTypes.bool, domain: PropTypes.oneOfType([ CustomPropTypes.domain, PropTypes.shape({ x: CustomPropTypes.domain, y: CustomPropTypes.domain }) ]), domainPadding: PropTypes.oneOfType([ PropTypes.shape({ x: PropTypes.oneOfType([ PropTypes.number, CustomPropTypes.domain ]), y: PropTypes.oneOfType([ PropTypes.number, CustomPropTypes.domain ]) }), PropTypes.number ]), events: PropTypes.arrayOf(PropTypes.shape({ target: PropTypes.oneOf(["axis", "axisLabel", "grid", "ticks", "tickLabels", "parent"]), eventKey: PropTypes.oneOfType([ PropTypes.array, CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]), PropTypes.string ]), eventHandlers: PropTypes.object })), fixLabelOverlap: PropTypes.bool, gridComponent: PropTypes.element, groupComponent: PropTypes.element, height: CustomPropTypes.nonNegative, label: PropTypes.any, name: PropTypes.string, offsetX: PropTypes.number, offsetY: PropTypes.number, orientation: PropTypes.oneOf(["top", "bottom", "left", "right"]), padding: PropTypes.oneOfType([ PropTypes.number, PropTypes.shape({ top: PropTypes.number, bottom: PropTypes.number, left: PropTypes.number, right: PropTypes.number }) ]), scale: CustomPropTypes.scale, sharedEvents: PropTypes.shape({ events: PropTypes.array, getEventState: PropTypes.func }), standalone: PropTypes.bool, style: PropTypes.shape({ parent: PropTypes.object, axis: PropTypes.object, axisLabel: PropTypes.object, grid: PropTypes.object, ticks: PropTypes.object, tickLabels: PropTypes.object }), theme: PropTypes.object, tickComponent: PropTypes.element, tickCount: CustomPropTypes.allOfType([ CustomPropTypes.integer, CustomPropTypes.greaterThanZero ]), tickFormat: PropTypes.oneOfType([ PropTypes.func, CustomPropTypes.homogeneousArray ]), tickLabelComponent: PropTypes.element, tickValues: CustomPropTypes.homogeneousArray, width: CustomPropTypes.nonNegative }; static defaultProps = { axisComponent: <Line type={"axis"}/>, axisLabelComponent: <VictoryLabel/>, tickLabelComponent: <VictoryLabel/>, tickComponent: <Line type={"tick"}/>, gridComponent: <Line type={"grid"}/>, scale: "linear", standalone: true, theme: VictoryTheme.grayscale, tickCount: 5, containerComponent: <VictoryContainer />, groupComponent: <g/>, fixLabelOverlap: false }; static getDomain = AxisHelpers.getDomain.bind(AxisHelpers); static getAxis = Axis.getAxis.bind(Axis); static getScale = AxisHelpers.getScale.bind(AxisHelpers); static getStyles = partialRight(AxisHelpers.getStyles.bind(AxisHelpers), fallbackProps.style); static getBaseProps = partialRight(AxisHelpers.getBaseProps.bind(AxisHelpers), fallbackProps); static expectedComponents = [ "axisComponent", "axisLabelComponent", "groupComponent", "containerComponent", "tickComponent", "tickLabelComponent", "gridComponent" ]; renderLine(props) { const { axisComponent } = props; const axisProps = this.getComponentProps(axisComponent, "axis", 0); return React.cloneElement(axisComponent, axisProps); } renderLabel(props) { const { axisLabelComponent } = props; const axisLabelProps = this.getComponentProps(axisLabelComponent, "axisLabel", 0); return React.cloneElement(axisLabelComponent, axisLabelProps); } renderGridAndTicks(props) { const { tickComponent, tickLabelComponent, gridComponent } = props; const gridAndTickComponents = []; for (let index = 0, len = this.dataKeys.length; index < len; index++) { const key = this.dataKeys[index]; const tickProps = this.getComponentProps(tickComponent, "ticks", index); const TickComponent = React.cloneElement(tickComponent, tickProps); const gridProps = this.getComponentProps(gridComponent, "grid", index); const GridComponent = React.cloneElement(gridComponent, gridProps); const tickLabelProps = this.getComponentProps(tickLabelComponent, "tickLabels", index); const TickLabel = React.cloneElement(tickLabelComponent, tickLabelProps); gridAndTickComponents[index] = React.cloneElement( props.groupComponent, {key: `tick-group-${key}`}, GridComponent, TickComponent, TickLabel ); } return gridAndTickComponents; } fixLabelOverlap(gridAndTicks, props) { const isVertical = Helpers.isVertical(props); const size = isVertical ? props.height : props.width; const isVictoryLabel = (child) => child.type.name === "VictoryLabel"; 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" ? 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) //ordinat axis has top-bottom orientation : getLabelCoord(a) - getLabelCoord(b) //ordinat axis has left-right orientation ); return sorted.filter((gridAndTick, index) => index % divider === 0); } renderContainer(props, group) { const { containerComponent } = props; const parentProps = this.getComponentProps(containerComponent, "parent", "parent"); return React.cloneElement(containerComponent, parentProps, group); } renderGroup(children, style) { return React.cloneElement( this.props.groupComponent, { role: "presentation", style}, ...children ); } shouldAnimate() { return !!this.props.animate; } render() { const props = Helpers.modifyProps(this.props, fallbackProps, "axis"); const { animate, standalone } = props; if (this.shouldAnimate()) { // Do less work by having `VictoryAnimation` tween only values that // make sense to tween. In the future, allow customization of animated // prop whitelist/blacklist? const whitelist = [ "style", "domain", "range", "tickCount", "tickValues", "offsetX", "offsetY", "padding", "width", "height" ]; return ( <VictoryTransition animate={animate} animationWhitelist={whitelist}> {React.createElement(this.constructor, props)} </VictoryTransition> ); } const styleObject = AxisHelpers.getStyleObject(props); const style = AxisHelpers.getStyles(props, styleObject); const gridAndTicks = this.renderGridAndTicks(props); const modifiedGridAndTicks = props.fixLabelOverlap ? this.fixLabelOverlap(gridAndTicks, props) : gridAndTicks; const children = [ ...modifiedGridAndTicks, this.renderLine(props), this.renderLabel(props) ]; const group = this.renderGroup(children, style.parent); return standalone ? this.renderContainer(props, group) : group; } } export default addEvents(VictoryAxis);