echarts源码解读《四》:echarts源码之Component分析

在本篇博文中,我们将继续Component源码解读,我将主要介绍各Component的渲染过程以及其使用的zrender graphic。希望和小伙伴们一起进步呀!!加油!!

前言

echarts对Component的定义呢,可以认为是除Series外的其他配置项,在这篇博文中我们将探讨datazoom(区域缩放)、visualMap(视图映射)、tooltip(提示框组件)、toolbox(工具栏)、brush(区域选择组件)、timeline(时间轴)、graphic(原生图形组件)、dataset(数据集)、marker(包括markPoint(图标标注)、markLine(图表标线)、markArea(图表标域))这些Component。

用户通过传递option对象来设置相应的Component。

区域缩放 DataZoom

dataZoom分为inside和slider两种类型

公共文件

DataZoomModel

DataZoomModel通过extendComponentModel扩展自Component Model,重写了defaultOption属性以及init、mergeOption等方法。

DataZoomView

DataZoomView通过extend扩展自Component View,重写了render方法并定义了getTargetCoordInfo方法。

DataZoomAction

DataZoomAction中注册了dataZoom action

Slider

SliderZoomModel

SliderZoomModel通过extend扩展自DataZoomModel,重写了defaultOption属性

SliderZoomView

SliderZoomView通过extend扩展自DataZoomView,重写了render、remove、dispose等方法,主要实现代码如下:

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
render: function (dataZoomModel, ecModel, api, payload) {
// 调用DataZoomView中的render方法
SliderZoomView.superApply(this, 'render', arguments);
...
if (!payload || payload.type !== 'dataZoom' || payload.from !== this.uid) {
// 绘制
this._buildView();
}
// 更新
this._updateView();
}
_buildView: function () {
var thisGroup = this.group;

thisGroup.removeAll();

this._resetLocation();
this._resetInterval();

var barGroup = this._displayables.barGroup = new graphic.Group();

// 绘制背景,通过graphic.Rect绘制背景,包括可视层以及Click panel
this._renderBackground();
// 绘制handle,通过graphic.Rect绘制选择的区域,并绑定拖拽相关以及mouseover、mouseout等事件处理器
// 通过graphic.Rect绘制Frame border
// 通过graphic.createIcon创建handler 图标,并绑定拖拽相关以及mouseover、mouseout等事件处理器
// 通过graphic.Text绘制handle中的label数据
this._renderHandle();
// 通过graphic.Polyline及graphic.Polygon绘制出datazoom下的数据缩略图
this._renderDataShadow();

thisGroup.add(barGroup);

this._positionGroup();
}

Inside

InsideZoomModel

InsideZoomModel通过extend扩展自DataZoomModel,重写了defaultOption属性。

InsideZoomView

InsideZoomView通过extend扩展自DataZoomView,重写了init、render、dispose等方法,主要实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
render: function (dataZoomModel, ecModel, api, payload) {
// 调用DataZoomView中的render方法
InsideZoomView.superApply(this, 'render', arguments);
this._range = dataZoomModel.getPercentRange();
// Reset controllers.
zrUtil.each(this.getTargetCoordInfo(), function (coordInfoList, coordSysName) {
...
zrUtil.each(coordInfoList, function (coordInfo) {
var coordModel = coordInfo.model;
var getRange = {};
// 绑定pan、zoom、scrollMove事件处理器,roamHandlers中定义了相关类型的事件处理函数
zrUtil.each(['pan', 'zoom', 'scrollMove'], function (eventName) {
getRange[eventName] = bind(roamHandlers[eventName], this, coordInfo, coordSysName);
}, this);
...
}, this);

}, this);
}

视图映射 VisualMap

VisualMap分为continuous连续型及piecewise分段型两个类型

公共文件

VisualMapAction

VisualMapAction中注册了selectDataRange action

VisualMapModel

VisualMapModel通过extendComponentModel方法扩展自Component Model,重写了defaultOption属性以及init、optionUpdated等方法,并定义了resetVisual、formatValueText等方法。

VisualMapView

VisualMapView通过extendComponentView方法扩展自Component View,重写了init、render等方法,定义了renderBackground方法,主要代码为:

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
render: function (visualMapModel, ecModel, api, payload) {
...
// 调用子类doRender方法
this.doRender.apply(this, arguments);
}
renderBackground: function (group) {
var visualMapModel = this.visualMapModel;
var padding = formatUtil.normalizeCssArray(visualMapModel.get('padding') || 0);
var rect = group.getBoundingRect();
// 通过graphic.Rect渲染visualMap背景
group.add(new graphic.Rect({
z2: -1, // Lay background rect on the lowest layer.
silent: true,
shape: {
x: rect.x - padding[3],
y: rect.y - padding[0],
width: rect.width + padding[3] + padding[1],
height: rect.height + padding[0] + padding[2]
},
style: {
fill: visualMapModel.get('backgroundColor'),
stroke: visualMapModel.get('borderColor'),
lineWidth: visualMapModel.get('borderWidth')
}
}));
}

