victory-chart
Version:
Chart Component for Victory
440 lines (405 loc) • 20.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _sortBy2 = require("lodash/sortBy");
var _sortBy3 = _interopRequireDefault(_sortBy2);
var _defaults2 = require("lodash/defaults");
var _defaults3 = _interopRequireDefault(_defaults2);
var _assign2 = require("lodash/assign");
var _assign3 = _interopRequireDefault(_assign2);
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
var _lineSegment = require("./line-segment");
var _lineSegment2 = _interopRequireDefault(_lineSegment);
var _scale = require("../../helpers/scale");
var _scale2 = _interopRequireDefault(_scale);
var _domain = require("../../helpers/domain");
var _domain2 = _interopRequireDefault(_domain);
var _data = require("../../helpers/data");
var _data2 = _interopRequireDefault(_data);
var _victoryCore = require("victory-core");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var defaultStyles = {
data: {
strokeWidth: 2,
fill: "none",
stroke: "#756f6a",
opacity: 1
},
labels: {
padding: 5,
fontFamily: "Helvetica",
fontSize: 10,
strokeWidth: 0,
stroke: "transparent",
textAnchor: "start"
}
};
var VictoryLine = function (_React$Component) {
_inherits(VictoryLine, _React$Component);
function VictoryLine() {
_classCallCheck(this, VictoryLine);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(VictoryLine).call(this));
_this.state = {};
_this.getEvents = _victoryCore.Helpers.getEvents.bind(_this);
_this.getEventState = _victoryCore.Helpers.getEventState.bind(_this);
return _this;
}
_createClass(VictoryLine, [{
key: "getDataSegments",
value: function getDataSegments(dataset) {
var orderedData = (0, _sortBy3.default)(dataset, "x");
var segments = [];
var segmentStartIndex = 0;
orderedData.forEach(function (datum, index) {
if (datum.y === null || typeof datum.y === "undefined") {
segments.push(orderedData.slice(segmentStartIndex, index));
segmentStartIndex = index + 1;
}
});
segments.push(orderedData.slice(segmentStartIndex, orderedData.length));
return segments.filter(function (segment) {
return Array.isArray(segment) && segment.length > 0;
});
}
}, {
key: "getLabelStyle",
value: function getLabelStyle(labelStyle, dataStyle) {
// match labels styles to data style by default (fill, opacity, others?)
var opacity = dataStyle.opacity;
// match label color to data color if it is not given.
// use fill instead of stroke for text
var fill = dataStyle.stroke;
var padding = labelStyle.padding || 0;
return (0, _defaults3.default)({}, labelStyle, { opacity: opacity, fill: fill, padding: padding });
}
}, {
key: "renderLine",
value: function renderLine(props, calculatedProps) {
var _this2 = this;
var dataSegments = calculatedProps.dataSegments;
var scale = calculatedProps.scale;
var style = calculatedProps.style;
var data = props.data;
var interpolation = props.interpolation;
var dataComponent = props.dataComponent;
var events = props.events;
var label = props.label;
var labelComponent = props.labelComponent;
var dataEvents = this.getEvents(events.data, "data");
return dataSegments.map(function (segment, index) {
var dataProps = (0, _defaults3.default)({}, _this2.getEventState(index, "data"), dataComponent.props, {
key: "line-segment-" + index,
data: segment,
style: _victoryCore.Helpers.evaluateStyle(style.data, segment),
interpolation: _victoryCore.Helpers.evaluateProp(interpolation, segment),
scale: scale
});
var segmentComponent = _react2.default.cloneElement(dataComponent, (0, _assign3.default)({
events: _victoryCore.Helpers.getPartialEvents(dataEvents, index, dataProps)
}, dataProps));
var text = _victoryCore.Helpers.evaluateProp(label, data);
if (index === dataSegments.length - 1 && text !== null && text !== undefined) {
var lastPoint = Array.isArray(segment) ? segment[segment.length - 1] : segment;
var labelStyle = _this2.getLabelStyle(style.labels, dataProps.style);
var labelEvents = _this2.getEvents(events.labels, "labels");
var labelProps = (0, _defaults3.default)({}, _this2.getEventState(index, "labels"), labelComponent.props, {
x: scale.x.call(_this2, lastPoint.x) + labelStyle.padding,
y: scale.y.call(_this2, lastPoint.y),
style: labelStyle,
data: data,
text: text,
scale: scale,
textAnchor: labelStyle.textAnchor || "start",
verticalAnchor: labelStyle.verticalAnchor || "middle",
angle: labelStyle.angle
});
var labelSegmentComponent = _react2.default.cloneElement(labelComponent, (0, _assign3.default)({
events: _victoryCore.Helpers.getPartialEvents(labelEvents, 0, labelProps)
}, labelProps));
return _react2.default.createElement(
"g",
{ key: "line-label-" + index },
segmentComponent,
labelSegmentComponent
);
}
return segmentComponent;
});
}
}, {
key: "renderData",
value: function renderData(props, style) {
var dataset = _data2.default.getData(props);
var dataSegments = this.getDataSegments(dataset);
var range = {
x: _victoryCore.Helpers.getRange(props, "x"),
y: _victoryCore.Helpers.getRange(props, "y")
};
var domain = {
x: _domain2.default.getDomain(props, "x"),
y: _domain2.default.getDomain(props, "y")
};
var scale = {
x: _scale2.default.getBaseScale(props, "x").domain(domain.x).range(range.x),
y: _scale2.default.getBaseScale(props, "y").domain(domain.y).range(range.y)
};
var calculatedProps = { dataset: dataset, dataSegments: dataSegments, scale: scale, style: style };
return _react2.default.createElement(
"g",
{ style: style.parent },
this.renderLine(props, calculatedProps)
);
}
}, {
key: "render",
value: function render() {
// If animating, return a `VictoryAnimation` element that will create
// a new `VictoryLine` with nearly identical props, except (1) tweened
// and (2) `animate` set to null so we don't recurse forever.
if (this.props.animate) {
// Do less work by having `VictoryAnimation` tween only values that
// make sense to tween. In the future, allow customization of animated
// prop whitelist/blacklist?
// TODO: extract into helper
var whitelist = ["data", "domain", "height", "padding", "samples", "style", "width", "x", "y"];
return _react2.default.createElement(
_victoryCore.VictoryTransition,
{ animate: this.props.animate, animationWhitelist: whitelist },
_react2.default.createElement(VictoryLine, this.props)
);
}
var style = _victoryCore.Helpers.getStyles(this.props.style, defaultStyles, "auto", "100%");
var group = _react2.default.createElement(
"g",
{ style: style.parent },
this.renderData(this.props, style)
);
return this.props.standalone ? _react2.default.createElement(
"svg",
_extends({
style: style.parent,
viewBox: "0 0 " + this.props.width + " " + this.props.height
}, this.props.events.parent),
group
) : group;
}
}]);
return VictoryLine;
}(_react2.default.Component);
VictoryLine.role = "line";
VictoryLine.defaultTransitions = {
onExit: {
duration: 500,
before: function before() {
return { y: null };
}
},
onEnter: {
duration: 500,
before: function before() {
return { y: null };
},
after: function after(datum) {
return { y: datum.y };
}
}
};
VictoryLine.propTypes = {
/**
* The animate prop specifies props for VictoryAnimation to use. The animate prop should
* also be used to specify enter and exit transition configurations with the `onExit`
* and `onEnter` namespaces respectively.
* @examples {duration: 500, onEnd: () => {}, onEnter: {duration: 500, before: () => ({y: 0})})}
*/
animate: _react.PropTypes.object,
/**
* The categories prop specifies how categorical data for a chart should be ordered.
* This prop should be given as an array of string values, or an object with
* these arrays of values specified for x and y. If this prop is not set,
* categorical data will be plotted in the order it was given in the data array
* @examples ["dogs", "cats", "mice"]
*/
categories: _react.PropTypes.oneOfType([_react.PropTypes.arrayOf(_react.PropTypes.string), _react.PropTypes.shape({
x: _react.PropTypes.arrayOf(_react.PropTypes.string),
y: _react.PropTypes.arrayOf(_react.PropTypes.string)
})]),
/**
* The data prop specifies the data to be plotted.
* Data should be in the form of an array of data points.
* Each data point may be any format you wish (depending on the `x` and `y` accessor props),
* but by default, an object with x and y properties is expected.
* @examples [{x: 1, y: 2}, {x: 2, y: 3}], [[1, 2], [2, 3]],
* [[{x: "a", y: 1}, {x: "b", y: 2}], [{x: "a", y: 2}, {x: "b", y: 3}]]
*/
data: _react.PropTypes.array,
/**
* The dataComponent prop takes an entire component which will be used to create line segments
* for each continuous set of data. (i.e. null data will result in multiple line segments)
* The new element created from the passed dataComponent will be provided with the following
* properties calculated by VictoryLine: data, index, scale, interpolation, and events.
* Any of these props may be overridden by passing in props to the supplied component,
* or modified or ignored within the custom component itself. If a dataComponent is not
* provided, VictoryLine will use its default LineSegment component.
*/
dataComponent: _react.PropTypes.element,
/**
* The domain prop describes the range of values your chart will include. This prop can be
* given as a array of the minimum and maximum expected values for your chart,
* or as an object that specifies separate arrays for x and y.
* If this prop is not provided, a domain will be calculated from data, or other
* available information.
* @examples [-1, 1], {x: [0, 100], y: [0, 1]}
*/
domain: _react.PropTypes.oneOfType([_victoryCore.PropTypes.domain, _react.PropTypes.shape({
x: _victoryCore.PropTypes.domain,
y: _victoryCore.PropTypes.domain
})]),
/**
* The events prop attaches arbitrary event handlers to data and label elements
* Event handlers are called with their corresponding events, corresponding component props,
* and their index in the data array, and event name. The return value of event handlers
* will be stored by index and namespace on the state object of VictoryLine
* i.e. `this.state[index].data = {style: {fill: "red"}...}`, and will be
* applied by index to the appropriate child component. Event props on the
* parent namespace are just spread directly on to the top level svg of VictoryLine
* if one exists. If VictoryLine is set up to render g elements i.e. when it is
* rendered within chart, or when `standalone={false}` parent events will not be applied.
*
* @examples {data: {
* onClick: () => return {data: {style: {fill: "green"}}, labels: {style: {fill: "black"}}}
*}}
*/
events: _react.PropTypes.shape({
data: _react.PropTypes.object,
labels: _react.PropTypes.object,
parent: _react.PropTypes.object
}),
/**
* The height props specifies the height the svg viewBox of the chart container.
* This value should be given as a number of pixels
*/
height: _victoryCore.PropTypes.nonNegative,
/**
* The interpolation prop determines how data points should be connected
* when plotting a line
*/
interpolation: _react.PropTypes.oneOf(["basis", "basisClosed", "basisOpen", "bundle", "cardinal", "cardinalClosed", "cardinalOpen", "catmullRom", "catmullRomClosed", "catmullRomOpen", "linear", "linearClosed", "monotoneX", "monotoneY", "natural", "radial", "step", "stepAfter", "stepBefore"]),
/**
* The label prop defines the label that will appear at the end of the line.
* This prop should be given a string or as a function of data. If individual
* labels are required for each data point, they should be created by composing
* VictoryLine with VictoryScatter
* @examples: "Series 1", (data) => `${data.length} points`
*/
label: _react.PropTypes.oneOfType([_react.PropTypes.func, _react.PropTypes.string]),
/**
* The labelComponent prop takes in an entire label component which will be used
* to create a label for the line. The new element created from the passed labelComponent
* will be supplied with the following properties: x, y, index, data, verticalAnchor,
* textAnchor, angle, style, text, and events. any of these props may be overridden
* by passing in props to the supplied component, or modified or ignored within
* the custom component itself. If labelComponent is omitted, a new VictoryLabel
* will be created with props described above. This labelComponent prop should be used to
* provide a series label for VictoryLine. If individual labels are required for each
* data point, they should be created by composing VictoryLine with VictoryScatter
*/
labelComponent: _react.PropTypes.any,
/**
* The padding props specifies the amount of padding in number of pixels between
* the edge of the chart and any rendered child components. This prop can be given
* as a number or as an object with padding specified for top, bottom, left
* and right.
*/
padding: _react.PropTypes.oneOfType([_react.PropTypes.number, _react.PropTypes.shape({
top: _react.PropTypes.number,
bottom: _react.PropTypes.number,
left: _react.PropTypes.number,
right: _react.PropTypes.number
})]),
/**
* The samples prop specifies how many individual points to plot when plotting
* y as a function of x. Samples is ignored if x props are provided instead.
*/
samples: _victoryCore.PropTypes.nonNegative,
/**
* The scale prop determines which scales your chart should use. This prop can be
* given as a string specifying a supported scale ("linear", "time", "log", "sqrt"),
* as a d3 scale function, or as an object with scales specified for x and y
* @exampes d3Scale.time(), {x: "linear", y: "log"}
*/
scale: _react.PropTypes.oneOfType([_victoryCore.PropTypes.scale, _react.PropTypes.shape({
x: _victoryCore.PropTypes.scale,
y: _victoryCore.PropTypes.scale
})]),
/**
* The standalone prop determines whether the component will render a standalone svg
* or a <g> tag that will be included in an external svg. Set standalone to false to
* compose VictoryLine with other components within an enclosing <svg> tag.
*/
standalone: _react.PropTypes.bool,
/**
* The style prop specifies styles for your VictoryLine. Any valid inline style properties
* will be applied. Height, width, and padding should be specified via the height,
* width, and padding props, as they are used to calculate the alignment of
* components within chart. in addition to normal style properties, angle and verticalAnchor
* may also be specified via the labels object, and they will be passed as props to
* VictoryLabel, or any custom labelComponent.
* @examples {data: {stroke: "red"}, labels: {fontSize: 12}}
*/
style: _react.PropTypes.shape({
parent: _react.PropTypes.object,
data: _react.PropTypes.object,
labels: _react.PropTypes.object
}),
/**
* The width props specifies the width of the svg viewBox of the chart container
* This value should be given as a number of pixels
*/
width: _victoryCore.PropTypes.nonNegative,
/**
* The x prop specifies how to access the X value of each data point.
* If given as a function, it will be run on each data point, and returned value will be used.
* If given as an integer, it will be used as an array index for array-type data points.
* If given as a string, it will be used as a property key for object-type data points.
* If given as an array of strings, or a string containing dots or brackets,
* it will be used as a nested object property path (for details see Lodash docs for _.get).
* If `null` or `undefined`, the data value will be used as is (identity function/pass-through).
* @examples 0, 'x', 'x.value.nested.1.thing', 'x[2].also.nested', null, d => Math.sin(d)
*/
x: _react.PropTypes.oneOfType([_react.PropTypes.func, _victoryCore.PropTypes.allOfType([_victoryCore.PropTypes.integer, _victoryCore.PropTypes.nonNegative]), _react.PropTypes.string, _react.PropTypes.arrayOf(_react.PropTypes.string)]),
/**
* The y prop specifies how to access the Y value of each data point.
* If given as a function, it will be run on each data point, and returned value will be used.
* If given as an integer, it will be used as an array index for array-type data points.
* If given as a string, it will be used as a property key for object-type data points.
* If given as an array of strings, or a string containing dots or brackets,
* it will be used as a nested object property path (for details see Lodash docs for _.get).
* If `null` or `undefined`, the data value will be used as is (identity function/pass-through).
* @examples 0, 'y', 'y.value.nested.1.thing', 'y[2].also.nested', null, d => Math.sin(d)
*/
y: _react.PropTypes.oneOfType([_react.PropTypes.func, _victoryCore.PropTypes.allOfType([_victoryCore.PropTypes.integer, _victoryCore.PropTypes.nonNegative]), _react.PropTypes.string, _react.PropTypes.arrayOf(_react.PropTypes.string)])
};
VictoryLine.defaultProps = {
events: {},
height: 300,
interpolation: "linear",
padding: 50,
samples: 50,
scale: "linear",
standalone: true,
width: 450,
x: "x",
y: "y",
dataComponent: _react2.default.createElement(_lineSegment2.default, null),
labelComponent: _react2.default.createElement(_victoryCore.VictoryLabel, null)
};
VictoryLine.getDomain = _domain2.default.getDomain.bind(_domain2.default);
VictoryLine.getData = _data2.default.getData.bind(_data2.default);
exports.default = VictoryLine;