D3

一個可以簡單用 JS 操作 SVG 的框架。

推薦資料來源:

使用

主要會使用到包含:

<line> elements for the edges
<text> elements for the labels
<circle> elements to represent the nodes
<g> elements as containers that move the nodes and labels together

1.選擇元素

d3.select('')
d3.selectAll('')

2.設定屬性

d3.select('').attr('width', 20)

設定 style

.style("fill", "#69b3a2")

3.新增元素

d3.select('').append("svg")

4.設定相關範圍,讓數值縮小

const x = d3.scaleLinear()
    .domain([0, 4000]) // 數值範圍
    .range([ 0, width ]); // 實際顯示範圍

5.傳入資料並且遍歷

記得 append 前要用 selectAll 相同東西,不然不會顯示

const data = [{...}...]

d3.select('#test')
    .selectAll('circle')
    .data(data)
    .enter() // 類似forEach
    .append("circle")

6.添加座標軸

const data = [{
  case: 23,
}....]

let aScale = d3
    .scaleLinear()
    .domain([0, d3.max(data, (d) => d.cases)])
    .range([0, 300]);
  
  d3.select("#scatterplot #x-axis")
    .call(d3.axisBottom(aScale).ticks(5));

7. 加上動畫

    .transition()
    .duration(1000)

地圖

typojson 比 geojson 檔案小。

以下範例讀取 typojson 然後用 相關套件轉換為geojson 並顯示到 d3 上

https://github.com/topojson/topojson-client/blob/master/README.md#feature

d3.json('data/world.json').then(world => {
    let projection = d3.geoWinkel3().scale(140).translate([365, 225]);
    let geoPath = d3.geoPath(projection);

    const svg = d3
      .select("#map-chart")
      .append("svg")
      .attr("width", 365 * 2)
      .attr("height", 225 * 2);

    svg
      .selectAll("path")
      .data(topojson.feature(world, world.objects.countries).features)
      .enter()
      .append("path")
      .attr("d", geoPath)
      .attr("class", "country");
});

加上地圖線

    var graticule = d3.geoGraticule();
    svg
      .append("path")
      .datum(graticule)
      .attr("class", "graticule line")
      .attr("d", geoPath);

    svg
      .append("path")
      .datum(graticule.outline)
      .attr("class", "graticule outline")
      .attr("d", geoPath);

css

.graticule {
    fill: none;
    stroke: #777;
    stroke-width: .5px;
    stroke-opacity: .2;
    pointer-events: none;
}
.graticule.outline {
    stroke-width: 2px;
  }

更新 x 與 y 軸

merge 與 exit().remove() 是給傳入 .data 用的,如果是軸要用以下方式判斷

    if(!document.querySelector('.wrapper-group g')) {
      // 第一次 init
      d3.select(".wrapper-group")
        .attr("transform", "translate(180, 10)")
        .append("g")
        .attr("id", "x-axis")
        .attr("transform", `translate(0,${this.height})`)
        .call(d3.axisBottom(xScale).ticks(5));
      d3.select(".wrapper-group")
        .append("g")
        .attr("id", "y-axis")
        .attr("transform", "translate(0,0)")
        .call(d3.axisLeft(yScale).ticks(5));
    } else {
      // 更新 axis
      d3.select(".wrapper-group #x-axis")
      .call(d3.axisBottom(xScale).ticks(5));
      d3.select(".wrapper-group #y-axis")
      .call(d3.axisLeft(yScale).ticks(5));
    }

畫 x, y 軸標籤

必須要手動添加 text,如果沒有顯示試著加在 svg 標籤下

      // 畫 y 軸 標籤
      d3.select(".plot-svg")
        .append("g")
        .attr(
          "transform",
          "translate(" + this.width / 6 + ", " + this.height / 2 + ")"
        )
        .append("text")
        .attr("text-anchor", "middle")
        .attr("transform", "rotate(-90)")
        .text('test');

事件綁定

點擊後觸發顏色改變

      ......
      .on('click', function(e) {
        d3.select(this).style("fill", 'black');
      })

讓顏色變深

      .on('click', function(e) {
        d3.select(this).style("fill", d3.color(d3.select(this).attr("fill")).darker());
      })

隱藏動畫

如果要讓隱藏和顯示有動畫,必須用 opacity,display: none 會沒作用

      d3.select(ele)
        .select("text")
        .transition()
        .duration(600)
        .attr("opacity", switchOn ? 1 : 0);

Forcing

類似於物體碰撞等物理效果

https://github.com/d3/d3-force

v3 之後的用法有改變

範例:

https://codepen.io/EasonWang01/pen/RwRoXZbhttps://bl.ocks.org/HarryStevens/f636199a46fc4b210fbca3b1dc4ef372

Last updated