0%

ECharts源码解析之批量更新(SetOption)

版本:V5.0.2

背景

使用EChartslazyUpdate模式多次执行setOption,仅对最终的option渲染一次。ECharts是和React一致自己单独实现了一套任务管理机制,还是基于setTimeoutPromise做的异步渲染?我们尝试从ECharts的源码中来一探究竟。

结论

代码挺好跟,但涉及到Even loop深入理解起来会相对困难。简单说明就是采用lazyUpdate更新图表的话,图表会在下一个 animation frame 中更新而在下一个animation frame之前执行的setOption会根据notMerge参数来判断是合并option还是采用最后一次option直接渲染。

定位实现代码

极好定位这里不再赘述

echarts.ts - apache/echarts

核心逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
 setOption<Opt extends ECBasicOption>(option: Opt, notMerge?: boolean | SetOptionOpts, lazyUpdate?: boolean): void {
if (__DEV__) {
assert(!this[IN_MAIN_PROCESS_KEY], '`setOption` should not be called during main process.');
}
if (this._disposed) {
disposedWarning(this.id);
return;
}

let silent;
let replaceMerge;
let transitionOpt: SetOptionTransitionOpt;
if (isObject(notMerge)) {
lazyUpdate = notMerge.lazyUpdate;
silent = notMerge.silent;
replaceMerge = notMerge.replaceMerge;
transitionOpt = notMerge.transition;
notMerge = notMerge.notMerge;
}

this[IN_MAIN_PROCESS_KEY] = true;

if (!this._model || notMerge) {
const optionManager = new OptionManager(this._api);
const theme = this._theme;
const ecModel = this._model = new GlobalModel();
ecModel.scheduler = this._scheduler;
ecModel.init(null, null, null, theme, this._locale, optionManager);
}

this._model.setOption(option as ECBasicOption, { replaceMerge }, optionPreprocessorFuncs);

const updateParams = {
seriesTransition: transitionOpt,
optionChanged: true
} as UpdateLifecycleParams;

// 批量更新、懒更新 标记
if (lazyUpdate) {
this[PENDING_UPDATE] = {
silent: silent,
updateParams: updateParams
};
// 这里把 主进程状态置为了 false
this[IN_MAIN_PROCESS_KEY] = false;
// this.getZr()直接返回了 zrender 对象 接下来看zrender的wakeUp方法(唤醒动画渲染)
this.getZr().wakeUp();
}
else {
prepare(this);

updateMethods.update.call(this, null, updateParams);


// 如果不是懒更新则直接flush图像
this._zr.flush();

this[PENDING_UPDATE] = null;
this[IN_MAIN_PROCESS_KEY] = false;

flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
}
}

wakeUp() {
// 这里启动了动画渲染
this.animation.start();
// Reset the frame count.
this._stillFrameAccum = 0;
}

/**
* Start animation.
*/
start() {
if (this._running) {
return;
}

this._time = new Date().getTime();
this._pausedTime = 0;

// 开启渲染动画
this._startLoop();
}

_startLoop() {
const self = this;

this._running = true;

// 渲染帧
function step() {
if (self._running) {

requestAnimationFrame(step);

!self._paused && self.update();
}
}

requestAnimationFrame(step);
}

参考 & 引用

react的setstate原理 (juejin.cn)

requestAnimationFrame 详解

requestIdleCallback和requestAnimationFrame详解

一帧剖析

requestAnimationFrame first tick (stackblitz.com)

requestAnimationFrame是一个宏任务么

Renderer Process Compositor Thread Compositor Tile Worker(s) Main Thread GPU Thread GPU Process Input event handlers requestAnim- ationFrame Parse HTML Recalc Styles Layout Update Layer Tree Paint Frame Start Composite Raster Scheduled Rasterize Frame End requestIdleCallback Layer tiles uploaded to GPU and composited. vsync and input data commit commit