資料視覺化初探-從零開始開發一個渲染引擎概述

STMU同學發表於2018-08-26

Demo

專案地址在這裡,歡迎fork和star

簡介:在之前的專案中用過一些圖表,比如Echart、chart.js等。遂對其比較感興趣,恰好最近在看canvas相關的知識,故嘗試寫了一下視覺化的渲染引擎。經過斷斷續續的開發,目前已實現了大部分基礎圖形。

總體架構設計

相信大家在專案開發中都或多或少的用過一些圖表,讓我們再看一下一個簡單的圖表是什麼樣的。

bar

截圖自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。

github.com/STMU1320/ze

相關文章