Continuous

ContinuousModel

ContinuousModel通过extend方法扩展自VisualMapModel,重写了defaultOption属性以及optionUpdated、resetItemSize等方法,并定义了completeVisualOption、setSelected、getSelected、getValueState等方法。

ContinuousView

ContinuousView通过extend方法扩展自VisualMapView,重写了init方法并定义了doRender方法,主要代码如下:

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
doRender: function (visualMapModel, ecModel, api, payload) {
if (!payload || payload.type !== 'selectDataRange' || payload.from !== this.uid) {
this._buildView();
}
}
_buildView: function () {
this.group.removeAll();

var visualMapModel = this.visualMapModel;
var thisGroup = this.group;

this._orient = visualMapModel.get('orient');
this._useHandle = visualMapModel.get('calculable');

this._resetInterval();
// 绘制visualMap映射组件
// 1)通过graphic.Polygon绘制inRange及outOfRange形状,并为inRange形状绑定drag相关事件处理器
// 2)若userHandle为true,则通过graphic.Polygon绘制handler,graphic.Text绘制handler label
// 并为handler绑定drag相关时间处理器
// 3)通过graphic.Polygon绘制指示器,graphic.Text绘制指示器 label
this._renderBar(thisGroup);

var dataRangeText = visualMapModel.get('text');
// 通过graphic.Text渲染visualMap首与尾处的label
this._renderEndsText(thisGroup, dataRangeText, 0);
this._renderEndsText(thisGroup, dataRangeText, 1);

this._updateView(true);
// 调用VisualMapView中的renderBackground方法
this.renderBackground(thisGroup);
// 通过graphic.LinearGradient创建线性渐变
this._updateView();
// 绑定mouseover及mouseout相关的事件处理器
// mouseover:showIndicator
// mouseout:hideIndicator
this._enableHoverLinkToSeries();
this._enableHoverLinkFromSeries();

this.positionGroup(thisGroup);
}

Piecewise

PiecewiseModel

PiecewiseModel通过extend方法扩展自VisualMapModel,重写了defaultOption属性以及optionUpdated等方法,并定义了getPieceList、getValueState、findTargetDataIndices等方法

PiecewiseView

PiecewiseView通过extend方法扩展自VisualMapView,定义了doRender方法,主要代码如下:

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
doRender: function () {
...
// 通过graphic.Text绘制
endsText && this._renderEndsText(
thisGroup, endsText[0], itemSize, showLabel, itemAlign
);

// viewPieceList数据处理的实现在PiecewiseModel中resetMethods
// 遍历viewPieceList调用renderItem进行片段visual的绘制
zrUtil.each(viewData.viewPieceList, renderItem, this);

endsText && this._renderEndsText(
thisGroup, endsText[1], itemSize, showLabel, itemAlign
);

layout.box(
visualMapModel.get('orient'), thisGroup, visualMapModel.get('itemGap')
);
// 调用VisualMapView中的renderBackground方法
this.renderBackground(thisGroup);
this.positionGroup(thisGroup);

function renderItem(item) {
var piece = item.piece;

var itemGroup = new graphic.Group();
// 绑定onclick时的事件处理器
itemGroup.onclick = zrUtil.bind(this._onItemClick, this, piece);
// 绑定mouseover、mouseout相关的事件处理器
this._enableHoverLink(itemGroup, item.indexInModelPieceList);
var representValue = visualMapModel.getRepresentValue(piece);
// 通过createSymbol方法创建symbol
this._createItemSymbol(
itemGroup, representValue, [0, 0, itemSize[0], itemSize[1]]
);
if (showLabel) {
var visualState = this.visualMapModel.getValueState(representValue);
// 渲染文本
itemGroup.add(new graphic.Text({
style: {
x: itemAlign === 'right' ? -textGap : itemSize[0] + textGap,
y: itemSize[1] / 2,
text: piece.text,
textVerticalAlign: 'middle',
textAlign: itemAlign,
textFont: textFont,
textFill: textFill,
opacity: visualState === 'outOfRange' ? 0.5 : 1
}
}));
}
thisGroup.add(itemGroup);
}
}

提示框 Tooltip

Tooltip.js

Tooltip.js中注册了showTip及hideTip action,trigger处理器调用TooltipView中定义的manuallyShowTip及manuallyHideTip方法。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
echarts.registerAction(
{
type: 'showTip',
event: 'showTip',
update: 'tooltip:manuallyShowTip'
},
function () {}
);

echarts.registerAction(
{
type: 'hideTip',
event: 'hideTip',
update: 'tooltip:manuallyHideTip'
},
function () {}
);

TooltipModel

TooltipModel通过extendComponentModel扩展自Component Model,重写了defaultOption属性。

TooltipView

