相信大家在项目开发中都或多或少的用过一些图表,让我们再看一下一个简单的图表是什么样的。
截图自EChart Demo
一个简单的条形图表都是由哪些基础图形组成的了?没错!正如看到的那样主要由线段和文字以及矩形组成。这三个基础图形组成了一个简单图表。然后对这三个基础图形进行划分,线段和文字为一组构成了坐标轴,所有矩形为一组构成了图表的主体。然后将坐标轴和主体放入一个画板中并绘制出来即形成了这个图表。 依据上面的描述并参考Photoshop绘图的理念可以提取一个简单的关系,如图:
以上便是一个总体的结构设计划分,接下来只需实现相应的类即可。
Canvas类
作为所有图形表现的媒介,Canvas类必须有一个可供图形呈现到网页中的元素,故初始化需传入一个 html 元素来确定图表的渲染位置,并需要提供相应的配置来确定画布的大小。此外作为绘图的入口还需要负责绘画的统一调度,以及对相应事件的转发和模拟,否则作为HTMLCanvasElement中的一个图像到目前为止是不能原生响应相应的事件的,事件的订阅和派发放到下面单独介绍。
Canvas类具体配置和常用方法如下
配置
container: 画板的容器,canvas渲染到html中的位置
width: 画布宽度
height: 画布高度
style: 绘画的各种样式,具体设置项同原生API
主要方法
draw() 绘制图形。
addLayer(options) 添加图层
addShape(type, options) 添加图形
remove(element) 移除图形或图层
clear() 清除所有图层
update() 更新画布
例子
const canvas = new ZE.Canvas('container', {
width: 800,
height: 600,
style: {
fillStyle: 'red'
}
})
复制代码
EventBus类
为了让canvas中的元素可以获得单独事件响应的能力,故而需要实现了此类,它是Canvas的基类。这个类主要提供了订阅和分发事件的方法,还会记录每种事件都由哪些元素订阅,从而实现事件的冒泡机制。
EventBus类的主要方法
addEventListener(type, func, [element, once])添加订阅事件 (alias: on)
removeEventListener(type, [element, func])移除订阅事件 (alias: off)
once(type, func, [element])添加只执行一次的事件
trigger(type, [element, ...data))触发事件(alias: emit)
例子
canvas.on('test', (str) => { console.log(str) });
canvas.emit('test', '您好');
复制代码
Element类
何为Element?其实Layer和Shape都可以看成是一个元素,他们有许多相似之处,Layer只是包含了若干个Shape的元素,所以提取出了Element这个类,它也是整个引擎最核心的类。我们需要在这个类完成元素的属性设置、动画等相关方法。
Element类的属性和方法
方法
setAttrs(attr)设置属性(具体属性视具体shape而定)
setStyle(style)设置样式
animate(options)设置动画 (默认设置后自动播放)
play() 播放动画
stop() 停止动画
update() 更新画面,更改属性或样式后需要update才会更新画布内容
show() 显示图形
hide() 隐藏图形
destroy() 从画板中移除自己
getCanvas() 获取画板容器
getContext() 获取画笔
getStatus() 返回图形当前状态
属性
attrs 图形相关配置
style 图形相关样式
type 图形类型
container 图形的容器
zIndex 位置层级
computed 根据图形样式和属性计算后得到的一些值
例子
const ball = canvas.addShape('circle', {
attrs: {
x: 50, // 圆心x坐标
y: 50, // 圆心y坐标
r: 50 // 半径
},
style: {
fillStyle: 'red'
},
zIndex: 1, // 层级 默认 0
event: { // 可以直接再初始化时配置相应的事件
click (e) { console.log(e); }
}
});
复制代码
Layer类
Layer是继承Element的,经过Element的封装后需要在Layer里面完成的事情就不多了。Layer作为一个容器,主要当然要提供对元素的添加和删除了,此外个人为每一个Layer设计了一个图形的缓存,即每个Layer都拥有一个自己的缓存画板,当Canvas调用draw来进行绘画时,可以直接将缓存的图像绘制到显示的绘画板中,这样的初衷是为了提高绘画的效率(待验证),但会增加内存的使用。
Layer类的主要方法和属性
addLayer() 添加图层
addShape() 添加图形
remove() 移除一个子元素
clear() 清除子元素
palette 缓存的画板
brush 缓存的画笔
attrs 画板的属性
例子
const layer = canvas.addLayer({
x: 10,
y: 20
});
layer.addShape('text', {
attrs: {
text: 'hello'
}
});
复制代码
Shape类
Shape其实是具体图形的基类,它只是将每个图形都会用到方法提取到了这个公用的地方,比如图形样式的设置和绘制。
绘制其实分两个步骤:1.创建图形路径。2.调用画笔绘制到画板。Shape类只负责后者。
由于每种图形的实现各不相同,并且有的需要很多复杂的数学知识在此就不一一描述了,具体可以查看源码或者搜索查看这些基本图形的实现方法或者另写相应的帖子再介绍。总之每种图形最主要做的两件事是1:实现图形的路径绘制。2:判断一个坐标点是否包含在图形内。
一个初步的渲染引擎基本就差不多了,当然每个人对于各个类的划分可能不尽相同,并且对于各种机制的设计也千差万别,最后再次附上本项目的GitHub地址,欢迎大家fork。