recharts
Version:
React charts
701 lines (581 loc) • 23 kB
JavaScript
'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;