UNPKG

react-timeseries-charts

Version:
618 lines (523 loc) 29.1 kB
"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; }; }(); var _underscore = require("underscore"); var _underscore2 = _interopRequireDefault(_underscore); var _react = require("react"); var _react2 = _interopRequireDefault(_react); var _propTypes = require("prop-types"); var _propTypes2 = _interopRequireDefault(_propTypes); var _d3Ease = require("d3-ease"); var _d3Scale = require("d3-scale"); var _reactHotLoader = require("react-hot-loader"); var _Brush = require("./Brush"); var _Brush2 = _interopRequireDefault(_Brush); var _YAxis = require("./YAxis"); var _YAxis2 = _interopRequireDefault(_YAxis); var _Charts = require("./Charts"); var _Charts2 = _interopRequireDefault(_Charts); var _MultiBrush = require("./MultiBrush"); var _MultiBrush2 = _interopRequireDefault(_MultiBrush); var _TimeMarker = require("./TimeMarker"); var _TimeMarker2 = _interopRequireDefault(_TimeMarker); var _interpolators = require("../js/interpolators"); var _interpolators2 = _interopRequireDefault(_interpolators); 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. */ function createScale(yaxis, type, min, max, y0, y1) { var scale = void 0; if (_underscore2.default.isUndefined(min) || _underscore2.default.isUndefined(max)) { scale = null; } else if (type === "linear") { scale = (0, _d3Scale.scaleLinear)().domain([min, max]).range([y0, y1]).nice(); } else if (type === "log") { var base = yaxis.props.logBase || 10; scale = (0, _d3Scale.scaleLog)().base(base).domain([min, max]).range([y0, y1]); } else if (type === "power") { var power = yaxis.props.powerExponent || 2; scale = (0, _d3Scale.scalePow)().exponent(power).domain([min, max]).range([y0, y1]); } return scale; } /** * A ChartRow is a container for a set of YAxis and multiple charts * which are overlaid on each other in a central canvas. * * Here is an example where a single `<ChartRow>` is defined within * the `<ChartContainer>`. Of course you can have any number of rows. * * For this row we specify the one prop `height` as 200 pixels high. * * Within the `<ChartRow>` we add: * * * `<YAxis>` elements for axes to the left of the chart * * `<Chart>` block containing our central chart area * * `<YAxis>` elements for our axes to the right of the charts * * ``` * <ChartContainer timeRange={audSeries.timerange()}> * <ChartRow height="200"> * <YAxis /> * <YAxis /> * <Charts> * charts... * </Charts> * <YAxis /> * </ChartRow> * </ChartContainer> * ``` */ var ChartRow = function (_React$Component) { _inherits(ChartRow, _React$Component); function ChartRow(props) { _classCallCheck(this, ChartRow); // id of clipping rectangle we will generate and use for each child // chart. Lives in state to ensure just one clipping rectangle and // id per chart row instance; we don't want a fresh id generated on // each render. var _this = _possibleConstructorReturn(this, (ChartRow.__proto__ || Object.getPrototypeOf(ChartRow)).call(this, props)); _this.isChildYAxis = function (child) { return (0, _reactHotLoader.areComponentsEqual)(child.type, _YAxis2.default) || _underscore2.default.has(child.props, "min") && _underscore2.default.has(child.props, "max"); }; var clipId = _underscore2.default.uniqueId("clip_"); var clipPathURL = "url(#" + clipId + ")"; _this.state = { clipId: clipId, clipPathURL: clipPathURL }; _this.mounted = true; return _this; } _createClass(ChartRow, [{ key: "updateScales", value: function updateScales(props) { var _this2 = this; var axisMargin = props.axisMargin; var innerHeight = +props.height - axisMargin * 2; var rangeTop = axisMargin; var rangeBottom = innerHeight - axisMargin; _react2.default.Children.forEach(props.children, function (child) { if (child === null) return; if (_this2.isChildYAxis(child)) { var _child$props = child.props, id = _child$props.id, max = _child$props.max, min = _child$props.min, _child$props$transiti = _child$props.transition, transition = _child$props$transiti === undefined ? 0 : _child$props$transiti, _child$props$type = _child$props.type, type = _child$props$type === undefined ? "linear" : _child$props$type; if (!_underscore2.default.has(_this2.scaleMap, id)) { // If necessary, initialize a ScaleInterpolator for this y-axis. // When the yScale changes, we will update this interpolator. _this2.scaleMap[id] = new _interpolators2.default(transition, _d3Ease.easeSinOut, function (s) { var yAxisScalerMap = _this2.state.yAxisScalerMap; yAxisScalerMap[id] = s; if (_this2.mounted) _this2.setState(yAxisScalerMap); }); } // Get the vertical scale for this y-axis. var scale = void 0; if (_underscore2.default.has(child.props, "yScale")) { // If the yScale prop is passed explicitly, use that. scale = child.props.yScale; } else { // Otherwise, compute the scale based on the max and min props. scale = createScale(child, type, min, max, rangeBottom, rangeTop); } // Update the scale on the interpolator for this y-axis. var cacheKey = type + "-" + min + "-" + max + "-" + rangeBottom + "-" + rangeTop; _this2.scaleMap[id].setScale(cacheKey, scale); } }); // Update the state with the newly interpolated scaler for each y-axis. var scalerMap = {}; _underscore2.default.forEach(this.scaleMap, function (interpolator, id) { scalerMap[id] = interpolator.scaler(); }); if (this.mounted) this.setState({ yAxisScalerMap: scalerMap }); } }, { key: "componentWillMount", value: function componentWillMount() { // Our chart scales are driven off a mapping between id of the axis // and the scale that axis represents. Depending on the transition time, // this scale will animate over time. The controller of this animation is // the ScaleInterpolator. We create new Scale Interpolators here for each // axis id. this.scaleMap = {}; this.updateScales(this.props); } /** * When we get changes to the row's props we update our map of * axis scales. */ }, { key: "componentWillReceiveProps", value: function componentWillReceiveProps(nextProps) { this.updateScales(nextProps); } }, { key: "componentWillUnmount", value: function componentWillUnmount() { this.mounted = false; } }, { key: "render", value: function render() { var _this3 = this; var _props = this.props, paddingLeft = _props.paddingLeft, paddingRight = _props.paddingRight; var axes = []; // Contains all the yAxis elements used in the render var chartList = []; // Contains all the Chart elements used in the render // Dimensions var innerHeight = +this.props.height - this.props.axisMargin * 2; // // Build a map of elements that occupy left or right slots next to the // chart. // // If an element has both and id and a min/max range, then we consider // it to be a y axis. For those we calculate a d3 scale that can be // reference by a chart. That scale will also be available to the axis // when it renders. // // For this row, we will need to know how many axis slots we are using. // var yAxisMap = {}; // Maps axis id -> axis element var leftAxisList = []; // Ordered list of left axes ids var rightAxisList = []; // Ordered list of right axes ids var alignLeft = true; _react2.default.Children.forEach(this.props.children, function (child) { if (child === null) return; if ((0, _reactHotLoader.areComponentsEqual)(child.type, _Charts2.default)) { alignLeft = false; } else { var _id = child.props.id; // Check to see if we think this 'axis' is actually an axis if (_this3.isChildYAxis(child)) { var yaxis = child; if (yaxis.props.id && yaxis.props.visible !== false) { // Relate id to the axis yAxisMap[yaxis.props.id] = yaxis; } // Columns counts if (alignLeft) { leftAxisList.push(_id); } else { rightAxisList.push(_id); } } } }); // Since we'll be building the left axis items from the inside to the outside leftAxisList.reverse(); // // Push each axis onto the axes, transforming each into its // column location // var transform = void 0; var id = void 0; var props = void 0; var axis = void 0; var posx = 0; // Space used by columns on left and right of charts var leftWidth = _underscore2.default.reduce(this.props.leftAxisWidths, function (a, b) { return a + b; }, 0); var rightWidth = _underscore2.default.reduce(this.props.rightAxisWidths, function (a, b) { return a + b; }, 0); var chartWidth = this.props.width - leftWidth - rightWidth - paddingLeft - paddingRight; posx = leftWidth; for (var leftColumnIndex = 0; leftColumnIndex < this.props.leftAxisWidths.length; leftColumnIndex += 1) { var colWidth = this.props.leftAxisWidths[leftColumnIndex]; posx -= colWidth; if (colWidth > 0 && leftColumnIndex < leftAxisList.length) { id = leftAxisList[leftColumnIndex]; if (_underscore2.default.has(yAxisMap, id)) { transform = "translate(" + (posx + paddingLeft) + ",0)"; // Additional props for left aligned axes props = { width: colWidth, height: innerHeight, chartExtent: chartWidth, isInnerAxis: leftColumnIndex === 0, align: "left", scale: this.scaleMap[id].latestScale() }; // Cloned left axis axis = _react2.default.cloneElement(yAxisMap[id], props); axes.push(_react2.default.createElement( "g", { key: "y-axis-left-" + leftColumnIndex, transform: transform }, axis )); } } } posx = this.props.width - rightWidth - paddingRight; for (var rightColumnIndex = 0; rightColumnIndex < this.props.rightAxisWidths.length; rightColumnIndex += 1) { var _colWidth = this.props.rightAxisWidths[rightColumnIndex]; if (_colWidth > 0 && rightColumnIndex < rightAxisList.length) { id = rightAxisList[rightColumnIndex]; if (_underscore2.default.has(yAxisMap, id)) { transform = "translate(" + (posx + paddingLeft) + ",0)"; // Additional props for right aligned axes props = { width: _colWidth, height: innerHeight, chartExtent: chartWidth, //showGrid: this.props.showGrid, isInnerAxis: rightColumnIndex === 0, align: "right", scale: this.scaleMap[id].latestScale() }; // Cloned right axis axis = _react2.default.cloneElement(yAxisMap[id], props); axes.push(_react2.default.createElement( "g", { key: "y-axis-right-" + rightColumnIndex, transform: transform }, axis )); } } posx += _colWidth; } // // Push each chart onto the chartList, transforming each to the right // of the left axis slots and specifying its width. Each chart is passed // its time and y-scale. The y-scale is looked up in scaleMap, whose // current value is stored in the component state. // var chartTransform = "translate(" + (leftWidth + paddingLeft) + ",0)"; var keyCount = 0; _react2.default.Children.forEach(this.props.children, function (child) { if (child === null) return; if ((0, _reactHotLoader.areComponentsEqual)(child.type, _Charts2.default)) { var _charts = child; _react2.default.Children.forEach(_charts.props.children, function (chart) { if (!_underscore2.default.has(chart.props, "visible") || chart.props.visible) { var scale = null; if (_underscore2.default.has(_this3.state.yAxisScalerMap, chart.props.axis)) { scale = _this3.state.yAxisScalerMap[chart.props.axis]; } var ytransition = null; if (_underscore2.default.has(_this3.scaleMap, chart.props.axis)) { ytransition = _this3.scaleMap[chart.props.axis]; } var chartProps = { key: keyCount, width: chartWidth, height: innerHeight, timeScale: _this3.props.timeScale, timeFormat: _this3.props.timeFormat }; if (scale) { chartProps.yScale = scale; } if (ytransition) { chartProps.transition = ytransition; } chartList.push(_react2.default.cloneElement(chart, chartProps)); keyCount += 1; } }); } }); // // Push each child Brush on to the brush list. We need brushed to be // rendered last (on top) of everything else in the Z order, both for // visual correctness and to ensure that the brush gets mouse events // before anything underneath // var brushList = []; var multiBrushList = []; keyCount = 0; _react2.default.Children.forEach(this.props.children, function (child) { if (child === null) return; if ((0, _reactHotLoader.areComponentsEqual)(child.type, _Brush2.default) || (0, _reactHotLoader.areComponentsEqual)(child.type, _MultiBrush2.default)) { var brushProps = { key: "brush-" + keyCount, width: chartWidth, height: innerHeight, timeScale: _this3.props.timeScale }; if ((0, _reactHotLoader.areComponentsEqual)(child.type, _Brush2.default)) { brushList.push(_react2.default.cloneElement(child, brushProps)); } else { multiBrushList.push(_react2.default.cloneElement(child, brushProps)); } } keyCount += 1; }); var charts = _react2.default.createElement( "g", { transform: chartTransform, key: "event-rect-group" }, _react2.default.createElement( "g", { key: "charts", clipPath: this.state.clipPathURL }, chartList ) ); // // Clipping // var clipper = _react2.default.createElement( "defs", null, _react2.default.createElement( "clipPath", { id: this.state.clipId }, _react2.default.createElement("rect", { x: "0", y: "0", style: { strokeOpacity: 0.0 }, width: chartWidth, height: innerHeight }) ) ); // // Brush // var brushes = _react2.default.createElement( "g", { transform: chartTransform, key: "brush-group" }, brushList ); // // Multi Brush // var multiBrushes = _react2.default.createElement( "g", { transform: chartTransform, key: "multi-brush-group" }, multiBrushList ); // // TimeMarker used as a tracker // var tracker = void 0; if (this.props.trackerTime) { var timeFormat = this.props.trackerTimeFormat || this.props.timeFormat; var timeMarkerProps = { timeFormat: timeFormat, showLine: false, showTime: this.props.trackerShowTime, time: this.props.trackerTime, timeScale: this.props.timeScale, width: chartWidth, infoStyle: this.props.trackerStyle }; if (this.props.trackerInfoValues) { timeMarkerProps.infoWidth = this.props.trackerInfoWidth; timeMarkerProps.infoHeight = this.props.trackerInfoHeight; timeMarkerProps.infoValues = this.props.trackerInfoValues; timeMarkerProps.timeFormat = this.props.trackerTimeFormat; } var trackerStyle = { pointerEvents: "none" }; var trackerTransform = "translate(" + (leftWidth + paddingLeft) + ",0)"; tracker = _react2.default.createElement( "g", { key: "tracker-group", style: trackerStyle, transform: trackerTransform }, _react2.default.createElement(_TimeMarker2.default, timeMarkerProps) ); } return _react2.default.createElement( "g", null, clipper, axes, charts, brushes, multiBrushes, tracker ); } }]); return ChartRow; }(_react2.default.Component); exports.default = ChartRow; ChartRow.defaultProps = { trackerTimeFormat: "%b %d %Y %X", enablePanZoom: false, height: 100, axisMargin: 5, visible: true }; ChartRow.propTypes = { /** * The height of the row. */ height: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), /** * The vertical margin between the top and bottom of the row * height and the top and bottom of the range of the chart. */ axisMargin: _propTypes2.default.number, /** * Show or hide this row */ visible: _propTypes2.default.bool, /** * Should the time be shown on top of the tracker info box */ trackerShowTime: _propTypes2.default.bool, /** * The width of the tracker info box */ trackerInfoWidth: _propTypes2.default.number, /** * The height of the tracker info box */ trackerInfoHeight: _propTypes2.default.number, /** * Info box value or values to place next to the tracker line. * This is either an array of objects, with each object * specifying the label (a string) and value (also a string) * to be shown in the info box, or a simple string label. */ trackerInfoValues: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.arrayOf(_propTypes2.default.shape({ label: _propTypes2.default.string, // eslint-disable-line value: _propTypes2.default.string // eslint-disable-line }))]), /** * Specify the title for the chart row */ title: _propTypes2.default.string, /** * Specify the height of the title * Default value is 28 pixels */ titleHeight: _propTypes2.default.number, /** * Specify the styling of the chart row's title */ titleStyle: _propTypes2.default.object, /** * Specify the styling of the box behind chart row's title */ titleBoxStyle: _propTypes2.default.object, children: _propTypes2.default.oneOfType([_propTypes2.default.arrayOf(_propTypes2.default.node), _propTypes2.default.node]), leftAxisWidths: _propTypes2.default.arrayOf(_propTypes2.default.number), rightAxisWidths: _propTypes2.default.arrayOf(_propTypes2.default.number), width: _propTypes2.default.number, timeScale: _propTypes2.default.func, trackerTimeFormat: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.func]), timeFormat: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.func]), trackerTime: _propTypes2.default.instanceOf(Date) };