D3.js 簡介 (Part 1)

I. HTML Elements

常見的HTML元素

<h1>, <h2>, ... , <h6>

Header, 即標題,h1字體最大,h6字體最小

<p>

paragraph,段落,會自成一行

<span>

文字,不自成一行,能放在段落或其他物件中以表示與段落中的文字不同的性質

<div>

單獨存在沒有意義,但是使用<div>可以方便排版設計以及做階層化的管理

<svg>

向量圖

<g>

通常用於<svg> 中,能將向量圖中的元素分組管理

<script>

JavaScript程式碼,可以放在<head>或是<body>中,不過要注意不能存取到尚未初始化的html物件

<script src:"...">

要使用外部(或是獨立的)JavaScript程式碼時使用

<style>

CSS Style,定義 CSS 的性質。

ID 與 class

可以在 html 物件中加入 idclass 屬性,通常是方便定義 css 屬性或是方便 JavaScript 存取用

<tag id='idName' class='className'></tag>

Class

class 用來指定物件的類別,通常用途相近的物件會定義相同的類別

id

id 用來指定物件的名稱,原則上不能將兩個物件定義相同的id,
不過即使違反這個原則,網頁還是能夠顯示,但是在 css 或是 JavaScript 取用 上會出現問題。

In [ ]:
%%html
<h1>標題1</h1>
<h2>標題2</h2>
<h3>標題3</h3>
<h4>標題4</h4>
<h5>標題5</h5>
<h6>標題6</h6>

<p>這是內文,<span style='color: blue'>這是span</span></p>

<div style='width:100%; align:center;background:aliceblue'>
    <span style='display:block;margin:auto;text-align:center'>這是div</span>
</div>

<div id='time'>
    <span>用 JavaScript 控制 html 的元素<br></span>
    <span></span>
</div>

<script>
    //每次執行時取用id為'time'的物件並將期中的第2個子元素的值設為目前的時間
    document.getElementById('time').children[1].innerHTML = (new Date()).toTimeString().split(' ')[0]
</script>

II. D3.js

D3.js (Data-Driven Documents) 是一個 JavaScript 的函式庫,能夠建立動態的視覺化圖形

使用 D3.js

In [ ]:
%%html
<script src="https://d3js.org/d3.v6.js"></script>

在網頁中加入以上程式碼以引入 D3 函式庫

D3.js 的基本功能

Selection

使用 d3.select() 能夠使用 D3 選擇 html 物件
能夠依據 tag, class, id 來選擇物件

d3.select('tagName')
d3.select('.className')
d3.select('#idName')

或是可以結合使用

d3.select('tagName.ClassName')
d3.select('.className#tagName')
...

Modifier Functions

  • d3.select().style()
  • d3.select().attr()
  • d3.select().classed()
  • d3.select().property()
  • d3.select().text()
  • d3.select().html()
In [ ]:
%%html
<g>Hello World!<g>
In [ ]:
%%html
<g class='gWithClass'>gWithClass</g>
<g id='gWithId'>gWithId</g>
In [ ]:
%%html
<script>
    d3.select('.gWithClass').html('helloClass')
    d3.select('#gWithId').html('helloId')
</script>

選擇多個物件

使用 d3.selectAll() 可以一次選擇多個物件,使用方法大致與 d3.select() 相同

In [ ]:
%%html
<g class='multiClass'>g1</g>
<g class='multiClass'>g2</g>
In [ ]:
%%html
<script>
    d3.selectAll('.multiClass').html('modified')
</script>

Function Chaining

透過 d3 取得的物件能夠連續套用多個不同的操作

In [ ]:
%%html
<text class='texts'>this is text1<br></text>
<text class='texts' id='text2'>this is text2<br></text>
<text class='texts'>this is text3<br></text>
In [ ]:
%%html
<script>
    d3.selectAll('.texts').style('color','red')
        .style('font-size', '16px')
        .classed('red', true)
</script>
In [ ]:
%%html
<script>
    d3.selectAll('#text2').style('color', 'blue')
        .style('font-size', '30px')
        .classed('red', false)
        .classed('blue', true)
</script>
In [ ]:
%%html
<script>
    d3.selectAll('.red').style('color', 'black')
</script>

append() Function

可以使用 append() 來增加子物件