TooltipView通过extendComponentView扩展自Component View,重写了init、render等方法,并定义了manuallyShowTip、manuallyHideTip等方法,主要代码如下:

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
init: function (ecModel, api) {
...
var tooltipModel = ecModel.getComponent('tooltip');
var renderMode = tooltipModel.get('renderMode');
this._renderMode = getTooltipRenderMode(renderMode);

var tooltipContent;
// 设置TooltipContent内容
// TooltipContent 中绑定了mouseenter、mousemove、mouseleave等事件处理器,
// TooltipRichContent 中绑定了mouseover、mouseout等事件处理器
// TooltipContent&TooltipRichContent 定义了setContent、show、hide、update、getSize等方法
if (this._renderMode === 'html') {
tooltipContent = new TooltipContent(api.getDom(), api);
this._newLine = '<br/>';
}
else {
tooltipContent = new TooltipRichContent(api);
this._newLine = '\n';
}

this._tooltipContent = tooltipContent;
}
render: function (tooltipModel, ecModel, api) {
...
var tooltipContent = this._tooltipContent;
// 调用tooltipContent的update方法
tooltipContent.update();
tooltipContent.setEnterable(tooltipModel.get('enterable'));
// 初始化tooltip触发监听器 “triggerOn”
this._initGlobalListener();
// 调用manuallyShowTip展示tooltip
// manuallyShowTip中调用了tryShow方法
this._keepShow();
}
_tryShow: function (e, dispatchAction) {
...
if (dataByCoordSys && dataByCoordSys.length) {
// 展示axis的tooltip信息
// 调用formatTooltip方法渲染Tooltip中的展示内容,包括圆点和数据信息
this._showAxisTooltip(dataByCoordSys, e);
}
else if (el && el.dataIndex != null) {
// 展示series tooltip信息
// 调用formatTooltip方法渲染Tooltip中的展示内容,包括圆点和数据信息
this._lastDataByCoordSys = null;
this._showSeriesItemTooltip(e, el, dispatchAction);
}
else if (el && el.tooltip) {
// 展示Component下的tooltip信息
this._lastDataByCoordSys = null;
this._showComponentItemTooltip(e, el, dispatchAction);
}
else {
this._lastDataByCoordSys = null;
this._hide(dispatchAction);
}
}

工具栏 Toolbox

ToolboxModel

ToolboxModel通过extendComponentModel方法扩展自Component Model,重写了defaultOption属性以及optionUpdated方法(merge feature下的option)

ToolboxView

ToolboxView通过extendComponentView扩展自Component View,重写了render、remove以及dispose等方法,并定义了updateView方法(调用feature下的updateView)。主要代码如下:

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
render: function (toolboxModel, ecModel, api, payload) {
var group = this.group;
group.removeAll();

if (!toolboxModel.get('show')) {
return;
}

var itemSize = +toolboxModel.get('itemSize');
var featureOpts = toolboxModel.get('feature') || {};
var features = this._features || (this._features = {});

var featureNames = [];
zrUtil.each(featureOpts, function (opt, name) {
featureNames.push(name);
});

(new DataDiffer(this._featureNames || [], featureNames))
.add(processFeature)
.update(processFeature)
.remove(zrUtil.curry(processFeature, null))
.execute();
this._featureNames = featureNames;

function processFeature(newIndex, oldIndex) {
var featureName = featureNames[newIndex];
var oldName = featureNames[oldIndex];
var featureOpt = featureOpts[featureName];
var featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel);
var feature;

if (featureName && !oldName) { // Create
if (isUserFeatureName(featureName)) {
feature = {
model: featureModel,
onclick: featureModel.option.onclick,
featureName: featureName
};
}
else {
var Feature = featureManager.get(featureName);
if (!Feature) {
return;
}
feature = new Feature(featureModel, ecModel, api);
}
features[featureName] = feature;
}
else {
feature = features[oldName];
// If feature does not exsit.
if (!feature) {
return;
}
feature.model = featureModel;
feature.ecModel = ecModel;
feature.api = api;
}
...
// 通过graphic.createIcon创建工具图标,绑定mouseover、mouseout及click事件处理器
createIconPaths(featureModel, feature, featureName);

featureModel.setIconStatus = function (iconName, status) {
var option = this.option;
var iconPaths = this.iconPaths;
option.iconStatus = option.iconStatus || {};
option.iconStatus[iconName] = status;
// FIXME
iconPaths[iconName] && iconPaths[iconName].trigger(status);
};

if (feature.render) {
// 调用相应feature的render方法
feature.render(featureModel, ecModel, api, payload);
}
}
listComponentHelper.layout(group, toolboxModel, api);
// 渲染背景
group.add(listComponentHelper.makeBackground(group.getBoundingRect(), toolboxModel));

// 处理icon title的位置,避免title溢出屏幕
group.eachChild(function (icon) {
...
});
}

Feature

featureManager中定义了注册以及获取feature的方法,实现代码如下:

1
2
3
4
5
6
export function register(name, ctor) {
features[name] = ctor;
}
export function get(name) {
return features[name];
}

SaveAsImage

SaveAsImage(保存为图片)定义了defaultOption属性以及onclick事件处理器,主要代码如下:

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
var proto = SaveAsImage.prototype;

