前言

由于echarts是基于zrender进行实现的,所以要解读echarts源码,首先要对zrender有大致的了解,在这篇文章中,我将对zrender的整体结构进行大致的解读。

介绍

zrender是canvas的一个类库,也就是说zrender是基于canvas实现的,canvas API可以参考:W3CSchool Canvas 参考手册

在后续的解读中,我们会介绍zrender使用canvas api 的位置及用途。

目录介绍

文件夹:

  • animation 动画有关;
  • contain 包含判断;
  • container Group.js 元素组的概念;
  • core 核心代码,包含一些工具(util.js)、事件(event.js)、唯一ID(guid.js)、矩阵运算有关(matrix.js)等;
  • dom HandleProxy.js dom事件有关;
  • graphic 图形有关,shape文件夹下就是各个图形的js文件;
  • mixin 混入模式要混入的函数;
  • tool 工具函数,包括颜色工具(color.js),path工具(path.js)和转换工具(transformPath.js);
  • vml IE中的画笔,[vml解释](http://www.g168.net/txt/vml/])

全局的文件:

  • config.js 配置文件
  • Element.js 元素文件作为zrender最基本的元素
  • Handle.js C层,控制层
  • Layer.js 图层管理
  • Painter.js V层,视图层
  • Storage.js M层,数据管理层
  • zrender.js 入口
zrender整体架构

zrender采用了MVC封装,M为Model数据层,V为View视图层,C为Controller控制层:

  • Storage(M):Storage为zrender中的Model层,它主要进行图形数据的增删改查(CRUD)操作;
  • Painter(V):Painter为zrender中的View层,它主要对canvas元素的生命周期进行管理,渲染视图以及控制更新等。定义了addHover、setBackgroundColor等方法;
  • Handler(C):Handler为zrender中的Controller层,它主要实现事件交互处理,实现完整dom事件的模拟封装。
图形

处于graphic文件夹下,zrender定义了一系列图形,供外界调用,包括圆形、矩形、心形、扇形、多边形、折线等,echarts通过调用zrender定义好的图形,可以很便捷地完成图表渲染。

Displayable

Displayable.js为Path、Text等的基类,继承自Element,定义了setStyle、useStyle等方法,

Text

文本,继承自Displayable,调用了helper/text.js中的renderText方法进行绘制。主要代码如下:

export function renderText(hostEl, ctx, text, style, rect, prevEl) {
    style.rich
        ? renderRichText(hostEl, ctx, text, style, rect, prevEl)
        : renderPlainText(hostEl, ctx, text, style, rect, prevEl);
}

文本分为富文本以及普通文本进行分别渲染,它们均通过调用canvas的ctx.fillText或ctx.strokeText 等api进行文本渲染,具体实现可以查看helper/text.js文件。

Style

图形的样式文件,定义了基本的样式属性,以及样式set、clone以及判断等方法。

Path

path为图形的基类,继承自Displayable,定义了buildPath、getBoundingRect、setShape等方法,其包含this._style属性即为Style对象实例。

Shape

shape文件下存放着zrender定义的图形文件,扩展自Path。

Circle

圆形,通过调用Path.extend方法进行扩展,重写了buildPath方法,buildPath调用了canvas api:ctx.arc进行圆形的绘制,主要实现代码如下:

buildPath: function (ctx, shape, inBundle) {
    if (inBundle) {
        ctx.moveTo(shape.cx + shape.r, shape.cy);
    }
    ctx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2, true);
}

Arc

弧形,通过调用Path.extend方法进行扩展,重写了buildPath方法,buildPath调用了canvas api:ctx.arc进行弧形的绘制,主要实现代码如下:

buildPath: function (ctx, shape) {
    var x = shape.cx;
    var y = shape.cy;
    var r = Math.max(shape.r, 0);
    var startAngle = shape.startAngle;
    var endAngle = shape.endAngle;
    var clockwise = shape.clockwise;

    var unitX = Math.cos(startAngle);
    var unitY = Math.sin(startAngle);

    ctx.moveTo(unitX * r + x, unitY * r + y);
    ctx.arc(x, y, r, startAngle, endAngle, !clockwise);
}

Heart

心形❤️,通过调用Path.extend方法进行扩展,重写了buildPath方法,buildPath调用了canvas api:ctx.bezierCurveTo进行弧形的绘制,主要实现代码如下:

buildPath: function (ctx, shape) {
    var x = shape.cx;
    var y = shape.cy;
    var a = shape.width;
    var b = shape.height;
    ctx.moveTo(x, y);
    ctx.bezierCurveTo(
        x + a / 2, y - b * 2 / 3,
        x + a * 2, y + b / 3,
        x, y + b
    );
    ctx.bezierCurveTo(
        x - a * 2, y + b / 3,
        x - a / 2, y - b * 2 / 3,
        x, y
    );
}

bezierCurveTo(控制点1x坐标, 控制点1y坐标, 控制点2x坐标, 控制点2y坐标, 结束点x坐标, 结束点y坐标)

三次被塞尔曲线绘制:

  • P0为开始点,P1为控制点1,P2为控制点2,P3为结束点
  • 取 P0P1 中点M
  • 取 P1P2 中点M’
  • 取 MM’ 中点P
  • 则 P 为曲线的必经点

具体绘制图如下:

Echarts源码阅读指南_Echarts

同理,我们可以绘制出左半边心形:

Echarts源码阅读指南_Echarts_02

Droplet

水滴形状????,通过调用Path.extend方法进行扩展,重写了buildPath方法,buildPath调用了canvas api:ctx.bezierCurveTo进行弧形的绘制,贝塞尔曲线的绘制过程同心形绘制原理,主要实现代码如下:

buildPath: function (ctx, shape) {
    var x = shape.cx;
    var y = shape.cy;
    var a = shape.width;
    var b = shape.height;

    ctx.moveTo(x, y + a);
    ctx.bezierCurveTo(
        x + a,
        y + a,
        x + a * 3 / 2,
        y - a / 3,
        x,
        y - b
    );
    ctx.bezierCurveTo(
        x - a * 3 / 2,
        y - a / 3,
        x - a,
        y + a,
        x,
        y + a
    );
    ctx.closePath();
}

Ellipse

椭圆,通过调用Path.extend方法进行扩展,重写了buildPath方法,buildPath调用了canvas api:ctx.bezierCurveTo进行弧形的绘制,贝塞尔曲线的绘制过程同心形绘制原理,主要实现代码如下:

buildPath: function (ctx, shape) {
    var k = 0.5522848;
    var x = shape.cx;
    var y = shape.cy;
    var a = shape.rx;
    var b = shape.ry;
    var ox = a * k; // 水平控制点偏移量
    var oy = b * k; // 垂直控制点偏移量
    // 从椭圆的左端点开始顺时针绘制四条三次贝塞尔曲线
    ctx.moveTo(x - a, y);
    ctx.bezierCurveTo(x - a, y - oy, x - ox, y - b, x, y - b);
    ctx.bezierCurveTo(x + ox, y - b, x + a, y - oy, x + a, y);
    ctx.bezierCurveTo(x + a, y + oy, x + ox, y + b, x, y + b);
    ctx.bezierCurveTo(x - ox, y + b, x - a, y + oy, x - a, y);
    ctx.closePath();
}

Sector

扇形,通过调用Path.extend方法进行扩展,重写了buildPath方法,buildPath调用了canvas api:ctx.arc以及arc.lineTo进行圆形的绘制,主要实现代码如下:

buildPath: function (ctx, shape) {
    var x = shape.cx;
    var y = shape.cy;
    var r0 = Math.max(shape.r0 || 0, 0);
    var r = Math.max(shape.r, 0);
    var startAngle = shape.startAngle;
    var endAngle = shape.endAngle;
    var clockwise = shape.clockwise;

    var unitX = Math.cos(startAngle);
    var unitY = Math.sin(startAngle);

    ctx.moveTo(unitX * r0 + x, unitY * r0 + y);

    ctx.lineTo(unitX * r + x, unitY * r + y);

    ctx.arc(x, y, r, startAngle, endAngle, !clockwise);

    ctx.lineTo(
        Math.cos(endAngle) * r0 + x,
        Math.sin(endAngle) * r0 + y
    );

    if (r0 !== 0) {
        ctx.arc(x, y, r0, endAngle, startAngle, clockwise);
    }

    ctx.closePath();
}

zrender还定义了Rect矩形、Isogon正多边形、Line直线、Polygon多边形、Polyline折线、Ring圆环、Rose玫瑰线、Star n角星等图形,其实现过程基本与上述图形上类似,都是基于canvas的lineTo、arc、bezierCurveTo等api进行绘制的,这边就不做一一介绍了。

总结

zrender中封装了很多工具,在后续echarts源码解读中,我们可以看到echarts是基于zrender进行开发的。