圖表庫源碼剖析 - 輕巧的 SVG.js

提要

平時咱們寫業務可視化組件的時候 會常用到 SVG, 也會使用 SVG 作一些動畫。SVG.js 是一個開源項目,可以讓你更優雅、便捷的方法編寫 SVG。 咱們也能夠經過對 SVG.js 的分析來一塊兒看下能夠如何去寫一個可視化的工具庫。git


SVG.js 目標:讓 svg 寫起來更簡單, 下面來分析下它的源碼和背後的設計思路,目標是如何作到的;github

SVG.js 簡介

SVG.js 是用於操做 SVG 和執行 SVG 動畫的輕量級庫。一個好的類庫,可擴展性也是必不可少的, SVG.js 擴展性作的也不錯;
SVG.js 沒有依賴關係,而且小(比較同類經常使用類庫 Snap.svg、 Raphael),同時提供接近完整的SVG規範。設計模式

SVG.js 用法

// 原生 svg 寫法
<div id="drawing">
  <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="300">
    <rect width="100" height="100" fill="#f06"></rect>
  </svg>
</div> 
// SVG.js 寫法 
var draw = SVG('drawing').size(300, 300)
var rect = draw.rect(100, 100).attr({ fill: '#f06' })
複製代碼

SVG.js 把 SVG 全部的圖形幫咱們封裝了一下, 可讓咱們更好的使用 js 用鏈式寫法,面向對象的寫法 更好的畫本身業務組件;讓一切迴歸簡單;
SVG.js 將基礎的 shape 進行了包裝,主要包括:circle, ellipse, line, polygon, polyline, rect 等。api

除了面向對象包裝和鏈式寫法, 它還有更好玩的語法糖, 一塊兒來看看bash

// 傳統寫法
[ 'M', 0, 0, 'L', 100, 100, 'z' ]

// SVG.js 寫法
[
  ['M', 0, 0]
, ['L', 100, 100]
, ['z']
]
複製代碼

這樣的寫法閱讀起來更方便svg

從Demo 中咱們能夠發現 SVG.js 定義的API 仍是簡單易用的,工具

設計思路

SVG.js 的設計思路也挺簡單, 從官網的 Overview 上也能夠看出來,把總體按照功能劃分紅幾大類, 這幾個大類定義好後, 全部和其相關的具體的組件或者類就能夠依照具體的功能場景和用途放到哪兒,就能夠知道該怎麼去整理實體關係結構, 而後就能夠針對每一個組件去開發實現其功能了。動畫

  • 【Parents】svg 容器相關。 全部的容器都繼承與 SVG.parent, 提供了不少基礎的方法
  • 【Elements】svg 展示元素相關。 全部渲染的元素的基本原型就SVG.Element , 提供了不少基礎的方法
  • 【Referencing】svg 獲取元素相關。 是一堆的獲取容器的方法
  • 【Manipulating】svg 操做相關。 包括 屬性、位置、大小、樣式、數據 等;
  • 【Boxes】Bounding Box, svg邊界盒子相關。
  • 【Animating】svg 動畫相關。 包括 幾個動畫組件和工具類 全部的動畫都是從這裏實現的和擴展出去的;
  • 【Events】svg 事件相關。 包括 幾個動畫組件和工具類 全部的動畫都是從這裏實現的和擴展出去的;
  • 【Classes】和svg 自己無關的一些工具類。
  • 【Extending】和svg 擴展相關的。 能夠重寫或者擴展組件,或者擴展插件;

來個總體概覽:ui

設計模式在每一個設計中多多少少 或者不經意間都會用到, SVG.js 中用到的設計計模式咱們一塊兒來看下:this

工廠模式,create

不管創造哪一種元素 都會通過SVG.js 模塊中的 create 方法; 統一進行建立;

策略模式, fx

SVG.js 的動畫有不少種類, 這些不一樣的種類能夠單獨封裝成一個策略,供上層統一調用;

extends

在這裏你可使用 SVG.extend 對某一個元素類的方法進行包裝加強, 也能夠增長方法擴展等.

API設計

SVG.js 總體的API 極簡且完備、語義清晰簡單、符合直覺、易於記憶, 名字和原生的SVG 和規範保持一致。

抽象方法篇

總體的抽象思路(ER 實體關係圖)

整體的ER模型(E-R圖 實體關係圖 Entity Relationship Diagram )設計的還算不錯, 但缺少了一點面向對象抽象的設計;
好比下列代碼中 poly這裏使用 SVG.js extends 的擴展方法, 讓同類的一系列類擁有相同的方法; 這個地方能夠抽象到 poly 類中, 讓每一個子類 包含或者集成父類, 這樣在每一個子類能夠方便的看到本身擁有哪些方法,繼承了哪些類。

// poly.js 
SVG.extend(SVG.Polyline, SVG.Polygon, {
  // Get array
  array: function() {
      ...
  }
  // Plot new path
, plot: function(p) {
   ...
  }
  ...
})
複製代碼

SVG.js 這樣的寫法, 閱讀一個類都有哪些集成比較費勁, 如查看Element 都哪些方法, 在代碼上怎麼看(固然能夠經過運行時的原型鏈獲查看到), 咱們得搜, 而後才能夠知道 都進行了哪些的擴展, 才知道增長了哪些方法,這是設計上的值得挑剔的地方。

SVG.invent 實現解析