proto.onclick = function (ecModel, api) {
var model = this.model;
var title = model.get('name') || ecModel.get('title.0.text') || 'echarts';
var $a = document.createElement('a');
var type = model.get('type', true) || 'png';
$a.download = title + '.' + type;
$a.target = '_blank';
var url = api.getConnectedDataURL({
type: type,
backgroundColor: model.get('backgroundColor', true)
|| ecModel.get('backgroundColor') || '#fff',
excludeComponents: model.get('excludeComponents'),
pixelRatio: model.get('pixelRatio')
});
$a.href = url;
// Chrome and Firefox
if (typeof MouseEvent === 'function' && !env.browser.ie && !env.browser.edge) {
var evt = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: false
});
$a.dispatchEvent(evt);
}
// IE浏览器
else {
if (window.navigator.msSaveOrOpenBlob) {
var bstr = atob(url.split(',')[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
var blob = new Blob([u8arr]);
window.navigator.msSaveOrOpenBlob(blob, title + '.' + type);
}
else {
var lang = model.get('lang');
var html = ''
+ '<body style="margin:0;">'
+ '<img src="' + url + '" style="max-width:100%;" title="' + ((lang && lang[0]) || '') + '" />'
+ '</body>';
var tab = window.open();
tab.document.write(html);
}
}
};

MagicType

MagicType(动态类型切换)定义了defaultOption属性,以及getIcons、onclick方法,并注册了changeMagicType action,主要代码如下:

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
var proto = MagicType.prototype;

proto.getIcons = function () {
var model = this.model;
var availableIcons = model.get('icon');
var icons = {};
zrUtil.each(model.get('type'), function (type) {
if (availableIcons[type]) {
icons[type] = availableIcons[type];
}
});
return icons;
};

// series option生成器
var seriesOptGenreator = {
'line': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'bar') {
return zrUtil.merge({
id: seriesId,
type: 'line',
// Preserve data related option
data: seriesModel.get('data'),
stack: seriesModel.get('stack'),
markPoint: seriesModel.get('markPoint'),
markLine: seriesModel.get('markLine')
}, model.get('option.line') || {}, true);
}
},
'bar': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'line') {
return zrUtil.merge({
id: seriesId,
type: 'bar',
// Preserve data related option
data: seriesModel.get('data'),
stack: seriesModel.get('stack'),
markPoint: seriesModel.get('markPoint'),
markLine: seriesModel.get('markLine')
}, model.get('option.bar') || {}, true);
}
},
'stack': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'line' || seriesType === 'bar') {
return zrUtil.merge({
id: seriesId,
stack: '__ec_magicType_stack__'
}, model.get('option.stack') || {}, true);
}
},
'tiled': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'line' || seriesType === 'bar') {
return zrUtil.merge({
id: seriesId,
stack: ''
}, model.get('option.tiled') || {}, true);
}
}
};

var radioTypes = [
['line', 'bar'],
['stack', 'tiled']
];

proto.onclick = function (ecModel, api, type) {
var model = this.model;
var seriesIndex = model.get('seriesIndex.' + type);
// Not supported magicType
if (!seriesOptGenreator[type]) {
return;
}
var newOption = {
series: []
};
// 生成新类型的series
var generateNewSeriesTypes = function (seriesModel) {
var seriesType = seriesModel.subType;
var seriesId = seriesModel.id;
var newSeriesOpt = seriesOptGenreator[type](
seriesType, seriesId, seriesModel, model
);
if (newSeriesOpt) {
// PENDING If merge original option?
zrUtil.defaults(newSeriesOpt, seriesModel.option);
newOption.series.push(newSeriesOpt);
}
// Modify boundaryGap
var coordSys = seriesModel.coordinateSystem;
if (coordSys && coordSys.type === 'cartesian2d' && (type === 'line' || type === 'bar')) {
var categoryAxis = coordSys.getAxesByScale('ordinal')[0];
if (categoryAxis) {
var axisDim = categoryAxis.dim;
var axisType = axisDim + 'Axis';
var axisModel = ecModel.queryComponents({
mainType: axisType,
index: seriesModel.get(name + 'Index'),
id: seriesModel.get(name + 'Id')
})[0];
var axisIndex = axisModel.componentIndex;

newOption[axisType] = newOption[axisType] || [];
for (var i = 0; i <= axisIndex; i++) {
newOption[axisType][axisIndex] = newOption[axisType][axisIndex] || {};
}
newOption[axisType][axisIndex].boundaryGap = type === 'bar';
}
}
};

zrUtil.each(radioTypes, function (radio) {
if (zrUtil.indexOf(radio, type) >= 0) {
zrUtil.each(radio, function (item) {
model.setIconStatus(item, 'normal');
});
}
});

// 设置icon的状态
model.setIconStatus(type, 'emphasis');

// 为每个series生成新option
ecModel.eachComponent(
{
mainType: 'series',
query: seriesIndex == null ? null : {
seriesIndex: seriesIndex
}
}, generateNewSeriesTypes
);
api.dispatchAction({
type: 'changeMagicType',
currentType: type,
newOption: newOption
});
};

