0%

ECharts源码解析之图层(CanvasLayer)

版本:V5.0.2

背景

ECharts在大规模数据渲染下采用了多层画布技术,对于背景元素、数据元素、交互提示类元素做分层处理对于样式稳定的元素单独绘制,不再重绘。用于性能优化。

ECharts如何去实现多层画布的?我们尝试从ECharts的源码中来一探究竟。

image-20211018022203998

image-20211018022308673

image-20211018022338439

image-20211018022407968

结论

在数据量小时,ECharts仍然采用单图层渲染。在数据量大时ECharts采用了标记增量数据所表示的图形的方式,来区分,数据元素、数据之前的元素、数据之后的元素。并由此分为三层canvas来渲染。

定位实现代码

过程不再赘述,这里只是将具体文件列出。

canvas.tszrendercanvas渲染器。

Painter.ts:渲染器的输出装置。

Layer.ts:图层类。

核心逻辑

Painter.ts:渲染器的输出装置。这里创建了各个层并在这里确定了各个层有哪些元素。

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
105
106
107
108
109
110
111
112
113
114
115
_updateLayerStatus(list: Displayable[]/* 待渲染的元素集合 */) {

this.eachBuiltinLayer(function (layer, z) {
layer.__dirty = layer.__used = false;
});

function updatePrevLayer(idx: number) {
if (prevLayer) {
if (prevLayer.__endIndex !== idx) {
prevLayer.__dirty = true;
}
// 这里确定了上一个图层内最后一个元素的下标。
prevLayer.__endIndex = idx;
}
}

if (this._singleCanvas) {
for (let i = 1; i < list.length; i++) {
const el = list[i];
if (el.zlevel !== list[i - 1].zlevel || el.incremental) {
this._needsManuallyCompositing = true;
break;
}
}
}

let prevLayer: Layer = null;
let incrementalLayerCount = 0;
let prevZlevel;
let i;

// list 即为要渲染的element(图形元素)列表。
for (i = 0; i < list.length; i++) {
const el = list[i];
// el 内部其实并未维护zlevel信息(均为0)
// 但维护了 incremental 信息
const zlevel = el.zlevel;
let layer;

if (prevZlevel !== zlevel) {
prevZlevel = zlevel;
incrementalLayerCount = 0;
}

// TODO Not use magic number on zlevel.
// Each layer with increment element can be separated to 3 layers.
// (Other Element drawn after incremental element)
// -----------------zlevel + EL_AFTER_INCREMENTAL_INC--------------------
// (Incremental element)
// ----------------------zlevel + INCREMENTAL_INC------------------------
// (Element drawn before incremental element)
// --------------------------------zlevel--------------------------------

// this.getLayer 会根据ID返回图层,如果无该图层则会创建图层并返回
// 如果el.incremental为true则创建新的图层
if (el.incremental) {
layer = this.getLayer(zlevel + INCREMENTAL_INC /* 0.001 */, this._needsManuallyCompositing);
layer.incremental = true;
incrementalLayerCount = 1;
}
// incremental 元素之后的元素 将会在下一个图层创建。
else {
layer = this.getLayer(
zlevel + (incrementalLayerCount > 0 ? EL_AFTER_INCREMENTAL_INC /* 0.01 */ : 0),
this._needsManuallyCompositing
);
}
// 以上将图层分为了三层,el.incremental的图层以及 el.incremental之前和之后的图层
// el.incremental图层即增量数据图层
if (!layer.__builtin__) {
util.logError('ZLevel ' + zlevel + ' has been used by unkown layer ' + layer.id);
}

if (layer !== prevLayer) {
layer.__used = true;
if (layer.__startIndex !== i) {
layer.__dirty = true;
}
// 这里确定了当前图层一个元素的下标。
// 结合 updatePrevLayer(i) 更新上一个图层,
// 确定了所有图层要渲染[待渲染的元素集合]中的哪一部分。
layer.__startIndex = i;
if (!layer.incremental) {
layer.__drawIndex = i;
}
else {
// Mark layer draw index needs to update.
layer.__drawIndex = -1;
}
updatePrevLayer(i);
prevLayer = layer;
}
if ((el.__dirty & REDRAW_BIT) && !el.__inHover) { // Ignore dirty elements in hover layer.
layer.__dirty = true;
if (layer.incremental && layer.__drawIndex < 0) {
// Start draw from the first dirty element.
layer.__drawIndex = i;
}
}
}

updatePrevLayer(i);

this.eachBuiltinLayer(function (layer, z) {
// Used in last frame but not in this frame. Needs clear
if (!layer.__used && layer.getElementCount() > 0) {
layer.__dirty = true;
layer.__startIndex = layer.__endIndex = layer.__drawIndex = 0;
}
// For incremental layer. In case start index changed and no elements are dirty.
if (layer.__dirty && layer.__drawIndex < 0) {
layer.__drawIndex = layer.__startIndex;
}
});
}

[LargeSymbolDraw.ts](LargeSymbolDraw.ts - apache/echarts - GitHub1s):在数据量大时,采用了IncrementalDisplayable类来区分图层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
incrementalPrepareUpdate(data: SeriesData) {
this.group.removeAll();

this._clearIncremental();
// Only use incremental displayables when data amount is larger than 2 million.
// PENDING Incremental data?
// 在数据量大时,采用了IncrementalDisplayable类来区分图层。
if (data.count() > 2e6) {
if (!this._incremental) {
this._incremental = new IncrementalDisplayable({
silent: true
});
}
this.group.add(this._incremental);
}
else {
this._incremental = null;
}
}

IncrementalDisplayable.ts:确定了元素的incremental = true

1
2
3
4
5
6
7
8
9
10
11
12
export default class IncrementalDisplayable extends Displayble {

notClear: boolean = true

incremental = true

private _displayables: Displayble[] = []
private _temporaryDisplayables: Displayble[] = []

...

}

参考 & 引用

IncrementalDisplayable.ts - ecomfe/zrender - GitHub1s

LargeSymbolDraw.ts - apache/echarts - GitHub1s

installCanvasRenderer.ts - apache/echarts - GitHub1s

宿爽 - 16毫秒的挑战_图表库渲染优化

Examples - Apache ECharts