ECharts 3.0源码简要分析1-总体架构

    xiaoxiao2021-03-25  124

    百度的Echarts 3.0作为前端领域可视化重要的开源库,是我们在日常工作生活中经常使用的,所以有必要一起来了解下Echarts的源码。我打算用一个系列介绍下Echarts 3.x的使用和源码,一些demo和没有在博客中介绍的源码请进我的github仓库。

    https://github.com/zrysmt/echarts3/tree/master/echarts

    本博文Echarts版本基于3.3.2。

    Echarts的源码是在zrender的基础上封装的,所以要看明白echarts源码须要先了解zrender的源码,不过为了本博文的独立可读性,这里也会将用到的zrender源码简单说明。如果要了解zrender具体的源码,这里给出了zrender源码解读博客和源码注释仓库。

    github仓库:https://github.com/zrysmt/echarts3/tree/master/zrender

    ECharts 3.0底层zrender 3.x源码分析1-总体架构 ECharts 3.0底层zrender 3.x源码分析2-Painter(V层) ECharts 3.0底层zrender 3.x源码分析3-Handler(C层)

    1.源码结构和打包

    1.1 源码打包

    源码使用webpack打包,查看文件webpack.config.js可知,将echarts源码编译成三个版本,分别为常用版本,精简版本,完整版本,分别对应webpack入口文件为index.common.js、index.simple.js、index.js。

    注:三个文件引用的都是lib文件下的文件,执行下面一步提示的命令npm insall后就可以得到lib文件夹,它里面的文件和src文件夹中的文件主要内容是相同的,不同之处在于:前者文件是通过类似CMD的模式打包的,后者文件是通过webpack进行打包的。我们在下面就分析src文件夹下的源码。注释也在其中。

    执行命令顺序为

    npm install //安装所有依赖包 webpack //打包 webpack -p //打成压缩包(.min.js)

    最后生成的文件在dist文件夹下。

    1.2 源码结构

    首先我们要明白两个重要的概念components和charts:charts是指各种类型的图表,例如line,bar,pie等,在配置项中指的是series对应的配置;components组件是在配置项中除了serie的其余项,例如title,legend,toobox等。

    源码的重要目录及说明如下(注:dist为编译后生成的文件夹)

    extension (扩展中使用)lib (源码中没有,执行webpack编译后才存在)map (世界地图,中国地图及中国各个省份地图的js和json两种格式的文件)src (核心源码)test (示例demo)theme (主题)

    2 渲染情况

    完整的例子代码戳我。

    最外层是id为main的div,是我们自己写的用来渲染echarts图表的。 echarts渲染了两个div,一个div用来渲染主要的图表的,div里面嵌套一个canvas标签, 第二个div是为了显示hover层信息的。

    3.入口echarts.js

    位置:src/echarts.js。

    大体的结构是一个构造函数(ECharts),原型上(ECharts.prototype)多个方法,一个echarts对象(包括对象上的属性和方法)。

    和zrender一样,使用init方法进行初始化。

    3.1 init方法

    源码:

    echarts.init = function(dom, theme, opts) { if (__DEV__) {//是否是debug模式 //... //错误判断这部分内容省略 } var chart = new ECharts(dom, theme, opts);//实例化ECharts chart.id = 'ec_' + idBase++;//chart实例的id号,唯一,逐一递增 instances[chart.id] = chart;//唯一instance(实例)对象 dom.setAttribute && dom.setAttribute(DOM_ATTRIBUTE_KEY, chart.id);//为外层dom设置了一个属性,属性值等于chart.id enableConnect(chart);//按照顺序更新状态,一共三个状态 /*var STATUS_PENDING = 0; var STATUS_UPDATING = 1; var STATUS_UPDATED = 2;*/ return chart; }; if (__DEV__)验证是否是debug模式,如果是就会有错误提示(错误判断这部分内容省略),否者就是生产模式,没有错误提示。参数说明 /** * @param {HTMLDomElement} dom 实例容器,一般是一个具有高宽的div元素 * @param {Object} [theme] 主题(说明见下面) * @param {Object} opts 配置属性,下面几个属性 * @param {number} [opts.devicePixelRatio] Use window.devicePixelRatio by default * @param {string} [opts.renderer] Currently only 'canvas' is supported. * @param {number} [opts.width] Use clientWidth of the input `dom` by default. * Can be 'auto' (the same as null/undefined) * @param {number} [opts.height] Use clientHeight of the input `dom` by default. * Can be 'auto' (the same as null/undefined) */ 主题theme /*theme主题,可以在官网下载(http://echarts.baidu.com/download-theme.html),或者自己构建 * 使用: * <script src="theme/vintage.js"></script> * <script> * // 第二个参数可以指定前面引入的主题 * var chart = echarts.init(document.getElementById('main'), 'vintage'); * </script> */

    使用:

    var chart = echarts.init(document.getElementById('main'), null, { renderer: 'canvas'});

    3.2 构造函数

    构造函数里面是属性的初始化和zrender的初始化(this._zr)。

    function ECharts(dom, theme, opts) { opts = opts || {}; if (typeof theme === 'string') { theme = themeStorage[theme]; } this.id; this.group; this._dom = dom; var zr = this._zr = zrender.init(dom, { renderer: opts.renderer || 'canvas', devicePixelRatio: opts.devicePixelRatio, width: opts.width, height: opts.height });//构造函数第三个参数使用的zrender处理的 this._throttledZrFlush = throttle.throttle(zrUtil.bind(zr.flush, zr), 17); this._theme = zrUtil.clone(theme); this._chartsViews = [];//存储所有的charts,为后面便利该变量渲染之 this._chartsMap = {}; this._componentsViews = [];//存储配置项组件的属性,为后面便利该变量渲染之 this._componentsMap = {}; this._api = new ExtensionAPI(this); //this._api是有'getDom', 'getZr', 'getWidth', 'getHeight', 'dispatchAction', 'isDisposed', //'on', 'off', 'getDataURL', 'getConnectedDataURL', 'getModel', 'getOption'方法的对象 this._coordSysMgr = new CoordinateSystemManager(); Eventful.call(this); this._messageCenter = new MessageCenter(); this._initEvents();//初始化鼠标事件 this.resize = zrUtil.bind(this.resize, this); this._pendingActions = []; function prioritySortFunc(a, b) { return a.prio - b.prio; } timsort(visualFuncs, prioritySortFunc); timsort(dataProcessorFuncs, prioritySortFunc); zr.animation.on('frame', this._onframe, this); }

    3.3 setOption

    首先我们来看下使用api的情况,我们在前面已经说过使用init方法初始化echarts了,接下来只需要配置option就可以得到渲染的图表。例子中有很多省略,完整的例子代码戳我。

    chart.setOption({ backgroundColor: '#eee', title: { text: '我是柱状图', padding: 20 }, legend: { inactiveColor: '#abc', borderWidth: 1, data: [{name: 'bar'}, 'bar2', '\n', 'bar3', 'bar4'], align: 'left', tooltip: {show: true } }, toolbox: { top: 25, feature: { magicType: { type: ['line', 'bar', 'stack', 'tiled']}, dataView: {}, saveAsImage: {pixelRatio: 2} }, iconStyle: { emphasis: {textPosition: 'top'} } }, tooltip: {}, xAxis: { //... }, yAxis: { //... }, series: [{ name: 'bar', type: 'bar', stack: 'one', itemStyle: itemStyle, data: data1 }, { name: 'bar2', type: 'bar', stack: 'one', itemStyle: itemStyle, data: data2 }, { //... ... }, { //... ... }] });

    源码的主要部分列下来:

    /** * @param {Object} option 配置项 * @param {boolean} notMerge 可选,是否不跟之前设置的option进行合并,默认为false,即合并。 * @param {boolean} [lazyUpdate=false] Useful when setOption frequently. * //可选,在设置完option后是否不立即更新图表,默认为false,即立即更新 */ echartsProto.setOption = function(option, notMerge, lazyUpdate) { this[IN_MAIN_PROCESS] = true; if (!this._model || notMerge) { //不和之前的option合并 var optionManager = new OptionManager(this._api); //option配置管理 var theme = this._theme; var ecModel = this._model = new GlobalModel(null, null, theme, optionManager); ecModel.init(null, null, theme, optionManager); //不合并的时候会重绘,option为最后一次使用setOption方法的参数 } this.__lastOnlyGraphic = !!(option && option.graphic); //是否设置了graphic属性 //graphic 是原生图形元素组件。可以支持的图形元素包括:image, text, circle, sector, //ring, polygon, polyline, rect, line, bezierCurve, arc, group, //http://echarts.baidu.com/option.html#graphic zrUtil.each(option, function(o, mainType) { mainType !== 'graphic' && (this.__lastOnlyGraphic = false); }, this); //setOption之前先执行的函数列表optionPreprocessorFuncs this._model.setOption(option, optionPreprocessorFuncs); if (lazyUpdate) { //为true,不立刻更新 this[OPTION_UPDATED] = true; } else { updateMethods.prepareAndUpdate.call(this); //准备更新 // Ensure zr refresh sychronously, and then pixel in canvas can be // fetched after `setOption`. this._zr.flush(); //调用zrender中的方法,立即刷新 this[OPTION_UPDATED] = false; } this[IN_MAIN_PROCESS] = false; flushPendingActions.call(this, false); };

    说明: 这里setOption调用的顺序是这样的echarts.setOption==>GlobalModel.setOption(GlobalModel.js)==>OptionManager.setOption(OptionMManager.js)

    其中有两个关键的方法:prepareAndUpdate和flush,分别用来准备刷新和刷新,渲染图表,下面我们来一步一步看prepareAndUpdate方法。

    3.4 doRender方法

    接着上面的prepareAndUpdate方法看准备渲染图表视图的执行顺序:updateMethods.prepareAndUpdate==>updateMethods.update==>doRender==>render。 在doRneder函数中渲染所有components和charts,render方法分别对应在各个components和charts中有具体的实现。

    function doRender(ecModel, payload) { var api = this._api; // Render all components 渲染所有的配置组件,例如title,grid,toolbox,tooltip等 each(this._componentsViews, function(componentView) { var componentModel = componentView.__model; console.info("componentModel:", componentModel); componentView.render(componentModel, ecModel, api, payload); //在componentModal文件夹下调用相应的render方法 updateZ(componentModel, componentView); }, this); each(this._chartsViews, function(chart) { chart.__alive = false; }, this); // Render all charts 渲染所有的charts ecModel.eachSeries(function(seriesModel, idx) { var chartView = this._chartsMap[seriesModel.__viewId]; //this._chartsMap chartView.__alive = true; chartView.render(seriesModel, ecModel, api, payload); chartView.group.silent = !!seriesModel.get('silent'); updateZ(seriesModel, chartView); updateProgressiveAndBlend(seriesModel, chartView); }, this); // If use hover layer 如果使用hover,更新hover层 updateHoverLayerStatus(this._zr, ecModel); each(this._chartsViews, function(chart) { if (!chart.__alive) { chart.remove(ecModel, api); } }, this); }

    4.关于option的处理

    3.3部分已经说过使用setOption处理配置项options,这里介绍下源码里面是怎样管理配置项的。主要源码在echarts/model/Model(以下简称Model),echarts/model/Global(以下简称GlobalModel,继承Model),echarts/model/OptionManager(以下简称OptionManager)

    this._model.setOption(option, optionPreprocessorFuncs);

    注意这里面有个对象this._model用来存储配置项options的。

    Model模块是一些基本的方法,主要的方法就是get,getModel通过options的对象名获取对象值。还混合了lineStyle,areaStyle,textStyle,itemStyle方法用来管理与线,文本,项目有关的options属性。

    GlobalModel继承Model,暴露Model的方法,再封装一些自己独有的方法。

    OptionManager是用来管理options配置项的,有重要的setOption方法,mergeOption方法(私有方法,合并options),parseRawOption方法(私有方法,解析options)

    5.component组件和charts图表

    component组件和charts图表均有render方法,这是我们来重点探究的方法。

    5.1 component组件

    component组件和配置项的属性一一对应,对于复杂点的配置项,组件文件夹下的管理方式是按照MVC方式的,如legend文件夹下有基本的LegendModel.js(M),LegendView(V),LegendAction(C),与其他的组件一样,还可能有其他的一些js文件。

    先看一个比较简单的例子,如title.js,我们来分析下它的render方法。 - 首先是使用extendComponentModel,相当于Model层,用来配置一些默认的配置项(有的复杂点的会把这些单独拆分开一个Model文件)。 - 然后是extendComponentView,相当于View层,render方法就在其中(有的复杂点的会把这些单独拆分开一个View文件)。

    在render方法中首先当然是获取到配置options对象的内容。通过titleModel.getModel(path)(每个组件都会有一个对应的Model名称)获取对应的属性path。 然后调用new graphic.Text()去调用zrender里面的方法,渲染到canvas上,具体的实现参考上面给出的zrender源码分析内容。

    5.2 charts图表

    在charts文件夹下是各种类型的图表,包含line,bar,pie,map等,每种类型的文件夹下都有下面的几个文件结尾的js文件。 + **Series.js 继承src/modal/series.js中的基础方法,用来管理配置中的series属性,还提供一些默认的配置defaultOption; + **Veiw.js 继承至src/view/Chart.js(这里面相当于接口,没有具体实现方法),主要方法是render,渲染视图 render方法调用的是zrender.js中的内容,如lineView.js调用的是new graphic.Rect.

    6.事件

    示例中的使用:

    chart.on('click', function(params) { console.log(params); });

    关键源码:

    function createRegisterEventWithLowercaseName(method) { return function (eventName, handler, context) { // Event name is all lowercase eventName = eventName && eventName.toLowerCase(); Eventful.prototype[method].call(this, eventName, handler, context); }; } echartsProto.on = createRegisterEventWithLowercaseName('on'); echartsProto.off = createRegisterEventWithLowercaseName('off'); echartsProto.one = createRegisterEventWithLowercaseName('one');

    Eventful使用的是zrender中的(zrender/mixin/Eventful),是事件扩展,包括on,off,one,trigger等方法。

    我们知道canvas API没有提供监听每个元素的机制,这就需要一些处理。处理的思路是:监听事件的作用坐标(如点击时候的坐标),判断在哪个绘制元素的范围中,如果在某个元素中,这个元素就监听该事件。具体的思路可以查看HTML5 Canvas绘制的图形的事件处理。

    参考阅读: - echarts 3中文官网 - echarts 3 github网址 - ECharts 3.0底层zrender 3.x源码分析1-总体架构 - ECharts 3.0底层zrender 3.x源码分析2-Painter(V层) - ECharts 3.0底层zrender 3.x源码分析3-Handler(C层)

    转载请注明原文地址: https://ju.6miu.com/read-10177.html

    最新回复(0)