d2recharts
Version:
data driven react components of echarts
257 lines (232 loc) • 7.74 kB
JavaScript
'use strict';
/**
* d2recharts module
* @module d2recharts
* @see module:index
* @see https://github.com/hustcc/echarts-for-react/blob/master/src/echarts-for-react.js
*/
const React = require('react');
const echarts = require('echarts');
const elementResizeEvent = require('element-resize-event');
const _ = require('lodash');
const update = require('immutability-helper');
const propTypes = require('./prop-types');
const util = require('../util/index');
class Recharts extends React.Component {
componentDidMount() {
this.timer = setTimeout(() => {
const me = this;
const theme = this.props.option['data-theme'] || 'default';
echarts.init(me.wrapper, theme);
const echartObj = me.renderEchartDom();
const props = me.props;
const onEvents = props.onEvents || [];
_.forIn(onEvents, (eventFunc, eventName) => {
// ignore the event config which not satisfy
if (_.isString(eventName) && _.isFunction(eventFunc)) {
// binding event
echartObj.on(eventName, (param) => {
eventFunc(param, me, echartObj);
});
}
});
this.silent = true;
echartObj.on('updated', () => {
if (this.silent) return;
});
// on chart ready
if (_.isFunction(props.onChartReady)) {
props.onChartReady(me, echartObj);
}
// on resize
elementResizeEvent(me.wrapper, () => {
echartObj.resize();
});
// on css width/height change
if (MutationObserver) {
// 监听样式容器高度变化,重置图表大小
if (this.observer && this.observer.disconnect) {
this.observer.disconnect();
}
this.observer = new MutationObserver((mutations) => {
mutations.forEach(item => {
if (item.type === 'attributes' && item.attributeName === 'style') {
try {
echartObj.resize();
} catch (e) {
// 屏蔽一个奇怪的 echarts 错误: Cannot set property '__alive' of undefined
}
}
});
});
this.observer.observe(me.wrapper, {
attributes: true,
attributeFilter : ['style']
});
}
if (window.navigator.userAgent.indexOf('Mobile') > -1) {
// 解决移动端滚动时,tootips 不消失的问题
document.body.onscroll = _.debounce(() => {
echartObj.dispatchAction({
type: 'hideTip'
})
}, 200);
}
}, 0);
}
componentWillReceiveProps(nextProps) {
const newTheme = nextProps.option['data-theme'];
const oldTheme = this.props.option['data-theme'];
if (newTheme && newTheme !== oldTheme) {
// https://github.com/ecomfe/echarts/issues/2539
echarts.dispose(this.wrapper);
echarts.init(this.wrapper, newTheme);
}
}
componentDidUpdate(prevProps) {
// update
const omitKeys = ['height', 'width'];
if (
JSON.stringify(_.omit(prevProps.option, omitKeys))
!==
JSON.stringify(_.omit(this.props.option, omitKeys))
) {
this.renderEchartDom();
}
}
componentWillUnmount() {
// remove
clearTimeout(this.timer);
echarts.dispose(this.wrapper);
if (this.observer && this.observer.disconnect) {
this.observer.disconnect();
}
}
extendXaxis(u = {}) {
// 需要感知宽度以后的设置
const echartObj = this.getEchartsInstance();
if (!echartObj) { return; }
const { option, schema } = this.props;
const opt = {};
let lineWidth = 78;
if (_.isEmpty(_.get(option, 'xAxis'))){
// 如果没有xAxis,则不做处理
// update不能为undefined设置值
return;
}
const dimension = _.get(option, 'data-columns.0');
const dimensionMeta = _.find(schema, ['name', dimension]);
const isString = _.get(dimensionMeta, 'type') === 'string';
if (_.get(option, 'xAxis.0.type') === 'value') {
// x轴是数值的时候
// 默认值为5,估算值
if ((echartObj.getWidth() - 100) / 60 < 5) {
_.set(option, 'xAxis.0.splitNumber', 4);
}
return;
}
if (isString) {
// 文本类型全部展示
opt.interval = 0;
const xData = _.get(option, 'xAxis.data') || [];
let isOverflowed = false;
if (xData.length) {
// 横着放存在覆盖情况,0.8是推测值
const maxWidth = 0.8 * echartObj.getWidth() / xData.length;
isOverflowed = xData.some(v => util.guessTextWidth(v.slice(0, 7)) > maxWidth);
}
if (isOverflowed) {
// 斜过来以后最多展示4个字
lineWidth = 12 * 4;
opt.rotate = 45;
// 补丁: 当有缩略轴的时候,需要grid向上移
if(option['data-showDataZoomInside']){
option.grid.bottom = 65;
}
}
}
_.set(u, 'option.xAxis.axisLabel', {
$merge: {
...opt,
formatter: v => {
const defaultWidth = util.guessTextWidth(v);
if (defaultWidth > lineWidth) {
const guessLen = Math.floor(lineWidth / 12);
let start = guessLen;
let first = v.toString().substr(0, start);
while (util.guessTextWidth(`${first}...`) <= lineWidth) {
first += v[start];
start++;
}
return `${first}...`;
}
return v;
},
},
});
}
extendDataZoom(u = {}) {
const echartObj = this.getEchartsInstance();
if (!echartObj) { return; }
const { option } = this.props;
if (!_.isArray(option.dataZoom)) {
return;
}
const h = echartObj.getHeight();
_.set(u, 'option.dataZoom', {
$set: _.map(option.dataZoom, opt => {
let newOpt = _.assign({}, opt);
if (opt.type !== 'inside') {
// 为什么 使用 echarts 高度来计算 top 不行?
// 因为拖拽 rerender 图表有一个 debounce 限制,如果拖动太快,下一次渲染的时候,echarts 的高度还是之前的老的高度,这样减去 20 之后,导致缩略找很高 / 很低。
// 所以正确的做法是:在 echarts rerender 之后,再触发一个render,专门用来做缩略轴高度。
// newOpt.top = h - 20;
// FIXME 这个 height 确实在 echarts 文档中没有,但是实际使用确实是可行的。
newOpt.height = 20;
newOpt.bottom = 0;
}
return newOpt;
}),
});
}
getEchartsInstance() {
return echarts.getInstanceByDom(this.wrapper);
}
renderEchartDom() {
const echartObj = this.getEchartsInstance();
if (!echartObj) { return; }
const u = {};
this.extendXaxis(u);
this.extendDataZoom(u);
const props = update(this.props, u);
// set the echart option
const option = util.pickExcept(props.option, ['width', 'height']);
this.silent = false;
echartObj.setOption(option, props.notMerge, props.lazyUpdate);
// on chart ready
if (props.option && _.isFunction(props.option.onChartRendered)) {
props.option.onChartRendered(this, echartObj);
}
return echartObj;
}
render() {
const me = this;
const props = me.props;
const style = _.assign({
width: props.option.width || props.width,
height: props.option.height || props.height,
}, props.style);
// for render
return (
<div ref={(wrapper) => me.wrapper = wrapper} className={props.className} style={style}/>
);
}
}
Recharts.propTypes = propTypes.recharts;
Recharts.defaultProps = {
lazyUpdate: true,
notMerge: true,
width: '100%',
height: '300px',
};
module.exports = Recharts;