echarts.registerAction({
type: 'changeMagicType',
event: 'magicTypeChanged',
update: 'prepareAndUpdate'
}, function (payload, ecModel) {
// 合并option
ecModel.mergeOption(payload.newOption);
});

DataView

DataView(数据视图工具)定义了defaultOption属性及onclick、remove、dispose方法,并注册了changeDataView action,主要代码如下:

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
DataView.prototype.onclick = function (ecModel, api) {
var container = api.getDom();
var model = this.model;
if (this._dom) {
container.removeChild(this._dom);
}
var root = document.createElement('div');
root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;';
root.style.backgroundColor = model.get('backgroundColor') || '#fff';

// 创建element
var header = document.createElement('h4');
var lang = model.get('lang') || [];
header.innerHTML = lang[0] || model.get('title');
header.style.cssText = 'margin: 10px 20px;';
header.style.color = model.get('textColor');

var viewMain = document.createElement('div');
var textarea = document.createElement('textarea');
viewMain.style.cssText = 'display:block;width:100%;overflow:auto;';

var optionToContent = model.get('optionToContent');
var contentToOption = model.get('contentToOption');
// 获取数据
var result = getContentFromModel(ecModel);
if (typeof optionToContent === 'function') {
var htmlOrDom = optionToContent(api.getOption());
if (typeof htmlOrDom === 'string') {
viewMain.innerHTML = htmlOrDom;
}
else if (zrUtil.isDom(htmlOrDom)) {
viewMain.appendChild(htmlOrDom);
}
}
else {
// 默认使用textarea
viewMain.appendChild(textarea);
textarea.readOnly = model.get('readOnly');
textarea.style.cssText = 'width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;';
textarea.style.color = model.get('textColor');
textarea.style.borderColor = model.get('textareaBorderColor');
textarea.style.backgroundColor = model.get('textareaColor');
textarea.value = result.value;
}

var blockMetaList = result.meta;

// 创建按钮容器
var buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;';

var buttonStyle = 'float:right;margin-right:20px;border:none;'
+ 'cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px';
// 创建关闭以及刷新按钮
var closeButton = document.createElement('div');
var refreshButton = document.createElement('div');

buttonStyle += ';background-color:' + model.get('buttonColor');
buttonStyle += ';color:' + model.get('buttonTextColor');

var self = this;
...
// 添加click事件处理器
eventTool.addEventListener(closeButton, 'click', close);
eventTool.addEventListener(refreshButton, 'click', function () {
var newOption;
try {
if (typeof contentToOption === 'function') {
newOption = contentToOption(viewMain, api.getOption());
}
else {
newOption = parseContents(textarea.value, blockMetaList);
}
}
catch (e) {
close();
throw new Error('Data view format error ' + e);
}
if (newOption) {
api.dispatchAction({
type: 'changeDataView',
newOption: newOption
});
}

close();
});

closeButton.innerHTML = lang[1];
refreshButton.innerHTML = lang[2];
refreshButton.style.cssText = buttonStyle;
closeButton.style.cssText = buttonStyle;

!model.get('readOnly') && buttonContainer.appendChild(refreshButton);
buttonContainer.appendChild(closeButton);

// 使用tab键进行数据分割
eventTool.addEventListener(textarea, 'keydown', function (e) {
if ((e.keyCode || e.which) === 9) {
var val = this.value;
var start = this.selectionStart;
var end = this.selectionEnd;

// set textarea value to: text before caret + tab + text after caret
this.value = val.substring(0, start) + ITEM_SPLITER + val.substring(end);

// put caret at right position again
this.selectionStart = this.selectionEnd = start + 1;

// prevent the focus lose
eventTool.stop(e);
}
});

root.appendChild(header);
root.appendChild(viewMain);
root.appendChild(buttonContainer);

viewMain.style.height = (container.clientHeight - 80) + 'px';

container.appendChild(root);
this._dom = root;
};

DataView.prototype.remove = function (ecModel, api) {
this._dom && api.getDom().removeChild(this._dom);
};

DataView.prototype.dispose = function (ecModel, api) {
this.remove(ecModel, api);
};

DataZoom

DataZoom(数据区域缩放)设置defaultOption属性,定义了render、onclick、remove以及dispose等方法。主要代码如下:

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
proto.render = function (featureModel, ecModel, api, payload) {
this.model = featureModel;
this.ecModel = ecModel;
this.api = api;

// 使用BrushTargetManager管理区域选择
updateZoomBtnStatus(featureModel, ecModel, this, payload, api);
// 渲染还原按钮的iconStatus
updateBackBtnStatus(featureModel, ecModel);
};

proto.onclick = function (ecModel, api, type) {
handlers[type].call(this);
};
var handlers = {

zoom: function () {
var nextActive = !this._isZoomActive;

this.api.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: nextActive
});
},

