从零打造Echarts —— v1 ZRender和MVC
本篇开始进入正文。

写在前面
图形、元素、图形元素,都指的是XElement,看情况哪个顺口用哪个。
ts可能会报警告,我只是想用代码提示功能而已,就不管辣么多了。
文内并没有贴出所有代码,且随着版本更迭,可能有修改不及时导致文内代码和源码不一致的情况,可以参考源码进行查看。
源码查看的方式,源码放在这里,每一个版本都有对应的分支。
由于水平所限,以及后续设计的变更,无法在最开始的版本中就写出最优的代码,甚至可能还会存在一些问题,如果遇到你认为不应该这样写的代码请先不要着急。
zrender
zrender是echarts使用的2d渲染器,意思是,对于2d图表,echarts更多的是对于数据的处理,将数据绘制到canvas上这一步是由zrender来完成的。

大概流程就是,使用者告诉echarts我要画条形图,有十条数据,echarts计算出条形图高度和坐标和使用zrender在画布上绘制坐标轴和十个矩形。它也是echarts唯一的依赖。它是一个轻量的二维绘图引擎,但是实现了很多功能。本文就从实现zrender开始作为实现echarts的第一步。

本篇目标
前文说到,打造echarts从打造一个zrender开始,但是zrender的功能同样很多,不可能一步到位,所以先从最基础的功能开始,而我们的库我给它命名为XRender,即无限可能的渲染器。本篇结束后它将实现zrender的以下功能。

import * as xrender from '../xrender'let xr = xrender.init('#app')
let circle = new xrender.Circle({shape: {cx: 40,cy: 40,r: 20}
})
xr.add(circle)
// 现在画布上有一个半径为20的圆了

正文
模式
首先明确一点,我们根据数据来实现视图。

然后看看我们需要哪些东西来实现我们要的功能。 - 要绘制的元素,如圆、长方形, 即Element,为了和html中区分,暂命名为XElment。 - 因为会有多个元素,我们需要对其进行增查删改等管理,类似于3d游戏开发中常见的scene(场景),这里叫做Stage,舞台。zrender中叫做Storage。都差不多。 - 需要将舞台上的元素绘制到画布上,叫做Paniter。 - 最终需要将上面的三者关联起来,即XRender。

也就是MV模式。

考虑到会有多种图形,所以xrender最终导出的是一个命名空间,遵循zrender的设计,并不向外暴露XRender类。那么接下来就可以开始写代码了。

环境搭建
为了方便,我使用了vue-cli搭建环境,你也可以用其它方式,只要能支持出现的语法就行。接着创建xrender目录。或者克隆仓库一键安装。根据上面列出的类,创建如下文件。

index.js # 外部引用的入口
Painter.js
Stage.js
XElement.js
XRender.js

但是需要做一点小小的修正,因为XElement应该是一个抽象类,它只代表一个元素,它本身不提供任何绘制方法,提供绘制方法的应该是继承它的圆Circle类。所以修改后的目录如下。

│  index.js
│  Painter.js
│  Stage.js
│  XRender.js
│
└─xElementsCircle.jsXElement.js

接着在每个文件内创建对应的类,并让构造函数打印出当前类的名称,然后导出,以便搭建整体架构。如:

class Stage {constructor () {console.log('Stage')}
}export default Stage
然后编写index.jsimport XRedner from './XRender'
// 导出具体的元素类
export { default as Circle } from './xElements/Circle'
// 只暴露方法而不直接暴露`XRender`类
export function init () {return new XRedner()
}
在使用它之前我们还得为XRender类添加add方法,尽管现在它什么都没做。// 尽管没有使用,但是需要用它来做类型提示
// 用Flow和ts,或jsdoc等,都嫌麻烦
import XElement from "./xElements/XElement";class XRender {/*** * @param {XElement} xel */add (xel) {console.log('add an el')}
}
接下来就可以在App.vue中写最开始的代码。如果一切顺利,应该能在控制台上看到XRender
Circle
add an el

细节填充
在下一步之前,我们可能需要一些辅助函数,比如我们经常会判断某个参数是不是字符串。为此我们创建util文件夹来存放辅助函数。

XElement
图形元素,一个抽象类,它应该帮继承它的类如Circle处理好样式之类的选项,Circle只需要绘制即可。显然它的构造函数应该接受一个选项作为参数,包括这些:

import { merge } from '../util'
/*** 目前什么都没有*/
export interface XElementShape {
}
/*** 颜色*/
type Color = String | CanvasGradient | CanvasPattern
export interface XElementStyle {// 先只设定描边颜色和填充/*** 填充*/fill?: Color/*** 描边*/stroke?: Color
}
/*** 元素选项接口*/
interface XElementOptions {/*** 元素类型 */type?: string/*** 形状*/shape?: XElementShape/*** 样式*/style?: XElementStyle
}

接着是对类的设计,对于所有选项,它应该有一个默认值,然后在更新时被覆盖。

class XElement {shape: XElementShape = {}style: XElementStyle = {}constructor (opt: XElementOptions) {this.options = opt}/*** 这一步不在构造函数内进行是因为放在构造函数内的话,会被子类的默认属性声明重写*/updateOptions () {let opt = this.optionsif (opt.shape) {// 这个函数会覆盖第一个参数中原来的值merge(this.shape, opt.shape)}if (opt.style) {merge(this.style, opt.style)}}
}

对于一个元素,应该提供一个绘制方法,正如上面所提到的,这由它的子类提供。此外在绘制之前还需要对样式进行处理,绘制之后进行还原。而这就需要一个canvascontext。这里认为它由外部提供。涉及到的api请自行查阅。

class XElement {/*** 绘制*/render (ctx: CanvasRenderingContext2D) {}/*** 绘制之前进行样式的处理*/beforeRender (ctx: CanvasRenderingContext2D) {this.updateOptions()let style = this.stylectx.save()ctx.fillStyle = style.fillctx.strokeStyle = style.strokectx.beginPath()}/*** 绘制之后进行还原*/afterRender (ctx: CanvasRenderingContext2D) {ctx.stroke()ctx.fill()ctx.restore()}/*** 刷新,这个方法由外部调用*/refresh (ctx: CanvasRenderingContext2D) {this.beforeRender(ctx)this.render(ctx)this.afterRender(ctx)}

为什么不在创建它的时候传入ctx作为属性的一部分?实际上这完全可行。只是zrender这样设计,我也暂时先这么做。可能是为了解耦以及多种ctx的需要。

Circle
基类XElement已经初步构造完毕,接下来就来构造Circle,我们只需声明它需要哪些配置,并提供绘制方法即可。也就是,如何绘制一个圆。

import XElement, { XElementShape } from './XElement'interface CircleShape extends XElementShape {/*** 圆心x坐标*/cx: number/*** 圆心y坐标*/cy: number/*** 半径*/r: number
}
interface CircleOptions extends XElementOptions {shape: CircleShape
}class Circle extends XElement {name ='circle'shape: CircleShape = {cx: 0,cy: 0,r: 100}constructor (opt: CircleOptions) {super(opt)}render (ctx: CanvasRenderingContext2D) {let shape = this.shapectx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2, true)}
}export default Circle

来验证一下吧,在App.vue中加入如下代码:

mounted () {let canvas = document.querySelector('#canvas') as HTMLCanvasElementlet ctx = canvas.getContext('2d') as CanvasRenderingContext2Dcircle.refresh(ctx)
}

查看页面,已经有了一个黑色的圆。

Stage

需要它对元素进行增查删改,很容易写出这样的代码。

class Stage {/*** 所有元素的集合*/xelements: XElement[] = []constructor () {console.log('Stage')}/*** 添加元素* 显然可能会添加多个元素*/add (...xelements: XElement[]) {this.xelements.push(...xelements)}/*** 删除指定元素*/delete (xel: XElement) {let index = this.xelements.indexOf(xel)if (index > -1) {this.xelements.splice(index)}}/*** 获取所有元素*/getAll () {return this.xelements}
}

Painter

绘画控制器,它将舞台上的元素绘制到画布上,那么创建它时就需要提供一个Stage和画布——当然,库的通用做法是也可以提供一个容器,由库来创建画布。

/*** 创建canvas*/
function createCanvas (dom: string | HTMLCanvasElement | HTMLElement) {if (isString(dom)) {dom = document.querySelector(dom as string) as HTMLElement}if (dom instanceof HTMLCanvasElement) {return dom}let canvas = document.createElement('canvas');(<HTMLElement>dom).appendChild(canvas)return canvas
}class Painter {canvas: HTMLCanvasElementstage: Stagectx: CanvasRenderingContext2Dconstructor (dom: string | HTMLCanvasElement | HTMLElement, stage: Stage) {this.canvas = createCanvas(dom)this.stage = stagethis.ctx = this.canvas.getContext('2d')}
}

它应该实现一个render方法,遍历stage中的元素进行绘制。


render () {let xelements = this.stage.getAll()for (let i = 0; i < xelements.length; i += 1) {xelements[i].refresh(this.ctx)}}

XRender

最后一步啦,创建XRender将它们关联起来。这很简单。

import XElement from './xElements/XElement'
import Stage from './Stage'
import Painter from './Painter'class XRender {stage: Stagepainter: Painterconstructor (dom: string | HTMLElement) {let stage = new Stage()this.stage = stagethis.painter = new Painter(dom, stage)}add (...xelements: XElement[]) {this.stage.add(...xelements)this.render()}render () {this.painter.render()}
}

现在去掉之前试验Circle的代码,保存之后可以看见,仍然绘制出了一个圆,这说明成功啦!

让我们再多添加几个圆试一下,并传入不同的参数。

let xr = xrender.init('#app')let circle = new xrender.Circle({shape: {cx: 40,cy: 40,r: 20}})let circle1 = new xrender.Circle({shape: {cx: 60,cy: 60,r: 20},style: {fill: '#00f'}})let circle2 = new xrender.Circle({shape: {cx: 100,cy: 100,r: 40},style: {fill: '#0ff',stroke: '#f00'}})xr.add(circle, circle1, circle2)

可以看到屏幕上出现了3个圆。接下来我们再尝试扩展一个矩形。


interface RectShape extends XElementShape {/*** 左上角x*/x: number/*** 左上角y*/y: numberwidth: numberheight: number
}
interface RectOptions extends XElementOptions {shape: RectShape
}class Rect extends XElement {name ='rect'shape: RectShape = {x: 0,y: 0,width: 0,height: 0}constructor (opt: RectOptions) {super(opt)}render (ctx: CanvasRenderingContext2D) {let shape = this.shapectx.rect(shape.x, shape.y, shape.width, shape.height)}
}

然后在App.vue中添加代码:

let rect = new xrender.Rect({shape: {x: 120,y: 120,width: 40,height: 40},style: {fill: 'transparent'}})xr.add(rect)

可以看到矩形出现了。

小结
虽然还有很多问题,比如样式规则不完善,比如多次调用add会有不必要的重绘;实现添加圆和矩形这样的功能搞得如此复杂看起来也有点不必要。但是我们已经把基础的框架搭建好了,接下来相信可以逐步完善,最终达成我们想要的效果。

V2预览
下个版本中除了解决小结中出现的两个问题外,还将实现图形分层的功能,即指定图形的层叠顺序。
 转自https://blog.csdn.net/weixin_35538148/article/details/112509719?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242

echarts name 坐标轴_从零打造Echarts —— v1 ZRender和MVC相关推荐

