G2 定製儀表盤實踐

咱們打開 G2 看到的儀表盤以下圖左邊的樣子,可是這個儀表盤看起來有點生硬。下圖中右邊的儀表盤是否是樣子更炫酷了一些呢?那麼如何繪製一個這樣的儀表盤呢?接下來咱們就來剖析一下這樣的一個儀表盤是怎樣作出來的,並在文章最後附上所有代碼供你們參考。html

儀表盤4

指針的繪製

分析 G2 儀表盤核心代碼 源代碼請看這裏數組

var Shape = G2.Shape;
// 自定義Shape 部分
Shape.registerShape('point', 'pointer', {
  drawShape: function drawShape(cfg, group) {
    var center = this.parsePoint({ // 獲取極座標系下畫布中心點
      x: 0,
      y: 0
    });
    // 繪製指針
    group.addShape('line', {
      attrs: {
        x1: center.x,
        y1: center.y,
        x2: cfg.x,
        y2: cfg.y,
        stroke: cfg.color,
        lineWidth: 5,
        lineCap: 'round'
      }
    });
    return group.addShape('circle', {
      attrs: {
        x: center.x,
        y: center.y,
        r: 9.75,
        stroke: cfg.color,
        lineWidth: 4.5,
        fill: '#fff'
      }
    });
  }
});

該部分自定義了儀表盤的指針形狀,由一條線和一個圓圈組合而成。ide

drawShape 函數中第一個參數 cfg 中攜帶了數據當前點座標和顏色等信息,從而指針的指向會隨數據指標而變化。函數

那麼重寫該部分,就能夠將 G2 原生的指針變成咱們想要的樣子了。可是不變的是指針的指向須要隨數據指標而變化。如此一來,繪製一個箭頭型指針就變成了一個數學問題:已知圓心 (xc, yc),圓上任意一點 (x, y),繪製一個箭頭型指針從 (xc, yc) 指向 (x,y)測試

解題步驟

以儀表盤的原心座標爲原心創建座標系如圖,求出關鍵點 (x0, y0) (x1, y1)(x2, y2)(x3, y3)的位置。ui

座標軸1

(1)首先 (x0, y0)(x2, y2) 兩個點都在由 (xc, yc)(x, y) 兩點組成的線段上,因此 :this

(x0-xc, y0-yc) = λ1(x-xc, y-yc) => (x0, y0) = (λ1*(x-xc) + xc, λ1*(y-yc) + yc)spa

(x2-xc, y2-yc) = λ2(x-xc, y-yc) => (x2, y2) = (λ2*(x-xc) + xc, λ2*(y-yc) + yc)3d

​ 其中 λ1,λ2 都是可調節的參數在0到1範圍內,它們決定了箭頭的起始和終止位置。指針

(2)而後計算 (x1, y1)

​ 從 (x1, y1)(xc, yc)(x,y) 組成的線段作一條垂直線段,設該線段的長度是 d(如圖紅色部分),垂直點爲 (xd, yd)

​ 與步驟一同理,(xd, yd) 是由 (xc, yc)(x, y) 兩點組成的線段上的點,因此:

(xd-xc, yd-yc) = λd(x-xc, y-yc) => (xd, yd) = (λd*(x-xc) + xc, λd*(y-yc) + yc)

​ 線段 d 的傾斜角度與向量 (x ,y) 存在某種關係,冥冥中咱們能感受到,經過 (xd, yd)d 咱們能計算出 (x1, y1) 的位置,那麼如何計算呢?

​ 咱們以 (xd, yd) 爲原心,再次創建座標系,以下圖:

座標軸2

​ 放大該新的座標系,並創建輔助線:從 (x1, y1) 向橫座標軸作一條垂直線段以下圖:

座標軸3

​ 圖中出現了三個相等的角,如圖藍色的部分,因此採用三角函數可得:

計算x1

​ sin α1 = sin α3 => (xd - x1) / d = (y-yc) / √( (x-xc)² + (y-yc)²)

​ 把 xd = λd * (x-xc) + xc√( (x-xc)² + (y-yc)²) = r 帶入上式獲得:

(λd * (x-xc) + xc - x1) / d = (y-yc) / r

​ => x1 = λd (x-xc) + xc - (d/r) ( y - yc)