back: function () {
this._dispatchZoomAction(history.pop(this.ecModel));
}
};

Restore

Restore(配置项还原)定义了defaultOption属性以及onclick方法,并注册了restore action,主要代码如下:

1
2
3
4
5
6
7
8
9
10
var proto = Restore.prototype;

proto.onclick = function (ecModel, api, type) {
history.clear(ecModel);

api.dispatchAction({
type: 'restore',
from: this.uid
});
};

Brush

brush(选框组件的控制按钮)定义了defaultOption属性以及onclick、updateView、getIcons等方法,主要代码如下:

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
var proto = Brush.prototype;
proto.onclick = function (ecModel, api, type) {
var brushType = this._brushType;
var brushMode = this._brushMode;
if (type === 'clear') {
api.dispatchAction({
type: 'axisAreaSelect',
intervals: []
});
api.dispatchAction({
type: 'brush',
command: 'clear',
areas: []
});
}
else {
api.dispatchAction({
type: 'takeGlobalCursor',
key: 'brush',
brushOption: {
brushType: type === 'keep'
? brushType
: (brushType === type ? false : type),
brushMode: type === 'keep'
? (brushMode === 'multiple' ? 'single' : 'multiple')
: brushMode
}
});
}
};

区域选择组件 Brush

BrushModel

BrushModel通过extendComponentModel方法扩展自Component Model,重写了defaultOption属性以及optionUpdated方法,并定义了setAreas、setBrushOption等方法。

BrushView

BrushView通过extendComponentView方法扩展自Component View,重写了init、render、dispose等方法,主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
init: function (ecModel, api) {
...
// 绑定_onBrush事件
(this._brushController = new BrushController(api.getZr()))
.on('brush', zrUtil.bind(this._onBrush, this))
.mount();
},

render: function (brushModel) {
this.model = brushModel;
return updateController.apply(this, arguments);
},

BrushAction

BrushAction注册了brush、brushSelect等action

Brush.js

Brush.js即toolbox中Brush的内容

时间轴 Timeline

默认为slider类型

TimelineAction

TimelineAction注册了timelineChange、timelinePlayChange等action

TimelineModel

TimelineModel通过extend方法扩展自Component Model,重写了defaultOption属性以及init方法,并定义了setCurrentIndex、getCurrentIndex、 setPlayState、getPlayState等方法

TimelineView

TimelineView通过extend方法扩展自Component Model。

SliderTimelineModel

SliderTimelineModel通过extend方法扩展自TimelineModel,重写了defaultOption属性

SliderTimelineView

SliderTimelineView通过extend方法扩展自TimelineView,重写了init、render方法,主要代码如下:

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
render: function (timelineModel, ecModel, api, payload) {
this.model = timelineModel;
this.api = api;
this.ecModel = ecModel;

this.group.removeAll();

if (timelineModel.get('show', true)) {

// 获取布局信息
var layoutInfo = this._layout(timelineModel, api);
// 通过graphic.Group创建组
var mainGroup = this._createGroup('mainGroup');
var labelGroup = this._createGroup('labelGroup');
// 新建new TimelineAxis对象实例
var axis = this._axis = this._createAxis(layoutInfo, timelineModel);

timelineModel.formatTooltip = function (dataIndex) {
return encodeHTML(axis.scale.getLabel(dataIndex));
};

// 调用_renderAxisLine:通过graphic.Line绘制轴线
// 调用_renderAxisTick:通过createSymbol创建symbol
// 调用_renderControl:通过new BoundingRect来创建control btn,包括prev、next、play及stop
// 调用_renderCurrentPointer:通过createSymbol创建pointer symbol
each(
['AxisLine', 'AxisTick', 'Control', 'CurrentPointer'],
function (name) {
this['_render' + name](layoutInfo, mainGroup, axis, timelineModel);
},
this
);
// 通过graphic.Text渲染axis label
this._renderAxisLabel(layoutInfo, labelGroup, axis, timelineModel);
this._position(layoutInfo, timelineModel);
}
// 设置定时器,实现时间轴自动播放效果
this._doPlayStop();
},

原生图形组件 Graphic

graphic.js

graphic.js文件中定义了GraphicModel以及GraphicView

GraphicModel

GraphicModel通过extendComponentModel扩展自Component Model,重写了defaultOption属性以及mergeOption、optionUpdated等方法

GraphicView