  1. vue echarts div变化_数据可视化之echarts在Vue中的使用

    数据可视化的本质是将数据通过各种视觉通道映射成图形,可以使得用户更快.更准确的理解数据. 一.为什么选择echarts: 简单上手容易 满足绝大部分的开发需要 在可视化库中有较好的体验和口碑 二.在V ...

  2. echarts ucharts 和_使用chart和echarts制作图表

    前  言 chart.js是一个简单.面向对象.为设计者和开发者准备的图表绘制工具库.它可以帮你用不同的方式让你的数据变得可视化.每种类型的图表都有动画效果,并且看上去非常棒,即便是在retina屏幕 ...

  3. 松下伺服电机pwm控制连线_从零打造树莓派家庭监控 (一): 伺服电机控制

    本文首发自我的博客 MikeTech 现在很多家庭喜欢购买一个摄像头来监控家里的情况,但是往往却无法了解购买第三方摄像头的安全性,摄像头泄露的新闻也每过一段时间就会出现在人们的视野当中.如果购买的摄像 ...

  4. echarts python 教程_Echarts入门(零基础小白教程)

    前言 适合0前端基础的小白,什么配置啊,不会啊,我就想画个图先,没问题,按照步骤做,先来实现第一个图,之后官方教程会教会你更多的. 下载echarts 小白建议,直接下完整版,先搞清楚怎么画出一张图再 ...

