【圖表開發小案例】快來碼一個性感妖嬈的高逼格 「圓環」

圖1 - 風格各異的圓環效果

平常生產生活中,咱們會常常讀到或使用各類類型的圖表。圓環(圓弧)即是一種較常見的類型,用於直觀展示某一數據指標占總體的比例。本文以 HTML Canvas 的實現爲主(固然,SVG 黨能夠在瞭解原理後自行實現),逐層介紹圓環圖表開發的一些主要思路和原理。git

圖1 所示是一些咱們平時比較常見的一些圓環(圓弧)效果。雖然圖形的主體構成都是圓弧,但不一樣效果在信息傳達的功能上卻略有差別。如:github

  • 閉合的圓環能夠表示流程 「進度」 的概念
  • 非閉合圓環通常用於狀態量(標量)的展現,通常也稱爲 「儀表盤」 效果
  • 不一樣的色相可用於標識狀態量的狀態區間(如:低危-中危-高危 區間的標識可使用三種顏色的構成的過渡效果進行表達)

爲了更加方便、完善地解決咱們在業務開發時的具體須要,能夠對這些風格、樣式進行必定分析、抽象,總結出一個通用組件須要具有的能力,如:canvas

  • 顏色、漸變可配(支持傳入單一色值或顏色序列)
  • 圓弧寬度可調(內、外半徑大小可配置)
  • 圓弧夾角可調(開始角度、截止角度可配置)
  • 圓弧端點造型可選(可切換平角/半圓造型
  • 文案效果可調(字號、字體、顏色等)

下面,咱們着手於實現這樣一個功能全面、業務通用性較強的圓環組件。工具

1. 圓環的造型

繪製圓環造型的第一步,須要先繪製圓環圖表構成要素,即一段一段的圓弧。而對於像下圖中這樣的兩種倒角效果(黃色部分圓弧兩端的樣式),既能夠是直角,也能夠是半圓。字體

圖2 - 圓弧的兩種倒角風格

所以,咱們須要實現一個通用的方法來繪製圓弧,提供兩種倒角風格給用戶。動畫

1.1 前景圓弧的繪製

圖3 - 圓弧繪製的步驟

圓弧繪製的思路如上圖所示,按前後順序大體分爲幾個步驟:this

(1)繪製圓弧起始端的半圓輪廓
(2)繪製圓弧的外邊緣輪廓
(3)繪製圓弧終止端的半圓輪廓
(4)繪製圓弧的內邊緣輪廓
(5)閉合輪廓並填充色彩
注:因爲 canvas 繪製圓弧的方法默認是順時針方向,於是咱們的繪圖步驟也是沿着順時針方向

如下是一些姿式要領:spa

1.1.1 端點座標的計算

在繪製端點半圓以前咱們須要端點的位置座標,以其實端爲例,根據圓環的半徑(內外徑的均值,即圓弧中線的半徑)和起始端點的角度如何計算圓上一點的座標:設計

// 計算圓弧上某點的座標
// originX, originY - 圓心的座標
// radius - 圓環半徑,等於圓環內、外徑的平均值,也即圓弧中線的半徑
// alpha - 弧度
function calcPosition(originX, originY, radius, alpha) {
  return [
    radius * Math.cos(alpha) + originX,
    radius * Math.sin(alpha) + originY,
  ];
}

1.1.2 端點半圓的起止角度

圖4 - 半圓

在 canvas 中繪製一個 arc,須要知道其起始角度和終止角度。因爲 canvas 繪製默認方向爲屏幕順時針方向(屏幕 Z軸 的左手螺旋方向),從上面的示意圖中能夠看出:code

起始端半圓弧度範圍 - [radianStart - Math.PI, radianStart]
終止端半圓弧度範圍 - [radianEnd, radianEnd + Math.PI]

1.1.3 繪製半圓

有了端點座標和起止角度,即可以繪製端點的半圓:

// 以起始端的半圓倒角爲例
myCanvas.context.arc(
  x,
  y,
  (radiusOutter - radiusInner) / 2,     // 小圓半徑,等於圓環線寬的一半
  radianStart - Math.PI,
  radianStart
);

直角倒角風格的繪製與半圓倒角圓弧的繪製步驟基本相同,主要差異在於不用繪製圓弧兩個端點的小半圓,改爲繪製直線。背景圓弧的繪製也與前景圓弧方法一致。

2. Canvas 實現錐形漸變

上面的步驟能夠繪製出圓弧的輪廓,要達到 圖1 那樣的視覺效果,咱們須要給前面繪製出來的輪廓填充圖像。

沿着圓周方向的漸變,由於其圖像形似圓錐體的俯瞰效果,俗稱錐形漸變:

圖5 - 錐形漸變

衆所周知,CSS 中有一個名爲 conic-gradient 的屬性直接支持錐形漸變,而 HTML Canvas 的原生 API 目前尚未相似的能力。那麼,咱們如何在 canvas 中繪製出這樣的圖像呢?

下面咱們講下大體的原理:

(1)對用戶傳入的顏色進行插值,獲得一個顏色序列。

這裏,咱們直接使用 canvas 原生的 createLinearGradient 方法,在離屏 canvas 中繪製一個 1px 的線性漸變效果,圖像寬度正好是咱們要插值的數量,漸變插值的結果也就是 canvas 上對應像素位置的色值。

圖6 - 1px線性漸變

顏色插值(漸變取色)代碼實現以下:

// 用於實現顏色插值的工具類
export default class ColorInterpolate {
  // 參數01: stops - 爲要插值的顏色序列,數據格式形如:[[0, 'red'], [0.5, 'green'], [1.0, 'yellow']]
  // 參數02: segment - 插值段落數,即插值結果的顏色值的數量
  constructor(stops = [], segment = 100) {
    // 構建離屏 canvas
    const canvas = document.createElement('canvas');
    canvas.width = segment;
    canvas.height = 1;
    this.ctx = canvas.getContext('2d');

    // 繪製線性漸變
    const gradient = this.ctx.createLinearGradient(0, 0, segment, 0);
    for (let [offset, color] of stops) {
      gradient.addColorStop(offset, color);
    }

    this.ctx.fillStyle = gradient;
    this.ctx.fillRect(0, 0, segment, 1);
  }

  // 根據位置偏移量獲取插值後的色值
  getColor(offset) {
    const imgData = this.ctx.getImageData(offset, 0, 1, 1);
    return `rgba(${imgData.data.slice(0, 3).join(',')}, ${imgData.data[3] / 255})`;
  }
}

(2)以下圖所示,咱們能夠把漸變的圖像當作是由足夠多填充了單個色值的小 「扇面」 拼接而成。

圖7 - 「扇面法」繪製錐形漸變

按照這樣的思路,咱們只須要遍歷上面色彩插值獲得的各個顏色,而後逐個繪製小扇面,即可獲得一個錐形漸變圖像。

爲此咱們封裝了一個名爲 createConicalGradient 的方法,其使用習慣與 canvas 原生的 createLinearGradient 和 createRadialGradient 方法類似。具體代碼見 個人 Github(以爲有用的童鞋能夠 star 一下)。

3. 過渡動畫

3.1 圓弧的過渡

在數值發生改變時,咱們的圖表須要一個可以跟隨數據改變的過渡動畫效果,對於 canvas 而言,即是清除舊圖像而後繪製新一幀圖像。這裏有一些方法包裝上的技巧:

// 注:僞代碼,真實場景建議 OOP 方式包裝爲工具類

let _animTick = null;
let _animFrames = null;
let _frameData = null;
let _animDiff = null;

// 動畫方法
function _animate(duration) {
  if (_animTick === null) {
    // 根據動畫時長 duration 計算整個動畫一共須要多少幀(以 60fps 計算)
    _animFrames = Math.round((duration / 1e3) * 60);

    // 相鄰兩幀動畫的數據變化
    _animDiff = _calcAnimDiff(_animFrames);

    // 動畫幀數標識
    _animTick = 0;
  }

  // 當前幀的數據值
  _frameData = _caclCurentData(_animDiff, _animTick);
  _renderFrame(_frameData);

  if (_animTick !== null && _animTick < _animFrames) {
    // 繼續執行動畫
    window.requestAnimationFrame(() => {
      _animate();
      _animTick += 1;
    });
  } else {
    // 動畫結束
    _renderFrame(_frameData);
    _animTick = null;
  }
}

// 繪製當前幀
function _renderFrame(data) {
  // ...
}

// 計算動畫相鄰幀的數據差別
function _calcAnimDiff() {
  // ...
}

// 根據兩幀數據差計算當前幀
function _caclCurentData() {
  // ...
}

3.2 兩種動畫模式

在過渡動畫執行的過程當中,須要考慮兩種不一樣的模式:一種是漸變圖像不變化,僅是圓弧的輪廓從舊狀態變化到新狀態;一種是漸變圖像的夾角範圍跟隨輪廓的大小改變。

這兩種模式其實都有必定意義:前者可使用不一樣顏色表明數值的不一樣狀態;後者僅僅是將漸變的顏色當作一種裝飾效果。

4. 結果演示

比較關鍵的原理都介紹完了,最後展現一下咱們封裝的圖表組件的效果(右側 GUI 部分是咱們自研的設計引擎的編輯效果):

圖8 - 圓環組件效果

5. 寫在後面

本文是可視化圖表開發的一個小案例,也是【圖表開發小案例】這個系列的第一篇。篇幅比較短,還有不少細節沒有展開討論。感興趣的童鞋歡迎交流、探討,請多多關注咱們後面的推文 ~(^_^)Y