GraphicView通过extendComponentView扩展自Component View,重写了init、render方法。主要代码如下:

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
render: function (graphicModel, ecModel, api) {
if (graphicModel !== this._lastGraphicModel) {
this._clear();
}
this._lastGraphicModel = graphicModel;

this._updateElements(graphicModel);
this._relocate(graphicModel, api);
}
_updateElements: function (graphicModel) {
var elOptionsToUpdate = graphicModel.useElOptionsToUpdate();

if (!elOptionsToUpdate) {
return;
}

var elMap = this._elMap;
var rootGroup = this.group;

// Top-down tranverse to assign graphic settings to each elements.
zrUtil.each(elOptionsToUpdate, function (elOption) {
var $action = elOption.$action;
var id = elOption.id;
var existEl = elMap.get(id);
var parentId = elOption.parentId;
var targetElParent = parentId != null ? elMap.get(parentId) : rootGroup;

var elOptionStyle = elOption.style;
if (elOption.type === 'text' && elOptionStyle) {
// In top/bottom mode, textVerticalAlign should not be used, which cause
// inaccurately locating.
if (elOption.hv && elOption.hv[1]) {
elOptionStyle.textVerticalAlign = elOptionStyle.textBaseline = null;
}

// Compatible with previous setting: both support fill and textFill,
// stroke and textStroke.
!elOptionStyle.hasOwnProperty('textFill') && elOptionStyle.fill && (
elOptionStyle.textFill = elOptionStyle.fill
);
!elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && (
elOptionStyle.textStroke = elOptionStyle.stroke
);
}

// Remove unnecessary props to avoid potential problems.
var elOptionCleaned = getCleanedElOption(elOption);
...
// createEl:根据graphicType创建元素
if (!$action || $action === 'merge') {
existEl
? existEl.attr(elOptionCleaned)
: createEl(id, targetElParent, elOptionCleaned, elMap);
}
else if ($action === 'replace') {
removeEl(existEl, elMap);
createEl(id, targetElParent, elOptionCleaned, elMap);
}
else if ($action === 'remove') {
removeEl(existEl, elMap);
}
...
});
}

数据集 Dataset

dataset

dataset中定义了Model&View

  • Model: 通过extend方法扩展自Component Model,重写了defaultOption属性以及optionUpdated方法
  • View: 通过extend方法扩展自Component View

图表标识 Marker

公共文件

MarkerModel

MarkerModel通过extendComponentModel方法扩展自Component Model,重写了init、mergeOption、formatTooltip、getData及setData等方法

MarkerView

MarkerView通过extendComponentView方法扩展自Component View,重写了init、render等方法并定义了renderSeries方法(子类中重写),主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
render: function (markerModel, ecModel, api) {
var markerGroupMap = this.markerGroupMap;
markerGroupMap.each(function (item) {
item.__keep = false;
});

var markerModelKey = this.type + 'Model';
// 调用子类的renderSeries方法
ecModel.eachSeries(function (seriesModel) {
var markerModel = seriesModel[markerModelKey];
markerModel && this.renderSeries(seriesModel, markerModel, ecModel, api);
}, this);

markerGroupMap.each(function (item) {
!item.__keep && this.group.remove(item.group);
}, this);
}

图表标线 MarkLine

MarkLineModel

MarkLineModel通过extend方法扩展自MarkerModel,重写了defaultOption属性

MarkLineView

MarkLineView通过extend方法扩展自MarkerView,重写了renderSeries方法,主要代码如下:

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
renderSeries: function (seriesModel, mlModel, ecModel, api) {
var coordSys = seriesModel.coordinateSystem;
var seriesId = seriesModel.id;
var seriesData = seriesModel.getData();

var lineDrawMap = this.markerGroupMap;
// 创建LineDraw对象(绘制line)实例
var lineDraw = lineDrawMap.get(seriesId)
|| lineDrawMap.set(seriesId, new LineDraw());
this.group.add(lineDraw.group);
// 处理data数据,并返回
var mlData = createList(coordSys, seriesModel, mlModel);

var fromData = mlData.from;
var toData = mlData.to;
var lineData = mlData.line;

mlModel.__from = fromData;
mlModel.__to = toData;
// Line data for tooltip and formatter
mlModel.setData(lineData);

var symbolType = mlModel.get('symbol');
var symbolSize = mlModel.get('symbolSize');
if (!zrUtil.isArray(symbolType)) {
symbolType = [symbolType, symbolType];
}
if (typeof symbolSize === 'number') {
symbolSize = [symbolSize, symbolSize];
}
...
// Update visual and layout of line
lineData.each(function (idx) {
var lineColor = lineData.getItemModel(idx).get('lineStyle.color');
lineData.setItemVisual(idx, {
color: lineColor || fromData.getItemVisual(idx, 'color')
});
lineData.setItemLayout(idx, [
fromData.getItemLayout(idx),
toData.getItemLayout(idx)
]);

lineData.setItemVisual(idx, {
'fromSymbolSize': fromData.getItemVisual(idx, 'symbolSize'),
'fromSymbol': fromData.getItemVisual(idx, 'symbol'),
'toSymbolSize': toData.getItemVisual(idx, 'symbolSize'),
'toSymbol': toData.getItemVisual(idx, 'symbol')
});
});
// 根据lineData绘制line
lineDraw.updateData(lineData);
...
lineDraw.__keep = true;

lineDraw.group.silent = mlModel.get('silent') || seriesModel.get('silent');
}

图标标注 MarkPoint

MarkPointModel

MarkPointModel通过extend方法扩展自MarkerModel,重写了defaultOption属性

MarkPointView