SVG.invent 方法是用來建立新元素的方法, SVG.js 中絕大部分類的建立都是經過這個基礎方法實現的。

// 設計發明 新元素
SVG.invent = function(config) {
  // 建立元素初始值設定項
  var initializer = typeof config.create == 'function' ?
    config.create :
    function() {
      this.constructor.call(this, SVG.create(config.create))
    }

  // 設置繼承原型鏈
  if (config.inherit)
    initializer.prototype = new config.inherit

  // 擴展方法(新元素的方法增長都放在這裏)
  if (config.extend)
    SVG.extend(initializer, config.extend)

  // 將構造方法裏的方法附加到父容器上 (注意 Container, 由於沒有設置 parent, 因此大部分的construct方法都會去加強Container)
  if (config.construct)
    SVG.extend(config.parent || SVG.Container, config.construct)

  return initializer
}
複製代碼

SVG.invent 是新增長髮明新元素的一個基類方法,新增長的元素會主要包幾個配置項:

create

是建立元素初始值的設定項 。能夠是一個字符串, 會以該字符串的名稱來建立Dom元素, 在實例化的時候會做爲參數傳入改Dom元素;也能夠是一個function, 若是新元素是用來渲染用的 那麼在function內部也會去實現 this.constructor.call 的方法 去傳入element, 且增長其餘處理, 若是新元素不是渲染用的元素 如 SVG.Set、SVG.Array、SVG.Transformation 等, 則沒必要去調用 this.constructor.call

inherit

設置原型鏈方法, 繼承方法使用; 能夠設置新元素類繼承哪一個父類

extend

擴展方法, 新元素的新方法都在這裏

construct

每一個新元素的 construct 裏會有一些方法, 這些方法會extend 到父容器中, 若是沒有設置 parent 父容器, 則會extend 到 SVG.Container 中 , 這就是爲何 new Doc 後獲取到的元素會有全部你想要的方法; 其中 SVG.Doc (繼承關係如上ER圖) 對應的原型鏈是 Doc → Container → Parent -> Element 。

執行原理

一塊兒來看下SVG.js 一個簡單的 polygon 多邊形 底層是怎麼執行的;

var draw = SVG('drawing').size(300, 130)
var polygon = draw.polygon([
    [50, 0],
    [60, 40],
    [100, 50],
    [60, 60],
    [50, 100],
    [40, 60],
    [0, 50],
    [40, 40]
  ])
複製代碼

咱們一塊兒來分析下上邊小Demo 2 個主要的方法: SVG(‘drawing’) 、draw.polygon(…) ;

Step1: SVG(‘drawing’) 流程

SVG(‘drawing’) 主要是在id 爲drawing的 Dom元素上生成一個 SVG Dom 元素(Element), 並返回一個 SVG.Doc 對象, 這個返回的對象(Element)能夠用來生成一切你想要的SVG 元素 如 Polygon 、 Rect 等。前邊說過 SVG.Doc (繼承關係如上ER圖) 對應的原型鏈是 Doc → Container → Parent -> Element ,因此SVG.Doc 對象 擁有了全部SVG.js 所想一想要提供的能夠操做的方法。

Step2: draw.polygon(…) 流程

draw.polygon(…) 方法如上圖流程, 2.一、2.2 會建立 Polygon 元素, 並放到父容器裏邊; 2.3 會去繪製新的路徑點; 2.四、2.5 會經過從新 attr 方法 從新繪製 路徑; 這樣一個流程就能夠畫出來對應 Polygon 圖形;

插件擴展

最上邊的NEFE->海豚的 漸變(Morph)圖片 是使用SVG.js 的插件 pathmorph 作的效果, 很簡單

// create path
 var path = draw.path(nefe)
 // animate path
 setTimeout(function() {
   path.animate(1000).plot(dolphin);
 }, 2000); 
複製代碼

但這個插件不是太強大, 咱們有時候 須要去設置 起始 svg 的某些點,對應結束 svg 的某些點; 有這些,整個漸變的過程纔看起來更協調; 否則漸變漸變的有點生硬了。
能夠暴漏出來 哪些元素進行相互漸變的接口, 如:

// api 建議 能夠設計成
animate.to("#E", "#tail", time) //字母E變體成 海豚尾巴

   // demo
 path.animate({
    time:1000,
    [{from:'#E',to:'#tail'}]
 }).plot(dolphin);
複製代碼

若是提供這樣的API , 就能夠知足大部分的動畫的效果,能夠自由的設置複雜的動效;

總結.

SVG.js 把純粹是 XML 的 SVG 文件的寫法包裝成 JS , 讓由寫 HTML 或者 XML 的體驗轉化爲了腳本, 這樣使整個開發體驗更流暢; SVG.js 提供的API 這種面向對象的鏈式寫法會讓開發者不用考慮原生 SVG 如何渲染到 Dom 上、如何寫一堆的代碼設置去達到想要的效果, 使用 SVG.js會更簡潔;SVG.js 的動畫也讓不少特效的開發變得更有可能 , 能夠有助於加大開發人員對於動畫的思想空間, 讓 SVG 的世界變得更美好。

咱們將持續對可視化工具和類庫作相關的分析和實驗,咱們會應用於各類數據媒體大屏和數據產品之中。 來吧! 咱們一塊兒努力、一塊兒攻克、一塊兒共建、一塊兒分享, 咱們在這兒等着你 mickle.zy@alibaba-inc.com。

相關文章
相關標籤/搜索