  5. 零基础ECharts 图表 使用 步骤 流程图 详解 与 pc 端自适应

    前言:呦呦呦 是我我来了我是静静,这回又是零基础 echarts 图表的使用,最近项目中要用到echarts 使用,实话是在使用中,一共就三个步骤,看完开发文档稳稳当当的就会使用,但是大家懒啊 所以这 ...

  6. echarts 世界地图标点_关于echarts的那些事(地图标点,折线图,饼图)

    前记:离上一篇博客的发布已经过去两个月了,这期间总想写点什么,却怎么都写不出来,一直拖到了现在.现在的感觉,不是像这期间一样,想好好整理一番,写一篇好博客,却写不出来.事实发现,随心就好,较好的博客, ...

  7. echarts setoption方法_在Vue和React中使用ECharts的多种方法

    前言 俗话说:"工欲善其事,必先利其器".现如今已经有许多成熟易用的可视化解决方案,例如ECharts,AntV等等.我们可以把这些解决方案比作是一套套成熟的"工具&qu ...

  8. vue中echarts调用接口_在vue2中使用echarts (Vue-ECharts插件)

    Vue-ECharts ECharts 的 Vue.js 组件. 基于 ECharts v4.1.0 + 开发,依赖 Vue.js v2.2.6 +. 安装 npm(推荐方式) $ npm insta ...

  9. Echarts系列(一): 可视化技术概述与Echarts⼊⻔

    一.数据可视化概述 ​ 广义上来说,数据可视化本身是一种泛称,它统一了较成熟的科学可视化和较年轻的信息可视化.而在大数据时代,除了包含这两种以外还囊括了在他们基础上发展起来的知识可视化以及结合了数据分 ...

最新文章

  1. PyTorch 笔记(05)— Tensor 基本运算(torch.abs、torch.add、torch.clamp、torch.div、torch.mul、torch.pow等)
  2. DOM_05之DOM、BOM常用对象
  3. 原生js已载入就执行函数_手写CommonJS 中的 require函数
  4. 编写自己的工具箱 (一)
  5. iText操作Word工具类
  6. mysql导出sql和表格文件大小_atitit.sql server2008导出导入数据库大的表格文件... oracle mysql...
  7. 数字电子技术基础(二):原码、反码、补码
  8. 在外通过手机远程控制家中或者公司的电脑
  9. Xpose 建立新工程
  10. 性价比最高处理器和国产处理器I.MX6UL/A40I/T3对比
  11. pycharm IDEA专业版2016.3.2版本和 python3.5.0 win7 64位安装包 百度云资源共享 及安装和编辑器注册图录
  12. html5游戏 遥控器按键,遥控器按键功能说明汇总
  13. 创龙基于TI AM437x ARM Cortex-A9 + Xilinx Spartan-6 FPGA的 电源接口和拨码开关、JTAG仿真器接口
  14. android 官方bootloader,安卓系统bootloader模式是什么?如何进入bootloader模式
  15. 725 数模 空气污染问题研究 (15 五一 B)
  16. hibernate查询方式总结(四)之QBC,QBE查询
  17. 字节跳动面试:从草根到百万年薪程序员的十年风雨之路,成功收获美团,小米安卓offer
  18. 如何利用python刷微博粉丝_使用python进行新浪微博粉丝爬虫
  19. 云计算学习之路——LVS负载均衡
  20. ThingJS粒子特效一键实现雨雪效果

热门文章

  1. 为什么说贴片式T卡完胜T卡?
  2. 采集文章发布到Discuz论坛指定版块
  3. 正则化-最通俗的解释
  4. no suitable driver found解决方法
  5. spring日志加载代码解析
  6. R语言用WinBUGS 软件对学术能力测验(SAT)建立分层模型
  7. 光学工程毕业论文题目【必看】
  8. [Classic AUTOSAR学习] DLT模块(LogTrace)
  9. 主集天线和分集天线——4G天线技术
  10. 实现ABB PLC远程控制,远程上下载