victory-chart
Version:
Chart Component for Victory
224 lines (207 loc) • 8.03 kB
JavaScript
import React, { PropTypes } from "react";
import { partialRight } from "lodash";
import {
PropTypes as CustomPropTypes, Helpers, VictoryTransition, VictoryLabel, addEvents,
VictoryContainer, VictoryTheme, DefaultTransitions, Candle
} from "victory-core";
import CandlestickHelpers from "./helper-methods";
const fallbackProps = {
width: 450,
height: 300,
padding: 50,
candleColors: {
positive: "#ffffff",
negative: "#252525"
}
};
const defaultData = [
{x: new Date(2016, 6, 1), open: 5, close: 10, high: 15, low: 0},
{x: new Date(2016, 6, 2), open: 10, close: 15, high: 20, low: 5},
{x: new Date(2016, 6, 3), open: 15, close: 20, high: 25, low: 10},
{x: new Date(2016, 6, 4), open: 20, close: 25, high: 30, low: 15},
{x: new Date(2016, 6, 5), open: 25, close: 30, high: 35, low: 20},
{x: new Date(2016, 6, 6), open: 30, close: 35, high: 40, low: 25},
{x: new Date(2016, 6, 7), open: 35, close: 40, high: 45, low: 30},
{x: new Date(2016, 6, 8), open: 40, close: 45, high: 50, low: 35}
];
class VictoryCandlestick extends React.Component {
static displayName = "VictoryCandlestick";
static role = "candlestick";
static defaultTransitions = DefaultTransitions.discreteTransitions();
static propTypes = {
animate: PropTypes.object,
candleColors: PropTypes.shape({ positive: PropTypes.string, negative: PropTypes.string }),
categories: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.shape({
x: PropTypes.arrayOf(PropTypes.string), y: PropTypes.arrayOf(PropTypes.string)
})
]),
close: PropTypes.oneOfType([
PropTypes.func,
CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
PropTypes.string,
PropTypes.arrayOf(PropTypes.string)
]),
containerComponent: PropTypes.element,
data: PropTypes.array,
domainPadding: PropTypes.oneOfType([
PropTypes.shape({
x: PropTypes.oneOfType([ PropTypes.number, CustomPropTypes.domain ]),
y: PropTypes.oneOfType([ PropTypes.number, CustomPropTypes.domain ])
}),
PropTypes.number
]),
dataComponent: PropTypes.element,
domain: PropTypes.oneOfType([
CustomPropTypes.domain,
PropTypes.shape({ x: CustomPropTypes.domain, y: CustomPropTypes.domain })
]),
events: PropTypes.arrayOf(PropTypes.shape({
target: PropTypes.oneOf(["data", "labels", "parent"]),
eventKey: PropTypes.oneOfType([
PropTypes.array,
PropTypes.func,
CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
PropTypes.string
]),
eventHandlers: PropTypes.object
})),
eventKey: PropTypes.oneOfType([
PropTypes.func,
CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
PropTypes.string
]),
groupComponent: PropTypes.element,
height: CustomPropTypes.nonNegative,
high: PropTypes.oneOfType([
PropTypes.func,
CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
PropTypes.string,
PropTypes.arrayOf(PropTypes.string)
]),
labels: PropTypes.oneOfType([ PropTypes.func, PropTypes.array ]),
labelComponent: PropTypes.element,
low: PropTypes.oneOfType([
PropTypes.func,
CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
PropTypes.string,
PropTypes.arrayOf(PropTypes.string)
]),
name: PropTypes.string,
open: PropTypes.oneOfType([
PropTypes.func,
CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
PropTypes.string,
PropTypes.arrayOf(PropTypes.string)
]),
padding: PropTypes.oneOfType([
PropTypes.number,
PropTypes.shape({
top: PropTypes.number, bottom: PropTypes.number,
left: PropTypes.number, right: PropTypes.number
})
]),
samples: CustomPropTypes.nonNegative,
scale: PropTypes.oneOfType([
CustomPropTypes.scale,
PropTypes.shape({ x: CustomPropTypes.scale, y: CustomPropTypes.scale })
]),
sharedEvents: PropTypes.shape({
events: PropTypes.array,
getEventState: PropTypes.func
}),
size: PropTypes.oneOfType([
CustomPropTypes.nonNegative,
PropTypes.func
]),
standalone: PropTypes.bool,
style: PropTypes.shape({
parent: PropTypes.object, data: PropTypes.object, labels: PropTypes.object
}),
theme: PropTypes.object,
width: CustomPropTypes.nonNegative,
x: PropTypes.oneOfType([
PropTypes.func,
CustomPropTypes.allOfType([CustomPropTypes.integer, CustomPropTypes.nonNegative]),
PropTypes.string,
PropTypes.arrayOf(PropTypes.string)
])
};
static defaultProps = {
samples: 50,
scale: "linear",
data: defaultData,
standalone: true,
dataComponent: <Candle/>,
labelComponent: <VictoryLabel/>,
containerComponent: <VictoryContainer/>,
groupComponent: <g/>,
theme: VictoryTheme.grayscale
};
static getDomain = CandlestickHelpers.getDomain.bind(CandlestickHelpers);
static getData = CandlestickHelpers.getData.bind(CandlestickHelpers);
static getBaseProps = partialRight(
CandlestickHelpers.getBaseProps.bind(CandlestickHelpers), fallbackProps);
static expectedComponents = [
"dataComponent", "labelComponent", "groupComponent", "containerComponent"
];
renderData(props) {
const { dataComponent, labelComponent, groupComponent} = props;
const dataComponents = [];
const labelComponents = [];
for (let index = 0, len = this.dataKeys.length; index < len; index++) {
const dataProps = this.getComponentProps(dataComponent, "data", index);
dataComponents[index] = React.cloneElement(dataComponent, dataProps);
const labelProps = this.getComponentProps(labelComponent, "labels", index);
if (labelProps && labelProps.text !== undefined && labelProps.text !== null) {
labelComponents[index] = React.cloneElement(labelComponent, labelProps);
}
}
return labelComponents.length > 0 ?
React.cloneElement(groupComponent, {}, ...dataComponents, ...labelComponents) :
dataComponents;
}
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, "candlestick");
const { animate, standalone, style, theme } = props;
// If animating, return a `VictoryAnimation` element that will create
// a new `VictoryCandlestick` with nearly identical props, except (1) tweened
// and (2) `animate` set to null so we don't recurse forever.
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 = [
"data", "domain", "height", "padding", "samples", "size",
"style", "width", "x", "y"
];
return (
<VictoryTransition animate={animate} animationWhitelist={whitelist}>
{React.createElement(this.constructor, props)}
</VictoryTransition>
);
}
const styleObject = theme && theme.candlestick && theme.candlestick.style ?
theme.candlestick.style : {};
const baseStyle = Helpers.getStyles(style, styleObject, "auto", "100%");
const group = this.renderGroup(this.renderData(props), baseStyle.parent);
return standalone ? this.renderContainer(props, group) : group;
}
}
export default addEvents(VictoryCandlestick);