D3.js, 數據可視化入門筆記

D3入門

D3(Data Driven Docuemtns)是一套非常優秀的數據可視化庫,它可以幫助開發者在瀏覽器中直觀地展現各種數據。

雖然這個工具本身非常強大,但是其學習門檻並不低。其中一個原因在於教程的不友好,新手學習起來很容易沒有頭緒。官方給出的 tutorials 一方面更新不及時,導致和API文檔對不上號,用v3的教程對照v5的API文檔看的一頭霧水。二來很多例子也相對複雜,缺少一些循序漸進的講解。

這裏將會分享如何實現一些簡單的數據可視化圖形。

Fundamentals 入門教學

首先推薦一篇優秀的 d3 基礎教程

如何製作一個餅圖/環形圖(pie/donut)

我們可以把一個數組 [110, 12, 18] ,用環形圖的方式展示。

源代碼在這裏查看。

像這樣的餅圖,可以分成三個部門:環形(Arc)、文字(Label)和連接環形文字之間的連線(Line)。

Arc

在這裏每一條數據對應的環形,可以使用 SVG 裏的 Path 元素來表示,通過給 Path 傳遞描述圖形形狀的參數,便可以在瀏覽器中渲染出我們需要的圖形。爲了畫出環形,我們需要幾個基本信息:

  • 環內徑 (innerRadius)
  • 環外經 (outerRadius)
  • 起始弧度 (startAngle)
  • 終止弧度 (endAngle)

這裏使用弧度來表示環的起始和終止位置,而不是角度。長度等於半徑的弧長對應的弧度定義爲1,所以對於一個完整的圓,弧度是 2 * PI。後面我們會用到中間弧度 (midAngle) 來判斷環在左半邊圓還是右半邊圓,這可以幫助我們定位數據標籤 (label)。

d3-shape 可以幫我們計算需要的圖形參數。調用 d3.pie() 會返回一個 pie 生成器 (generator)。這個生成器可以把傳入的數據轉化成扇形/環形相應的弧度。而爲了得到環形的形狀描述(path),我們還要用到 d3.arc 生成器,並傳入內外徑參數,有了弧度和半徑參數之後,才能生成實際的圖形。

// 指定了兩個相關函數:value accessor 和 sort
// value 函數表示如何從單個 data 中取取到值
// sort 函數如果沒有專門指定的話,會默認降序排列
const pie = d3.pie().value((d) => d).sort(null);
const slices = pie(data);
// slices 的結構如下,注意 slices 的排序和給定的 data 排序一致,index 對應 sort 之後的順序。

[
  {
    data: number,
    value: number,
    index: number,
    startAngle: number,
    endAngle: number,
    padAngle: number,
  },
  ...
]

// 帶有內外徑的 arc 生成器
const innerArc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius);

// D3 的選擇 (selection) 部分
const slice = d3.select('.slices').selectAll('path').data(slices);

slice.enter()
  .append('path')
  .attr('d', (d, i) => innerArc(slices[i]))
  .attr('transform', `translate(${width / 2}, ${height / 2})`)
  .attr('fill', (d, i) => colorArray[i % (colorArray.length)]);

slice.exit().remove();

Line

Line 是連接環形圖心和文字標籤之間的連線,可以用 Polyline 元素渲染。爲了繪製連線,我們需要知道起點、轉折點和終點。

連線的起點是環的圓心,轉折點是外層一個不可見的同弧度環的圓心,終點根據環的位置判斷,如果環在左半邊圓,那麼從轉折點往左平移一段距離,在右邊則向右平移一段距離。

 const endPoints = []; // 記錄下終點,爲文字標籤定位
 
      const pivotArc = d3.arc().innerRadius(outerRadius).outerRadius(pivotRadius);
      const line = d3.select('.lines').selectAll('polyline').data(slices);
      line.enter()
        .append('polyline')
        .attr('points', (d, i) => {
          const slice = slices[i];
          const innerCentroid = innerArc.centroid(slice);
          const x1 = innerCentroid[0] + width / 2;
          const y1 = innerCentroid[1] + height / 2;
          const pivotPoint = pivotArc.centroid(slice);
          const x2 = pivotPoint[0] + width / 2;
          const y2 = pivotPoint[1] + height / 2;
          const midAngle = getMidAngle(slice);
          const x3 = x2 + (midAngle > Math.PI ? -20 : 20);
          const y3 = y2;
          endPoints[i] = [x3, y3];
          return `${x1},${y1} ${x2},${y2} ${x3},${y3}`;
        })
        .attr('fill', 'none')
        .attr('stroke', (d, i) => colorArray[i % (colorArray.length)]);
      line.exit().remove();

Label

定位到終點之後,我們可以添加文字標籤。

const label = svg.select('.labels').selectAll('text').data(slices);      

label.enter()
  .append('text')
  .text((d) => d.value)
  .attr('transform', (d, i) => {
    const x = endPoints[i][0] + (getMidAngle(d) > Math.PI ? -10 : 10);
     const y = endPoints[i][1] + 5;
     return `translate(${x}, ${y})`;
   })
   .attr('text-anchor', (d) => {
     const midAngle = getMidAngle(d);
     return midAngle > Math.PI ? 'end' : 'start';
   });
   
label.exit().remove();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章