bizcharts
Version:
bizcharts
249 lines (226 loc) • 6.98 kB
text/typescript
import uniqueId from '@antv/util/lib/unique-id';
import _each from '@antv/util/lib/each';
import _isFunction from '@antv/util/lib/is-function';
import _isArray from '@antv/util/lib/is-array';
import _isEqual from '@antv/util/lib/is-equal';
import { Chart as G2Chart } from '../../core';
import warn from 'warning';
import shallowEqual from '../../utils/shallowEqual';
import pickWithout from '../../utils/pickWithout';
import cloneDeep from '../../utils/cloneDeep';
import { REACT_PIVATE_PROPS } from '../../utils/constant';
import { VIEW_LIFE_CIRCLE } from '@antv/g2/lib/constant';
import EventEmitter from '@antv/event-emitter';
import { IEvent } from '../../interface';
import { pickEventName } from './events';
const processData = (data) => {
if (data && data.rows) {
return data.rows;
}
return data;
}
class ChartHelper extends EventEmitter {
public chart: G2Chart;
public config: Record<string, any> = {};
private isNewInstance: boolean;
public extendGroup: any;
public key: string;
createInstance(config) {
this.chart = new G2Chart({ ...config });
this.key = uniqueId('bx-chart');
this.chart.emit('initialed');
this.isNewInstance = true; // 更新了实例的标记
this.extendGroup = {
isChartCanvas: true,
chart: this.chart,
};
}
render() {
if (!this.chart) {
return;
}
try {
// 普通error 只能兜住react render周期里的error。 chart render周期的error 要单独处理
if (this.isNewInstance) {
this.chart.render();
this.onGetG2Instance();
// @ts-ignore
this.chart.unbindAutoFit(); // 不使用g2的监听
this.isNewInstance = false;
// @ts-ignore
} else if(this.chart.forceReRender) {
// forceReRender 填部分g2更新不干净的坑
this.chart.render();
} else {
this.chart.render(true);
}
// 处理elements状态
this.chart.emit('processElemens');
} catch(e) {
this.emit('renderError', e);
this.destory();
if(console) {
console.error(e?.stack)
}
}
}
private onGetG2Instance() {
// 当且仅当 isNewInstance 的时候执行。
if (_isFunction(this.config.onGetG2Instance)) {
this.config.onGetG2Instance(this.chart);
}
}
shouldReCreateInstance(newConfig) {
// 如果上一个实例数据为空则直接销毁重建,以免影响动画
if (!this.chart || newConfig.forceUpdate) {
return true;
}
const { data:preData, ...preOptions} = this.config;
const { data, ...options } = newConfig;
if (_isArray(this.config.data)
&& preData.length === 0
&& _isArray(data)
&& data.length !== 0 ) {
return true;
}
// scale 切换不需要重建实例
const unCompareProps = [...REACT_PIVATE_PROPS, 'scale', 'width', 'height', 'container', '_container', '_interactions', 'placeholder', /^on/, /^\_on/];
if (!_isEqual(pickWithout(preOptions, [...unCompareProps]),
pickWithout(options, [...unCompareProps]))) {
return true;
}
return false;
}
update(props) {
const newConfig = cloneDeep(this.adapterOptions(props));
if (this.shouldReCreateInstance(newConfig)) {
this.destory();
this.createInstance(newConfig);
}
// 重置
if (newConfig.pure) {
// 纯画布 关闭
this.chart.axis(false);
this.chart.tooltip(false);
this.chart.legend(false);
// @ts-ignore
this.chart.isPure = true;
}
// 事件
const events = pickEventName(this.config);
const newEvents = pickEventName(newConfig);
// 配置
const { data, interactions, ...options } = newConfig;
const { data: preData, interactions: preInteractions = [] } = this.config;
if (!this.isNewInstance) {
// 取消事件绑定
events.forEach(ev => {
this.chart.off(ev[1], this.config[`_${ev[0]}`])
});
}
/** 更新 */
// 绑定事件
newEvents.forEach(evName => {
newConfig[`_${evName[0]}`] = (ev: IEvent) => {
// 输入chart实例方便用户使用
newConfig[evName[0]](ev, this.chart);
}
this.chart.on(evName[1], newConfig[`_${evName[0]}`])
});
// 数据 更新
if(_isArray(preData) && preData.length) {
// 数据只做2级浅比较
// fixme: 做4级比较
let isEqual = true;
if (newConfig.notCompareData) {
// 手动关闭对比
isEqual = false;
}
if (preData.length !== data.length) {
// 长度不相等
isEqual = false;
} else {
preData.forEach((element, index) => {
if (!shallowEqual(element, data[index])) {
isEqual = false;
}
});
}
if (!isEqual) {
// @ts-ignore
this.chart.isDataChanged = true;
this.chart.emit(VIEW_LIFE_CIRCLE.BEFORE_CHANGE_DATA);
// 1. 保存数据
this.chart.data(data);
// 2. 最后再渲染
// 3. 遍历子 view 进行 change data
const views = this.chart.views;
for (let i = 0, len = views.length; i < len; i++) {
const view = views[i];
// 子 view 有自己的数据, 会在执行view的配置时会覆盖
view.changeData(data);
}
this.chart.emit(VIEW_LIFE_CIRCLE.AFTER_CHANGE_DATA);
}
} else {
this.chart.data(data);
}
// 比例尺
this.chart.scale(options.scale);
// 动画
if (options.animate === false) {
this.chart.animate(false);
} else {
this.chart.animate(true);
}
// 交互 interactions
preInteractions.forEach(interact => {
// 这里不做对比,无意义,都是要销毁重建,不如直接全量销毁
this.chart.removeInteraction(interact);
});
interactions.forEach(interact => {
this.chart.interaction(interact);
});
// filter
_each(this.config.filter, (it, index) => {
// 销毁
if (_isArray(it)) {
this.chart.filter(it[0], null);
} else {
this.chart.filter(index, null);
}
});
_each(newConfig.filter, (it, index) => {
if (_isArray(it)) {
this.chart.filter(it[0], it[1]);
} else {
this.chart.filter(index, it);
}
})
// 主题
this.chart.theme(newConfig.theme);
// 缓存处理后的配置
this.config = newConfig;
}
adapterOptions({data, ...others}) {
// 剔除 React 自身的属性
const options = pickWithout(others, [...REACT_PIVATE_PROPS]);
if (options.forceFit) {
warn(false, 'forceFit 已废弃,请使用`autoFit`替代');
}
options.data = processData(data) || [];
return options;
}
destory() {
if (!this.chart) {
return;
}
this.extendGroup = null;
let { chart } = this;
chart.destroy();
chart = null;
this.chart = null;
this.config = {};
}
}
export default ChartHelper;