MarkPointView通过extend方法扩展自MarkerView,重写了renderSeries方法,主要代码如下:

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
renderSeries: function (seriesModel, mpModel, ecModel, api) {
var coordSys = seriesModel.coordinateSystem;
var seriesId = seriesModel.id;
var seriesData = seriesModel.getData();

var symbolDrawMap = this.markerGroupMap;
// 创建SymbolDraw对象(绘制Symbol)实例
var symbolDraw = symbolDrawMap.get(seriesId)
|| symbolDrawMap.set(seriesId, new SymbolDraw());

var mpData = createList(coordSys, seriesModel, mpModel);

// FIXME
mpModel.setData(mpData);

updateMarkerLayout(mpModel.getData(), seriesModel, api);

mpData.each(function (idx) {
var itemModel = mpData.getItemModel(idx);
var symbolSize = itemModel.getShallow('symbolSize');
if (typeof symbolSize === 'function') {
// FIXME 这里不兼容 ECharts 2.x,2.x 貌似参数是整个数据?
symbolSize = symbolSize(
mpModel.getRawValue(idx), mpModel.getDataParams(idx)
);
}
mpData.setItemVisual(idx, {
symbolSize: symbolSize,
color: itemModel.get('itemStyle.color')
|| seriesData.getVisual('color'),
symbol: itemModel.getShallow('symbol')
});
});

// mpData绘制Symbol
symbolDraw.updateData(mpData);
this.group.add(symbolDraw.group);
...
symbolDraw.__keep = true;

symbolDraw.group.silent = mpModel.get('silent') || seriesModel.get('silent');
}

图表标域 MarkArea

MarkAreaModel

MarkAreaModel通过extend方法扩展自MarkerModel,重写了defaultOption属性

MarkAreaView

MarkAreaView通过extend方法扩展自MarkerView,重写了renderSeries方法,主要代码如下:

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
renderSeries: function (seriesModel, maModel, ecModel, api) {
var coordSys = seriesModel.coordinateSystem;
var seriesId = seriesModel.id;
var seriesData = seriesModel.getData();

var areaGroupMap = this.markerGroupMap;
// 创建group
var polygonGroup = areaGroupMap.get(seriesId)
|| areaGroupMap.set(seriesId, {group: new graphic.Group()});

this.group.add(polygonGroup.group);
polygonGroup.__keep = true;

// 处理数据
var areaData = createList(coordSys, seriesModel, maModel);
...
areaData.diff(polygonGroup.__data)
.add(function (idx) {
// 通过graphic.Polygon创建元素
var polygon = new graphic.Polygon({
shape: {
points: areaData.getItemLayout(idx)
}
});
areaData.setItemGraphicEl(idx, polygon);
polygonGroup.group.add(polygon);
})
.update(function (newIdx, oldIdx) {
// 更新
var polygon = polygonGroup.__data.getItemGraphicEl(oldIdx);
graphic.updateProps(polygon, {
shape: {
points: areaData.getItemLayout(newIdx)
}
}, maModel, newIdx);
polygonGroup.group.add(polygon);
areaData.setItemGraphicEl(newIdx, polygon);
})
.remove(function (idx) {
var polygon = polygonGroup.__data.getItemGraphicEl(idx);
polygonGroup.group.remove(polygon);
})
.execute();

areaData.eachItemGraphicEl(function (polygon, idx) {
// 设置样式
var itemModel = areaData.getItemModel(idx);
var labelModel = itemModel.getModel('label');
var labelHoverModel = itemModel.getModel('emphasis.label');
var color = areaData.getItemVisual(idx, 'color');
polygon.useStyle(
zrUtil.defaults(
itemModel.getModel('itemStyle').getItemStyle(),
{
fill: colorUtil.modifyAlpha(color, 0.4),
stroke: color
}
)
);

polygon.hoverStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle();

graphic.setLabelStyle(
polygon.style, polygon.hoverStyle, labelModel, labelHoverModel,
{
labelFetcher: maModel,
labelDataIndex: idx,
defaultText: areaData.getName(idx) || '',
isRectText: true,
autoColor: color
}
);

graphic.setHoverStyle(polygon, {});

polygon.dataModel = maModel;
});

polygonGroup.__data = areaData;

polygonGroup.group.silent = maModel.get('silent') || seriesModel.get('silent');
}

总结

这篇博文简单介绍了DataZoom、VisualMap、Tooltip、Toolbox、Brush、Timeline、Graphic以及Marker等组件的渲染细节,到这里我们对于Echarts组件的渲染分析已经告一段落了,接下来我们将进入Series即图表的渲染过程分析阶段。

希望能够跟小伙伴们一起进步呀!继续加油!!

本文标题:echarts源码解读《四》:echarts源码之Component分析

文章作者:萌萌哒的邱邱邱邱

发布时间:2019年05月23日 - 19:05

最后更新:2019年06月06日 - 14:06

原始链接:https://qiuruolin.github.io/2019/05/23/echarts-4/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------本文结束感谢您的阅读-------------
感谢您的支持