In [ ]:
%%html
<div id='canvas1'></div>
In [ ]:
%%html
<script>
    let svg0 = d3.select('#canvas1').append('svg')
        .style('width', '500')
        .style('height', '500')
        .style('background', 'blue')
</script>

繪製 D3.js 圖表

定義 svg 區域

In [ ]:
%%html
<div id='canvas2'></div>
In [ ]:
%%html
<script>
    let svg1 = d3.select('#canvas2').append('svg')
        .style('width', '500')
        .style('height', '500')
</script>

繪製矩形

In [ ]:
%%html
<script>
    svg1.append("rect")
        .attr("x", 10)
        .attr("y", 10)
        .attr('width', 100).attr('height', 100)
        .style("fill", "blue")
</script>

根據資料製作圖表

定義新的畫布

In [ ]:
%%html
<div id='canvas3'></div>

<script>
    let svg3 = d3.select('#canvas3').append('svg')
        .style('height', '220') //將高度設為200
</script>

載入資料

這組資料是隨機生成的

In [ ]:
%%html
<script>
    let data = [1535, 3081, 2494, 9078, 9843, 6856, 234, 529, 6729, 2321]
</script>

根據資料生成矩形

使用 selectAll('rect') 選擇物件中的所有矩形
接著呼叫 data(data) 可以載入外部的資料,載入資料後
程式會計算資料和選擇的物件之間的數量差,接下來,
如果要新增矩形,則呼叫 enter() 之後用 append('rect') 新增矩形
如果要減少矩形,則呼叫 exit() 之後用 remove() 刪除矩形
接下來就可以設定每一個矩形的位置及高度等屬性

Enter and exit(D3 in Depth)

In [ ]:
%%html
<script>
    let rects = svg3.selectAll("rect")
        .data(data)
        .enter()
        .append("rect")
        .attr("x", (d, i) => {return i*10+10})
        .attr("y",10)
        .attr('height', 20)
        .attr('width', 5)
        .style("fill", "red")
</script>

接下來要利用 scaleLinear() 計算每個矩形的位置
也就是做線性變換
使用 .domain() 輸入資料的上下限
使用 .range() 輸入圖表的顯示範圍

d3提供多種線性變換模型,可以根據資料選擇適合的模型
d3-scale說明(github)

In [ ]:
%%html
<script>
    let xScale = d3.scaleLinear()
        .domain([0, data.length])
        .range([0, 200])
    let yScale = d3.scaleLinear()
        .domain([0, 10000])
        .range([100, 0])
</script>

接著將 scaleLinear() 計算的結果套用到圖形中

In [ ]:
%%html
<script>
    rects.attr("x", (d, i) => xScale(i) + 50)         //50為座標軸預留的空間
        .attr("y", (d, i) => yScale(d) + 100)         //100為座標軸預留的空間
        .attr('height', (d, i) => 100 - yScale(d))    //因為 scaleLinear 的 range 為 [100, 1],因此矩形的長度要用 100 扣掉
        .attr('width', 15)
</script>

加入座標軸

先用 d3.axisBottom(xScale) 以及 d3.axisLeft(yScale) 定義座標軸

In [ ]:
%%html
<script>
    let xAxis = d3.axisBottom(xScale)
    let yAxis = d3.axisLeft(yScale)
</script>

接著將座標軸繪製到圖上

In [ ]:
%%html
<script>
    svg3.append('g').attr('class', 'axis')
        .attr('transform', 'translate(50, 100)') //y軸的位置
        .call(yAxis)
    
    svg3.append('g').attr('class', 'axis')
        .attr('transform', 'translate(50, 200)') //x軸的位置
        .call(xAxis)
</script>

D3.js 的矩陣操作

D3.js 提供了多個矩陣操作的函式

let arr

d3.max(arr)
d3.min(arr)
d3.extent(arr)    //同時找到最小及最大的元素
d3.sum(arr)
d3.median(arr)    //中位數
d3.mean(arr)      //平均數
arr.sort(d3.ascending)  //順序排序
arr.sort(d3.descending) //倒序排序
d3.quantile(arr.sort(d3.ascending), 0.25) //第一四分位數(0.25的位置)
d3.deviation(arr) //標準差

III. 其他參考資料

In [ ]: