react-timeseries-charts
Version:
Declarative timeseries charts
457 lines (386 loc) • 22.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
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; }; }();
require("d3-transition");
var _underscore = require("underscore");
var _underscore2 = _interopRequireDefault(_underscore);
var _merge = require("merge");
var _merge2 = _interopRequireDefault(_merge);
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
var _reactDom = require("react-dom");
var _reactDom2 = _interopRequireDefault(_reactDom);
var _propTypes = require("prop-types");
var _propTypes2 = _interopRequireDefault(_propTypes);
var _d3Array = require("d3-array");
var _d3Axis = require("d3-axis");
var _d3Ease = require("d3-ease");
var _d3Format = require("d3-format");
var _d3Selection = require("d3-selection");
require("d3-selection-multi");
var _util = require("../js/util");
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; } /**
* Copyright (c) 2015-present, The Regents of the University of California,
* through Lawrence Berkeley National Laboratory (subject to receipt
* of any required approvals from the U.S. Dept. of Energy).
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
// eslint-disable-line
var MARGIN = 0;
var defaultStyle = {
label: {
stroke: "none",
fill: "#8B7E7E", // Default label color
fontWeight: 100,
fontSize: 12,
font: '"Goudy Bookletter 1911", sans-serif"'
},
values: {
stroke: "none",
fill: "#8B7E7E", // Default value color
fontWeight: 100,
fontSize: 11,
font: '"Goudy Bookletter 1911", sans-serif"'
},
ticks: {
fill: "none",
stroke: "#C0C0C0"
},
axis: {
fill: "none",
stroke: "#C0C0C0"
}
};
/**
* The `YAxis` widget displays a vertical axis to the left or right
* of the charts. A `YAxis` always appears within a `ChartRow`, from
* which it gets its height and positioning. You can have more than
* one axis per row. You do control how wide it is.
*
* Here's a simple YAxis example:
*
* ```js
* <YAxis
* id="price-axis"
* label="Price (USD)"
* min={0} max={100}
* width="60"
* type="linear"
* format="$,.2f"
* />
* ```
*
* Visually you can control the axis `label`, its size via the `width`
* prop, its `format`, and `type` of scale (linear). You can quicky turn
* it on and off with the `visible` prop.
*
* Each axis also defines a scale through a `min` and `max` prop. Chart
* then refer to the axis by by citing the axis `id` in their `axis`
* prop. Those charts will then use the axis scale for their y-scale.
* This is what ties them together. Many charts can use the same axis,
* or not.
*
* Here is an example of two line charts that each have their own axis:
*
* ```js
* <ChartContainer timeRange={audSeries.timerange()}>
* <ChartRow height="200">
* <YAxis id="aud" label="AUD" min={0.5} max={1.5} width="60" format="$,.2f"/>
* <Charts>
* <LineChart axis="aud" series={audSeries} style={audStyle}/>
* <LineChart axis="euro" series={euroSeries} style={euroStyle}/>
* </Charts>
* <YAxis id="euro" label="Euro" min={0.5} max={1.5} width="80" format="$,.2f"/>
* </ChartRow>
* </ChartContainer>
* ```
*
* Note that there are two `<YAxis>` components defined here, one before
* the `<Charts>` block and one after. This defines that the first axis will
* appear to the left of the charts and the second will appear right of the charts.
* Each of the line charts uses its `axis` prop to identify the axis ("aud" or "euro")
* it will use for its vertical scale.
*/
var YAxis = function (_React$Component) {
_inherits(YAxis, _React$Component);
function YAxis() {
_classCallCheck(this, YAxis);
return _possibleConstructorReturn(this, (YAxis.__proto__ || Object.getPrototypeOf(YAxis)).apply(this, arguments));
}
_createClass(YAxis, [{
key: "componentDidMount",
value: function componentDidMount() {
this.renderAxis(this.props.align, this.props.scale, +this.props.width, +this.props.height, this.props.showGrid, +this.props.chartExtent, this.props.hideAxisLine, this.props.absolute, this.props.type, this.props.format, this.props.label, this.props.tickCount, this.props.min, this.props.max);
}
}, {
key: "componentWillReceiveProps",
value: function componentWillReceiveProps(nextProps) {
var scale = nextProps.scale,
align = nextProps.align,
width = nextProps.width,
height = nextProps.height,
chartExtent = nextProps.chartExtent,
absolute = nextProps.absolute,
format = nextProps.format,
type = nextProps.type,
showGrid = nextProps.showGrid,
hideAxisLine = nextProps.hideAxisLine,
label = nextProps.label,
tickCount = nextProps.tickCount,
min = nextProps.min,
max = nextProps.max;
if ((0, _util.scaleAsString)(this.props.scale) !== (0, _util.scaleAsString)(scale)) {
this.updateAxis(align, scale, width, height, showGrid, chartExtent, hideAxisLine, absolute, type, format, label, tickCount, min, max);
} else if (this.props.format !== format || this.props.align !== align || this.props.width !== width || this.props.height !== height || this.props.type !== type || this.props.absolute !== absolute || this.props.chartExtent !== chartExtent || this.props.showGrid !== showGrid || this.props.hideAxisLine !== hideAxisLine) {
this.renderAxis(align, scale, +width, +height, showGrid, chartExtent, hideAxisLine, absolute, type, format, label, tickCount, min, max);
} else if (this.props.label !== label) {
this.updateLabel(label);
}
}
}, {
key: "shouldComponentUpdate",
value: function shouldComponentUpdate() {
return false;
}
}, {
key: "yformat",
value: function yformat(fmt) {
if (_underscore2.default.isString(fmt)) {
return (0, _d3Format.format)(fmt);
} else if (_underscore2.default.isFunction(fmt)) {
return fmt;
} else {
return (0, _d3Format.format)("");
}
}
}, {
key: "mergeStyles",
value: function mergeStyles(style) {
return {
labelStyle: (0, _merge2.default)(true, defaultStyle.label, this.props.style.label ? this.props.style.label : {}),
valueStyle: (0, _merge2.default)(true, defaultStyle.values, this.props.style.values ? this.props.style.values : {}),
axisStyle: (0, _merge2.default)(true, defaultStyle.axis, this.props.style.axis ? this.props.style.axis : {}),
tickStyle: (0, _merge2.default)(true, defaultStyle.ticks, this.props.style.ticks ? this.props.style.ticks : {})
};
}
}, {
key: "postSelect",
value: function postSelect(style, hideAxisLine, height) {
var valueStyle = style.valueStyle,
tickStyle = style.tickStyle,
axisStyle = style.axisStyle;
(0, _d3Selection.select)(_reactDom2.default.findDOMNode(this)).select("g").selectAll(".tick").select("text").styles(valueStyle);
(0, _d3Selection.select)(_reactDom2.default.findDOMNode(this)).select("g").selectAll(".tick").select("line").styles(tickStyle);
(0, _d3Selection.select)(_reactDom2.default.findDOMNode(this)).select("g").selectAll(".domain").remove();
if (!hideAxisLine) {
(0, _d3Selection.select)(_reactDom2.default.findDOMNode(this)).select("g").append("line").styles(axisStyle).attr("x1", 0).attr("y1", 0).attr("x2", 0).attr("y2", height);
}
}
}, {
key: "generator",
value: function generator(type, absolute, yformat, axis, scale, height, tickCount, min, max) {
var axisGenerator = void 0;
if (type === "linear" || type === "power") {
if (tickCount > 0) {
var stepSize = (max - min) / (tickCount - 1);
axisGenerator = axis(scale).tickValues((0, _d3Array.range)(min, max + max / 10000, stepSize)).tickFormat(function (d) {
if (absolute) {
return yformat(Math.abs(d));
}
return yformat(d);
}).tickSizeOuter(0);
} else {
if (height <= 200) {
axisGenerator = axis(scale).ticks(4).tickFormat(function (d) {
if (absolute) {
return yformat(Math.abs(d));
}
return yformat(d);
}).tickSizeOuter(0);
} else {
axisGenerator = axis(scale).tickFormat(function (d) {
if (absolute) {
return yformat(Math.abs(d));
}
return yformat(d);
}).tickSizeOuter(0);
}
}
} else if (type === "log") {
if (min === 0) {
throw Error("In a log scale, minimum value can't be 0");
}
axisGenerator = axis(scale).ticks(10, ".2s").tickSizeOuter(0);
}
return axisGenerator;
}
}, {
key: "renderAxis",
value: function renderAxis(align, scale, width, height, showGrid, chartExtent, hideAxisLine, absolute, type, fmt, label, tickCount, min, max) {
var yformat = this.yformat(fmt);
var axis = align === "left" ? _d3Axis.axisLeft : _d3Axis.axisRight;
var style = this.mergeStyles(this.props.style);
var labelStyle = style.labelStyle,
valueStyle = style.valueStyle;
var tickSize = showGrid && this.props.isInnerAxis ? -chartExtent : 5;
var x = align === "left" ? width - MARGIN : 0;
var labelOffset = align === "left" ? this.props.labelOffset - 50 : 40 + this.props.labelOffset;
// Axis generator
var axisGenerator = this.generator(type, absolute, yformat, axis, scale, height, tickCount, min, max);
// Remove the old axis from under this DOM node
(0, _d3Selection.select)(_reactDom2.default.findDOMNode(this)).selectAll("*").remove();
// Add the new axis
this.axis = (0, _d3Selection.select)(_reactDom2.default.findDOMNode(this)).append("g").attr("transform", "translate(" + x + ",0)").attr("class", "yaxis").styles(valueStyle).call(axisGenerator.tickSize(tickSize)).append("text").text(label || this.props.label).styles(labelStyle).attr("transform", "rotate(-90)").attr("class", "yaxislabel").attr("y", labelOffset).attr("dy", ".71em").attr("text-anchor", "end");
this.postSelect(style, hideAxisLine, height);
}
}, {
key: "updateAxis",
value: function updateAxis(align, scale, width, height, showGrid, chartExtent, hideAxisLine, absolute, type, fmt, label, tickCount, min, max) {
var yformat = this.yformat(fmt);
var axis = align === "left" ? _d3Axis.axisLeft : _d3Axis.axisRight;
var style = this.mergeStyles(this.props.style);
var tickSize = showGrid && this.props.isInnerAxis ? -chartExtent : 5;
var axisGenerator = this.generator(type, absolute, yformat, axis, scale, height, tickCount, min, max);
// Transition the existing axis
(0, _d3Selection.select)(_reactDom2.default.findDOMNode(this)).select(".yaxis").transition().duration(this.props.transition).ease(_d3Ease.easeSinOut).call(axisGenerator.tickSize(tickSize));
this.updateLabel(label);
this.postSelect(style, hideAxisLine, height);
}
}, {
key: "updateLabel",
value: function updateLabel(label) {
(0, _d3Selection.select)(_reactDom2.default.findDOMNode(this)).select(".yaxislabel").text(label);
}
}, {
key: "render",
value: function render() {
return _react2.default.createElement("g", null);
}
}]);
return YAxis;
}(_react2.default.Component);
exports.default = YAxis;
YAxis.defaultProps = {
id: "yaxis", // id referred to by the chart
align: "left", // left or right of the chart
min: 0, // range
max: 1,
showGrid: false,
hideAxisLine: false,
type: "linear", // linear, log, or power
absolute: false, // Display scale always positive
format: ".2s", // Format string for d3.format
labelOffset: 0, // Offset the label position
transition: 100, // Axis transition time
width: 80,
style: defaultStyle
};
YAxis.propTypes = {
/**
* A name for the axis which can be used by a chart to reference the axis.
* This is used by the ChartRow to match charts to this axis.
*/
id: _propTypes2.default.string.isRequired, // eslint-disable-line
/**
* Show or hide this axis
*/
visible: _propTypes2.default.bool,
/**
* The label to be displayed alongside the axis.
*/
label: _propTypes2.default.string,
/**
* The scale type: linear, power, or log.
*/
type: _propTypes2.default.oneOf(["linear", "power", "log"]),
/**
* Minimum value, which combined with "max", define the scale of the axis.
*/
min: _propTypes2.default.number.isRequired, // eslint-disable-line
/**
* Maximum value, which combined with "min", define the scale of the axis.
*/
max: _propTypes2.default.number.isRequired, // eslint-disable-line
/**
* A d3 scale for the y-axis which you can use to transform your data in the y direction.
* If omitted, the scale will be automatically computed based on the max and min props.
*/
yScale: _propTypes2.default.func,
/**
* Render all ticks on the axis as positive values.
*/
absolute: _propTypes2.default.bool, // eslint-disable-line
/**
* Object specifying the CSS by which the axis can be styled. The object can contain:
* "label", "values", "axis" and "ticks". Each of these is an inline CSS style applied
* to the axis label, axis values, axis line and ticks respectively.
*
* Note that these are passed into d3's styling, so are regular CSS property names
* and not React's camel case names (e.g. "stroke-dasharray" not strokeDasharray).
*/
style: _propTypes2.default.shape({
label: _propTypes2.default.object, // eslint-disable-line
axis: _propTypes2.default.object, // eslint-disable-line
values: _propTypes2.default.object, // esline-disable-line
ticks: _propTypes2.default.object // esline-disable-line
}),
/**
* Render a horizontal grid by extending the axis ticks across the chart area. Note that this
* can only be applied to an inner axis (one next to a chart). If you have multiple axes then
* this can't be used on the outer axes. Also, if you have an axis on either side of the chart
* then you can use this, but the UX not be ideal.
*/
showGrid: _propTypes2.default.bool,
/**
* Render the axis line. This is a nice option of you are also using `showGrid` as you may not
* want both the vertical axis line and the extended ticks.
*/
hideAxisLine: _propTypes2.default.bool,
/**
* The transition time for moving from one scale to another
*/
transition: _propTypes2.default.number,
/**
* The width of the axis
*/
width: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
/**
* Offset the axis label from its default position. This allows you to
* fine tune the label location, which may be necessary depending on the
* scale and how much room the tick labels take up. Maybe positive or
* negative.
*/
labelOffset: _propTypes2.default.number,
/**
* If a string, the d3.format for the axis labels (e.g. `format=\"$,.2f\"`).
* If a function, that function will be called with each tick value and
* should generate a formatted string for that value to be used as the label
* for that tick (e.g. `function (n) { return Number(n).toFixed(2) }`).
*/
format: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.func]),
/**
* If the chart should be rendered to with the axis on the left or right.
* If you are using the axis in a ChartRow, you do not need to provide this.
*/
align: _propTypes2.default.string,
/**
* [Internal] The scale supplied by the ChartRow
*/
scale: _propTypes2.default.func,
/**
* [Internal] The height supplied by the surrounding ChartContainer
*/
height: _propTypes2.default.number,
/**
* The number of ticks
*/
tickCount: _propTypes2.default.number
};