UNPKG

recharts

Version:
701 lines (581 loc) 23 kB
'use strict'; 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 _class, _temp2; /** * @fileOverview Scatter Chart */ Object.defineProperty(exports, "__esModule", { value: true }); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _invariant = require('invariant'); var _invariant2 = _interopRequireDefault(_invariant); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _d3Scale = require('d3-scale'); var _rechartsScale = require('recharts-scale'); var _Surface = require('../container/Surface'); var _Surface2 = _interopRequireDefault(_Surface); var _Layer = require('../container/Layer'); var _Layer2 = _interopRequireDefault(_Layer); var _Legend = require('../component/Legend'); var _Legend2 = _interopRequireDefault(_Legend); var _Tooltip = require('../component/Tooltip'); var _Tooltip2 = _interopRequireDefault(_Tooltip); var _Cross = require('../shape/Cross'); var _Cross2 = _interopRequireDefault(_Cross); var _CartesianAxis = require('../cartesian/CartesianAxis'); var _CartesianAxis2 = _interopRequireDefault(_CartesianAxis); var _CartesianGrid = require('../cartesian/CartesianGrid'); var _CartesianGrid2 = _interopRequireDefault(_CartesianGrid); var _Scatter = require('../cartesian/Scatter'); var _Scatter2 = _interopRequireDefault(_Scatter); var _XAxis = require('../cartesian/XAxis'); var _XAxis2 = _interopRequireDefault(_XAxis); var _YAxis = require('../cartesian/YAxis'); var _YAxis2 = _interopRequireDefault(_YAxis); var _ZAxis = require('../cartesian/ZAxis'); var _ZAxis2 = _interopRequireDefault(_ZAxis); var _ReactUtils = require('../util/ReactUtils'); var _ReactUtils2 = _interopRequireDefault(_ReactUtils); 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 ScatterChart = (_temp2 = _class = function (_Component) { _inherits(ScatterChart, _Component); function ScatterChart() { var _Object$getPrototypeO; var _temp, _this, _ret; _classCallCheck(this, ScatterChart); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_Object$getPrototypeO = Object.getPrototypeOf(ScatterChart)).call.apply(_Object$getPrototypeO, [this].concat(args))), _this), _this.state = { activeTooltipCoord: { x: 0, y: 0 }, isTooltipActive: false, activeItem: null }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(ScatterChart, [{ key: 'getComposedData', /** * Compose the data of each group * @param {Array} data The original data * @param {Object} xAxis The configuration of x-axis * @param {Object} yAxis The configuration of y-axis * @param {Object} zAxis The configuration of z-axis * @return {Array} Composed data */ value: function getComposedData(data, xAxis, yAxis, zAxis) { var xAxisDataKey = xAxis.dataKey; var yAxisDataKey = yAxis.dataKey; var zAxisDataKey = zAxis.dataKey; return data.map(function (entry) { return { cx: xAxis.scale(entry[xAxisDataKey]), cy: yAxis.scale(entry[yAxisDataKey]), r: zAxisDataKey !== undefined ? zAxis.scale(entry[zAxisDataKey]) : zAxis.range[0], payload: { x: entry[xAxisDataKey], y: entry[yAxisDataKey], z: zAxisDataKey !== undefined && entry[zAxisDataKey] || '-' } }; }); } /** * Get the ticks of an axis * @param {Object} axis The configuration of an axis * @param {Boolean} isGrid Whether or not are the ticks in grid * @return {Array} Ticks */ }, { key: 'getAxisTicks', value: function getAxisTicks(axis) { var isGrid = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; var scale = axis.scale; if (axis.ticks) { return axis.ticks.map(function (entry) { return { coord: scale(entry), value: entry }; }); } if (scale.ticks) { return scale.ticks(axis.tickCount).map(function (entry) { return { coord: scale(entry), value: entry }; }); } return scale.domain().map(function (entry) { return { coord: scale(entry), value: entry }; }); } /** * Calculate the ticks of grid * @param {Array} ticks The ticks in axis * @param {Number} min The minimun value of axis * @param {Number} max The maximun value of axis * @return {Array} Ticks */ }, { key: 'getGridTicks', value: function getGridTicks(ticks, min, max) { var hasMin = undefined; var hasMax = undefined; var values = undefined; values = ticks.map(function (entry) { if (entry.coord === min) { hasMin = true; } if (entry.coord === max) { hasMax = true; } return entry.coord; }); if (!hasMin) { values.push(min); } if (!hasMax) { values.push(max); } return values; } /** * get domain of ticks * @param {Array} ticks Ticks of axis * @return {Array} domain */ }, { key: 'getDomainOfTicks', value: function getDomainOfTicks(ticks) { return [Math.min.apply(null, ticks), Math.max.apply(null, ticks)]; } }, { key: 'getDomain', value: function getDomain(items, dataKey) { var domain = items.reduce(function (result, item) { return result.concat(item.props.data.map(function (entry) { return entry[dataKey]; })); }, []); return [Math.min.apply(null, domain), Math.max.apply(null, domain)]; } /** * Get the configuration of x-axis or y-axis * @param {String} axisType The type of axis * @param {Array} items The instances of item * @return {Object} Configuration */ }, { key: 'getAxis', value: function getAxis() { var axisType = arguments.length <= 0 || arguments[0] === undefined ? 'xAxis' : arguments[0]; var items = arguments[1]; var children = this.props.children; var Axis = axisType === 'xAxis' ? _XAxis2.default : _YAxis2.default; var axis = _ReactUtils2.default.findChildByType(children, Axis); (0, _invariant2.default)(axis, 'recharts: ScatterChart must has %s', Axis.displayName); if (axis) { var domain = this.getDomain(items, axis.props.dataKey); return _extends({}, axis.props, { axisType: axisType, domain: domain }); } return null; } /** * Get the configuration of z-axis * @param {Array} items The instances of item * @return {Object} Configuration */ }, { key: 'getZAxis', value: function getZAxis(items) { var children = this.props.children; var axisItem = _ReactUtils2.default.findChildByType(children, _ZAxis2.default); var axisProps = axisItem && axisItem.props || _ZAxis2.default.defaultProps; var domain = axisProps.dataKey ? this.getDomain(items, axisProps.dataKey) : [-1, 1]; return _extends({}, axisProps, { domain: domain, scale: (0, _d3Scale.linear)().domain(domain).range(axisProps.range) }); } }, { key: 'getOffset', value: function getOffset(xAxis, yAxis) { var _props = this.props; var width = _props.width; var height = _props.height; var margin = _props.margin; var offset = _extends({}, margin); offset[xAxis.orientation] += xAxis.height; offset[yAxis.orientation] += yAxis.width; return _extends({}, offset, { width: width - offset.left - offset.right, height: height - offset.top - offset.bottom }); } /** * Configure the scale function of axis * @param {Object} scale The scale function * @param {Object} opts The configuration of axis * @return {Object} null */ }, { key: 'setTicksOfScale', value: function setTicksOfScale(scale, opts) { // Give priority to use the options of ticks if (opts.ticks && opts.ticks) { opts.domain = this.getDomainOfTicks(opts.ticks, opts.type); scale.domain(opts.domain).ticks(opts.ticks.length); } else { // Calculate the ticks by the number of grid var domain = scale.domain(); var tickValues = (0, _rechartsScale.getNiceTickValues)(domain, opts.tickCount); opts.ticks = tickValues; opts.domain = this.getDomainOfTicks(tickValues, opts.type); scale.domain(opts.domain).ticks(opts.tickCount); } } /** * Calculate the scale function, position, width, height of axes * @param {Object} axis The configuration of axis * @param {Object} offset The offset of main part in the svg element * @param {Object} axisType The type of axis, x-axis or y-axis * @return {Object} Configuration */ }, { key: 'getFormatAxis', value: function getFormatAxis(axis, offset, axisType) { var orientation = axis.orientation; var domain = axis.domain; var tickFormat = axis.tickFormat; var range = axisType === 'xAxis' ? [offset.left, offset.left + offset.width] : [offset.top + offset.height, offset.top]; var scale = (0, _d3Scale.linear)().domain(domain).range(range); this.setTicksOfScale(scale, axis); if (tickFormat) { scale.tickFormat(tickFormat); } var x = undefined; var y = undefined; if (axisType === 'xAxis') { x = offset.left; y = orientation === 'top' ? offset.top - axis.height : offset.top + offset.height; } else { x = orientation === 'left' ? offset.left - axis.width : offset.right; y = offset.top; } return _extends({}, axis, { scale: scale, width: axisType === 'xAxis' ? offset.width : axis.width, height: axisType === 'yAxis' ? offset.height : axis.height, x: x, y: y }); } /** * Get the content to be displayed in the tooltip * @param {Object} data The data of active item * @param {Object} xAxis The configuration of x-axis * @param {Object} yAxis The configuration of y-axis * @param {Object} zAxis The configuration of z-axis * @return {Array} The content of tooltip */ }, { key: 'getTooltipContent', value: function getTooltipContent(data, xAxis, yAxis, zAxis) { if (!data) { return null; } var content = [{ key: xAxis.name || xAxis.dataKey, unit: xAxis.unit || '', value: data.x }, { key: yAxis.name || yAxis.dataKey, unit: yAxis.unit || '', value: data.y }]; if (data.z && data.z !== '-') { content.push({ key: zAxis.name || zAxis.dataKey, unit: zAxis.unit || '', value: data.z }); } return content; } /** * The handler of mouse entering a scatter * @param {Object} el The active scatter * @param {Object} e Event object * @return {Object} no return */ }, { key: 'handleScatterMouseEnter', value: function handleScatterMouseEnter(el, e) { this.setState({ isTooltipActive: true, activeItem: el, activeTooltipCoord: { x: el.cx, y: el.cy } }); } /** * The handler of mouse leaving a scatter * @return {Object} no return */ }, { key: 'handleScatterMouseLeave', value: function handleScatterMouseLeave() { this.setState({ isTooltipActive: false }); } /** * Draw Tooltip * @param {Array} items The instances of Scatter * @param {Object} xAxis The configuration of x-axis * @param {Object} yAxis The configuration of y-axis * @param {Object} zAxis The configuration of z-axis * @param {Object} offset The offset of main part in the svg element * @return {ReactElement} The instance of Tooltip */ }, { key: 'renderTooltip', value: function renderTooltip(items, xAxis, yAxis, zAxis, offset) { var children = this.props.children; var tooltipItem = _ReactUtils2.default.findChildByType(children, _Tooltip2.default); if (!tooltipItem || !tooltipItem.props.cursor || !this.state.isTooltipActive) { return null; } var _state = this.state; var chartX = _state.chartX; var chartY = _state.chartY; var isTooltipActive = _state.isTooltipActive; var activeItem = _state.activeItem; var activeTooltipCoord = _state.activeTooltipCoord; var viewBox = { x: offset.left, y: offset.top, width: offset.width, height: offset.height }; return _react2.default.cloneElement(tooltipItem, { viewBox: viewBox, active: isTooltipActive, label: '', payload: this.getTooltipContent(activeItem && activeItem.payload, xAxis, yAxis, zAxis), coordinate: activeTooltipCoord, mouseX: chartX, mouseY: chartY }); } /** * Draw grid * @param {Object} xAxis The configuration of x-axis * @param {Object} yAxis The configuration of y-axis * @param {Object} offset The offset of main part in the svg element * @return {ReactElement} The instance of grid */ }, { key: 'renderGrid', value: function renderGrid(xAxis, yAxis, offset) { var _props2 = this.props; var children = _props2.children; var width = _props2.width; var height = _props2.height; var gridItem = _ReactUtils2.default.findChildByType(children, _CartesianGrid2.default); if (!gridItem) { return null; } var verticalPoints = this.getGridTicks(_CartesianAxis2.default.getTicks(_extends({}, _CartesianAxis2.default.defaultProps, xAxis, { ticks: this.getAxisTicks(xAxis), viewBox: { x: 0, y: 0, width: width, height: height } })), offset.left, offset.left + offset.width); var horizontalPoints = this.getGridTicks(_CartesianAxis2.default.getTicks(_extends({}, _CartesianAxis2.default.defaultProps, yAxis, { ticks: this.getAxisTicks(yAxis), viewBox: { x: 0, y: 0, width: width, height: height } })), offset.top, offset.top + offset.height); return _react2.default.cloneElement(gridItem, { key: 'grid', x: offset.left, y: offset.top, width: offset.width, height: offset.height, verticalPoints: verticalPoints, horizontalPoints: horizontalPoints }); } /** * Draw legend * @param {Array} items The instances of Scatters * @param {Object} offset The offset of main part in the svg element * @return {ReactElement} The instance of Legend */ }, { key: 'renderLegend', value: function renderLegend(items, offset) { var _props3 = this.props; var children = _props3.children; var width = _props3.width; var height = _props3.height; var legendItem = _ReactUtils2.default.findChildByType(children, _Legend2.default); if (!legendItem) { return null; } var legendData = items.map(function (child) { var _child$props = child.props; var name = _child$props.name; var fill = _child$props.fill; var legendType = _child$props.legendType; return { type: legendType || 'square', color: fill, value: name || '' }; }, this); return _react2.default.cloneElement(legendItem, _extends({}, _Legend2.default.getWithHeight(legendItem, width, height), { payload: legendData })); } /** * Draw axis * @param {Object} axis The configuration of axis * @param {String} layerKey The key of layer * @return {ReactElement} The instance of axis */ }, { key: 'renderAxis', value: function renderAxis(axis, layerKey) { var _props4 = this.props; var width = _props4.width; var height = _props4.height; if (axis && !axis.hide) { return _react2.default.createElement( _Layer2.default, { key: layerKey, className: layerKey }, _react2.default.createElement(_CartesianAxis2.default, { x: axis.x, y: axis.y, width: axis.width, height: axis.height, orientation: axis.orientation, viewBox: { x: 0, y: 0, width: width, height: height }, ticks: this.getAxisTicks(axis) }) ); } } }, { key: 'renderCursor', value: function renderCursor(xAxis, yAxis, offset) { var children = this.props.children; var tooltipItem = _ReactUtils2.default.findChildByType(children, _Tooltip2.default); if (!tooltipItem || !this.state.isTooltipActive) { return null; } var activeItem = this.state.activeItem; var cursorProps = _extends({ fill: '#f1f1f1' }, _ReactUtils2.default.getPresentationAttributes(tooltipItem.props.cursor), offset, { x: activeItem.cx, y: activeItem.cy, payload: activeItem }); return _react2.default.isValidElement(tooltipItem.props.cursor) ? _react2.default.cloneElement(tooltipItem.props.cursor, cursorProps) : _react2.default.createElement(_Cross2.default, cursorProps); } /** * Draw the main part of scatter chart * @param {Array} items All the instance of Scatter * @param {Object} xAxis The configuration of all x-axis * @param {Object} yAxis The configuration of all y-axis * @param {Object} zAxis The configuration of all z-axis * @return {ReactComponent} All the instances of Scatter */ }, { key: 'renderItems', value: function renderItems(items, xAxis, yAxis, zAxis) { var _this2 = this; var activeGroupId = this.state.activeGroupId; return items.map(function (child, i) { var _child$props2 = child.props; var strokeWidth = _child$props2.strokeWidth; var data = _child$props2.data; var finalStrokeWidth = strokeWidth === +strokeWidth ? strokeWidth : 1; finalStrokeWidth = activeGroupId === 'scatter-' + i ? finalStrokeWidth + 2 : finalStrokeWidth; return _react2.default.cloneElement(child, { key: 'scatter-' + i, groupId: 'scatter-' + i, strokeWidth: finalStrokeWidth, onMouseLeave: _this2.handleScatterMouseLeave.bind(_this2), onMouseEnter: _this2.handleScatterMouseEnter.bind(_this2), points: _this2.getComposedData(data, xAxis, yAxis, zAxis) }); }, this); } }, { key: 'render', value: function render() { if (!_ReactUtils2.default.validateWidthHeight(this)) { return null; } var _props5 = this.props; var style = _props5.style; var children = _props5.children; var className = _props5.className; var width = _props5.width; var height = _props5.height; var items = _ReactUtils2.default.findAllByType(children, _Scatter2.default); var zAxis = this.getZAxis(items); var xAxis = this.getAxis('xAxis', items); var yAxis = this.getAxis('yAxis', items); var offset = this.getOffset(xAxis, yAxis); xAxis = this.getFormatAxis(xAxis, offset, 'xAxis'); yAxis = this.getFormatAxis(yAxis, offset, 'yAxis'); return _react2.default.createElement( 'div', { className: (0, _classnames2.default)('recharts-wrapper', className), style: _extends({ position: 'relative', cursor: 'default' }, style) }, _react2.default.createElement( _Surface2.default, { width: width, height: height }, this.renderGrid(xAxis, yAxis, offset), this.renderAxis(xAxis, 'recharts-x-axis'), this.renderAxis(yAxis, 'recharts-y-axis'), this.renderCursor(xAxis, yAxis, offset), this.renderItems(items, xAxis, yAxis, zAxis, offset) ), this.renderLegend(items), this.renderTooltip(items, xAxis, yAxis, zAxis, offset) ); } }]); return ScatterChart; }(_react.Component), _class.displayName = 'ScatterChart', _class.propTypes = { width: _react.PropTypes.number, height: _react.PropTypes.number, margin: _react.PropTypes.shape({ top: _react.PropTypes.number, right: _react.PropTypes.number, bottom: _react.PropTypes.number, left: _react.PropTypes.number }), title: _react.PropTypes.string, style: _react.PropTypes.object, children: _react.PropTypes.oneOfType([_react.PropTypes.arrayOf(_react.PropTypes.node), _react.PropTypes.node]), className: _react.PropTypes.string }, _class.defaultProps = { style: {}, margin: { top: 5, right: 5, bottom: 5, left: 5 } }, _temp2); exports.default = ScatterChart;