​ 那麼 x1 就經過 (x, y)(xc, yc) 表達出來了。

計算 y1

​ cos α1 = cos α3 => (y1 - yd) / d = (x-xc) / √( (x-xc)² + (y-yc)²)

​ 把 yd = λd * (y-yc) + yc√( (x-xc)² + (y-yc)²) = r 帶入上式獲得:

​ (y1-(λd * (y-yc) + yc )) / d = (x-xc) / r

​ => y1 = λd (y-yc) + yc + (d/r) ( x - xc)

(3)計算 (x3, y3)

​ 與(2)中同理,從 (x3, y3)(xc, yc)(x,y) 組成的線段作一條垂直線段,設該線段的長度是 d(如圖紅色部分),垂直點爲 (xd, yd)。以下圖:

座標軸4

因爲箭頭型指針是一個對稱圖形,由數學知識可知 改(xd, yd) 點即爲(2)中的 (xd, yd) 點:

(xd-xc, yd-yc) = λd(x-xc, y-yc) => (xd, yd) = (λd*(x-xc) + xc, λd*(y-yc) + yc)

此時,三個相等的角 α1 α2 α3的位置變爲上圖藍色所示,

計算 x3

​ sin α1 = sin α3 => (x3 - xd) / d = (y-yc) / √( (x-xc)² + (y-yc)²)

xd = λd * (x-xc) + xc√( (x-xc)² + (y-yc)²) = r 帶入上式獲得:

​ (x3 - (λd * (x-xc) + xc)) / d = (y - yc) / r

​ => x3 = λd (x-xc) + xc + d/r (y - yc)

計算 y3

​ cos α1 = cos α3 => (yd - y3) / d = (x-xc) / √( (x-xc)² + (y-yc)²)

yd = λd * (y-yc) + yc√( (x-xc)² + (y-yc)²) = r 帶入上式獲得:

​ (λd * (y-yc) + yc - y3) / d = (x-xc) / r

​ => y3 = λd (y-yc) + yc - d/r (x-xc)

至此,(x0, y0)(x1, y1)(x2, y2)(x3, y3) 四個關鍵點的位置已經所有計算出來:

x0 = λ1 * (x-xc) + xc;
y0 = λ1 * (y-yc) + yc;
x1 = λd * (x-xc) + xc - (d/r) * (y - yc);
y1 = λd * (y-yc) + yc + (d/r) * (x - xc);
x2 = λ2 * (x-xc) + xc;
y2 = λ2 * (y-yc) + yc;
x3 = λd * (x-xc) + xc + d/r * (y - yc);
y3 = λd * (y-yc) + yc - d/r * (x - xc);

上列算式中:λ一、λ2 分別是決定指針的起點、終點位置,應介於0、1之間,分別取 0.4四、0.55;

​ λd 是 (xd, yd) 的位置,決定箭頭的折角位置,該值應介於λ一、λ2之間,靠近 λ1, 取0.46;

​ (d/r) 決定指針的胖瘦,取 0.012;

​ x、y在接下來的代碼中應爲 cfg.x、cfg.y;

​ 圓心(xc, yc) 中的 xc 、yc 在接下來的代碼中分別爲 center.x、 center.y。

應用到代碼中去:

Shape.registerShape('point', 'pointer', {
  drawShape: function drawShape(cfg, group) {
    const center = this.parsePoint({
      // 獲取極座標系下畫布中心點
      x: 0,
      y: 0,
    });
    // 繪製指針
    const x0 = (cfg.x - center.x) * 0.44 + center.x;
    const y0 = (cfg.y - center.y) * 0.44 + center.y;
    const x1 = (cfg.x - center.x) * 0.46 + center.x - (cfg.y - center.y) * 0.012;
    const y1 = (cfg.y - center.y) * 0.46 + center.y + (cfg.x - center.x) * 0.012;
    const x2 = (cfg.x - center.x) * 0.55 + center.x;
    const y2 = (cfg.y - center.y) * 0.55 + center.y;
    const x3 = (cfg.x - center.x) * 0.46 + center.x + (cfg.y - center.y) * 0.012;
    const y3 = (cfg.y - center.y) * 0.46 + center.y - (cfg.x - center.x) * 0.012;

    group.addShape('path', {
      attrs: {
        path: `M ${x0} ${y0} L ${x1} ${y1} L ${x2} ${y2} L ${x3} ${y3} Z`,
        lineWidth: 10,
        lineJoin: 'dot',
        stroke: '#5571F7',
      },
    });

    return group.addShape('circle', {
      attrs: {
        x: center.x,
        y: center.y,
        r: 3,
        stroke: '#5571F7',
        lineWidth: 4.5,
        fill: '#5571F7',
      },
    });
  },
});

測試辦法將上述代碼,替換掉 G2 測試代碼中的相應部分,運行查看效果。改造先後的樣子以下:

儀表盤5

形狀的改變

觀察文章開篇兩個圖表起始弧度與截止弧度也有差別:

chart.coord('polar', {
  startAngle: -9 / 8 * Math.PI,
  endAngle: 1 / 8 * Math.PI,
  radius: 0.75
});

修改成:

chart.coord('polar', {
  startAngle: -10 / 8 * Math.PI,
  endAngle: 2 / 8 * Math.PI,
  radius: 0.75
});

儀表盤6

數據改變

從 0 到 9 改變到 0 到 100

chart.scale('value', {
  min: 0,
  max: 9,
  tickInterval: 1,
  nice: false
});

修改成:

chart.scale('value', {
  min: 0,
  max: 100,
  tickInterval: 1,
  nice: false
});

可是你會發現一個問題,儀表盤從下圖中左邊的樣子變爲右邊的樣子,背景顏色少了一大半,究其緣由是什麼呢?咱們接下來看圓弧的繪製。

儀表盤7

圓弧的繪製

分析 G2 中圓弧的繪製部分,分爲兩步:儀表盤灰色背景的繪製,指標數據的繪製。

// 繪製儀表盤背景
chart.guide().arc({
  zIndex: 0,
  top: false,
  start: [0, 0.945],
  end: [9, 0.945],
  style: { // 底灰色
    stroke: '#CBCBCB',
    lineWidth: 18
  }
});
// 繪製指標
chart.guide().arc({
  zIndex: 1,
  start: [0, 0.945],
  end: [data[0].value, 0.945],
  style: {
    stroke: '#1890FF',
    lineWidth: 18
  }
});

採用繪製輔助弧線的方式繪製圓弧,start、end 分別表示圓弧的起始位置。其中 end: [9, 0.945],數組中第一項表示 value 維度,的二項表示半徑維度。因此在 value 從 0 到 9,變爲 0 到 100 時,灰色背景圓弧的截止位置應變爲 end: [100, 0.945]。

// 繪製儀表盤背景
chart.guide().arc({
  zIndex: 0,
  top: false,
  start: [0, 0.945],
  end: [100, 0.945],
  style: { // 底灰色
    stroke: '#CBCBCB',
    lineWidth: 18
  }
});
// 繪製指標
chart.guide().arc({
  zIndex: 1,
  start: [0, 0.945],
  end: [data[0].value, 0.945],
  style: {
    stroke: '#1890FF',
    lineWidth: 18
  }
});

儀表盤8

儀表盤背景色已經繪製完成,再觀察指標繪製就是另外一段圓弧的疊加。start、end 分別爲圓弧的起始、截止位置,style 中的 lineWidth 爲圓弧的厚度。

繪製陰影和弧線

以上繪製背景和指標的方式,即爲圓弧疊加,繪製外圈的陰影和弧線一樣可用此方式。

追加一段圓弧,用來表示外圈淺灰色陰影:

chart.guide().arc({
  zIndex: 1,
  start: [0, 1.15],
  end: [100, 1.15],
  style: {
    stroke: '#F5F7FB',
    lineWidth: 18
  }
});

分段繪製外圈4段弧線:

// 繪製第一段弧線 value 從 2 到 23 空出 2 個value 的位置顯示 label
chart.guide().arc({
  zIndex: 1,
  start: [2, 1.5],
  end: [23, 1.5],
  style: {
    stroke: '#F5F7FB',
    lineWidth: 2
  }
});
// 繪製第二段弧線 value 從 27 到 48 空出 23到27 之間的位置顯示 label 
chart.guide().arc({
  zIndex: 1,
  start: [27, 1.5],
  end: [48, 1.5],
  style: {
    stroke: '#F5F7FB',
    lineWidth: 2
  }
});
// 繪製第三段弧線
chart.guide().arc({
  zIndex: 1,
  start: [52, 1.5],
  end: [73, 1.5],
  style: {
    stroke: '#F5F7FB',
    lineWidth: 2
  }
});
// 繪製第四段弧線
chart.guide().arc({
  zIndex: 1,
  start: [77, 1.5],
  end: [97, 1.5],
  style: {
    stroke: '#F5F7FB',
    lineWidth: 2
  }
});

經過上述陰影繪製,加上弧線繪製,再把 label 的 offset 作調整,能夠將儀表盤從下圖左邊的樣子變爲下圖中右邊的樣子。

儀表盤9

繪製色彩分段與漸變

色彩的分段與弧形繪製的原理一致,弧形的分段繪製。這一部分在 G2 的分段儀表盤裏也已經有所介紹。不過在這裏我仍然想要梳理一下:

爲了將儀表盤分爲 4 段顏色展現,咱們找到3個 value 的分割點,2五、50、75。

數據所在區間以前的區間拼接規則以下,順序不能改變:

當 value >= 25 時,[0, 25] 區間段顏色塗滿爲 color[0];

當 value >= 50 時,[25, 50] 區間段顏色塗滿爲 color[1];

當 value >= 75 時,[50, 75] 區間顏色段塗滿爲 color[2];

數據所在區間塗色規則以下:

當 value < 25 時,[0, value] 區間顏色塗成 color[0];

當 value > 25 && value < 50 時, [25, value] 區間顏色圖爲 color[1];

當 value > 50 && value < 75 時, [50, value] 區間顏色塗成 color[2];

當 value > 75 時,[75, value] 區間的顏色塗成 color[3];

var color = [
  'l(0) 0:#69B4FA 1:#5AA9FC',
  'l(0) 0:#5AA9FC 1:#546DF6',
  'l(0) 0:#546DF6 1:#5461F7',
  'l(0) 0:#5461F7 1:#474DE2',
];
var value = data[0].value;
value >= 25 && chart.guide().arc({
  zIndex: 1,
  start: [0, 0.945],
  end: [25, 0.945],
  style: {
    stroke: color[0],
    lineWidth: 18
  }
});
value >= 50 && chart.guide().arc({
  zIndex: 1,
  start: [25, 0.945],
  end: [50, 0.945],
  style: {
    stroke: color[1],
    lineWidth: 18
  }
});
value >= 75 && chart.guide().arc({
  zIndex: 1,
  start: [50, 0.945],
  end: [75, 0.945],
  style: {
    stroke: color[2],
    lineWidth: 18
  }
});

value < 25 && chart.guide().arc({
  zIndex: 1,
  start: [0, 0.945],
  end: [value, 0.945],
  style: {
    stroke: color[0],
    lineWidth: 18
  }
});
value < 50 && value > 25 && chart.guide().arc({
  zIndex: 1,
  start: [25, 0.945],
  end: [value, 0.945],
  style: {
    stroke: color[1],
    lineWidth: 18
  }
});
value < 75 && value > 50 && chart.guide().arc({
  zIndex: 1,
  start: [50, 0.945],
  end: [value, 0.945],
  style: {
    stroke: color[2],
    lineWidth: 18
  }
});
value > 75 && chart.guide().arc({
  zIndex: 1,
  start: [75, 0.945],
  end: [value, 0.945],
  style: {
    stroke: color[3],
    lineWidth: 18
  }
});

將指標繪製的部分由上述代碼代替分段,其中 color 爲4個漸變色組成的數組。便可獲得分段漸變的儀表盤了。

儀表盤10

小結

其實儀表盤是一個極爲簡單的圖表,其數據通常只有一個,表達這個數據的在其波動區間內的佔比。那麼自定義它的樣式的難點就轉化到繪製上,經過上述分析和實踐,掌握兩個要點就能繪製本身的儀表盤了:儀表盤的圓弧是疊加畫出來,指針的形狀能夠自定義。

完整代碼

文章可隨意轉載,但請保留此 原文連接
很是歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj(at)alibaba-inc.com 。
相關文章
相關標籤/搜索