D3.js是一個基於HTML/SVG/CSS的資料視覺化庫,是領域內非常強大的存在了。
今後會在工作使用,也因此開始了自學之旅。學習過程中,我主要通過Curran Kelleher老師的系列教程進行學習,這個筆記用於學習、整理和分享,陸續學習、更新和記錄中....
原文連結
學習目的:
- 熟悉和認識D3.js
- 練習使用SVG畫圖,熟悉操作
- 練習D3.js的基本函式和操作
完成效果圖: 線上demo
D3初印象
選擇集
D3是實現資料視覺化,仍然離不開傳統DOM的選擇和操作,也因此,D3提供了類似Jquery的DOM操作指令:
d3.select
:選擇第一個指定元素d3.selectAll
: 選擇所有的元素
const svg = d3.select('svg') //選擇svg
const p = svg.selectAll('p') //選擇svg下所有的p標籤
複製程式碼
當然,可以使用#id
以及 .class
對id和類進行選擇
檢視狀態
d3.select
和d3.selectAll
返回的都是選擇集,新增、刪除以及修改都需要用到選擇集,檢視狀態有三個函式可以使用:
selection.empty()
選擇集為空,返回true,否則返回falseselection.node()
返回第一個非空元素,如果選擇集為空,返回nullselection.size()
返回選擇集中的元素個數
設定和獲取屬性
使用select
或selectAll
選擇後,可以通過attr
獲取和設定屬性,可以使用append
方法新增元素
const svg = select('svg');
svg.append('circle')
.attr('r','30')
複製程式碼
使用D3和SVG畫圖
html檔案
<html lang="en">
<head>
<title>Smile face with d3</title>
</head>
<body>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="index.js"></script>
</body>
</html>
複製程式碼
主要內容通過index.js實現:
第一步:通過d3選擇SVG,使用circle
構建輪廓
const svg = d3.select('svg');
const height = +svg.attr('height');
const width = +svg.attr('width');
svg.append('circle')
.attr('r',height / 2)
.attr('cx', width / 2)
.attr('cy', height / 2)
.attr('fill', 'yellow')
.attr('stroke','black')
const leftEye = svg.append('circle')
.attr('r', 30)
.attr('cx', width / 2 - 100)
.attr('cy', height / 2 - 80)
.attr('fill', 'black')
const rightEye = svg.append('circle')
.attr('r', 30)
.attr('cx', width / 2 + 100)
.attr('cy', height / 2 - 80)
.attr('fill', 'black')
複製程式碼
要點:
- 通過
attr
獲取的屬性是string
型別的,通過parseFloat
或者+
轉換為number
型別 - 對圓形
circle
的屬性cx
cy
等,使用變數作為其大小設定的值,而不用一個數字,方便日後的維護 - leftEye和RightEye用類似的方法,創造出來,並將眼睛放置在合適的位置。
程式碼優化
1)可以發現,對三個圓圓心的操作cx
和cy
出現了多次,而作用也僅僅是為了將圓放在中心。因此,可以通過SVG中的<g>
來分組來實現一次性操作。
const g = svg.append('g')
.attr('transform',`translate(${ width / 2}, ${ height / 2})`)
//這樣,在畫圓的時候,就可以去掉對其圓心的操作,因為預設的位置就在中心了
const circle = g.append('circle')
.attr('r',height / 2)
.attr('fill', 'yellow')
.attr('stroke','black')
複製程式碼
2)同樣的,對於眼睛的操作,也有點繁瑣,可以通過變數和分組,提高程式碼的可維護效能。
const eyeSpacing = 100;
const eyeYoffset = -80
const eyeRadius = 30;
const eyesG = g.append('g')
.attr('transform', `translate(0, ${eyeYoffset})`);
const leftEye = eyesG.append('circle')
.attr('r', eyeRadius)
.attr('cx', - eyeSpacing)
const rightEye = eyesG.append('circle')
.attr('r', eyeRadius)
.attr('cx', + eyeSpacing)
複製程式碼
第二步 嘴巴
嘴巴實際上是一個弧線,使用SVG中的path
進行繪製。熟悉path
的,當然可以直接給引數進行繪製,但是d3對圓弧有更好的支援,可以使用d3.arc
函式,方便的繪製圓弧。
arc函式
根據官方的API手冊,函式的使用方法如下:
var arc = d3.arc();
arc({
innerRadius: 0,
outerRadius: 100,
startAngle: 0,
endAngle: Math.PI / 2
}); // "M0,-100A100,100,0,0,1,100,0L0,0Z"
複製程式碼
其中,innerRadius是內圓半徑,outerRadius是外圓半徑,startAngle和endAngle分別是開始和結束的弧度(完整的圓是0~2PI)
笑臉的實現
根據以上內容,程式碼如下:
const mouth = g.append('path')
.attr('d',d3.arc()({
innerRadius: 150,
outerRadius: 170,
startAngle: Math.PI /2,
endAngle: Math.PI * 3 / 2
}))
複製程式碼
眉毛
眉毛用簡單的長方形來代替,也就是svg中的<rect>
,使用前文的程式設計風格,將眉毛歸為一個group,並將位置設定。
const eyebrowWidth = 50;
const eyebrowHeight = 10;
const eyebrowYoffset = -150;
const BrowG = g.append('g')
.attr('transform',`translate(${-eyebrowWidth / 2},${eyebrowYoffset})`);
const leftEyebrow = BrowG.append('rect')
.attr('width',eyebrowWidth)
.attr('height',eyebrowHeight)
.attr('x',-eyeSpacing)
const rightEyebrow = BrowG.append('rect')
.attr('width', eyebrowWidth)
.attr('height', eyebrowHeight)
.attr('x', eyeSpacing)
複製程式碼
加入動畫
使用transition
函式設定眉毛的動畫,讓笑臉動起來
const BrowG = g.append('g')
.attr('transform',`translate(${-eyebrowWidth / 2},${eyebrowYoffset})`);
BrowG.transition().duration(2000)
.attr('transform', `translate(${-eyebrowWidth / 2},${eyebrowYoffset - 50})`)
.transition().duration(2000)
.attr('transform', `translate(${-eyebrowWidth / 2},${eyebrowYoffset})`).duration(2000)
複製程式碼
注意點:
transition
函式要對號入座,即如果加在了<g>
上,對應的增加的屬性動畫應該在transform
上; 如果動畫在<rect>
上,動畫應該在y
屬性上。- 新增動畫不能在
append
函式之後連線,比如BrowG.append('g').attr(...).transition()
是不行的。因為過度動畫無法繫結在append
的元素上,需要分別操作。
完整程式碼
const svg = d3.select('svg');
const height = +svg.attr('height');
const width = +svg.attr('width');
const g = svg.append('g')
.attr('transform',`translate(${ width / 2}, ${ height / 2})`)
const circle = g.append('circle')
.attr('r',height / 2)
.attr('fill', 'yellow')
.attr('stroke','black')
const eyeSpacing = 100;
const eyeYoffset = -80
const eyeRadius = 30;
const eyebrowWidth = 50;
const eyebrowHeight = 10;
const eyebrowYoffset = -150;
const eyesG = g.append('g')
.attr('transform', `translate(0, ${eyeYoffset})`);
const leftEye = eyesG.append('circle')
.attr('r', eyeRadius)
.attr('cx', - eyeSpacing)
const rightEye = eyesG.append('circle')
.attr('r', eyeRadius)
.attr('cx', + eyeSpacing)
const mouth = g.append('path')
.attr('d',d3.arc()({
innerRadius: 150,
outerRadius: 170,
startAngle: Math.PI /2,
endAngle: Math.PI * 3 / 2
}))
const BrowG = g.append('g')
.attr('transform',`translate(${-eyebrowWidth / 2},${eyebrowYoffset})`);
BrowG.transition().duration(2000)
.attr('transform', `translate(${-eyebrowWidth / 2},${eyebrowYoffset - 50})`)
.transition().duration(2000)
.attr('transform', `translate(${-eyebrowWidth / 2},${eyebrowYoffset})`).duration(2000)
const leftEyebrow = BrowG.append('rect')
.attr('width',eyebrowWidth)
.attr('height',eyebrowHeight)
.attr('x',-eyeSpacing)
const rightEyebrow = BrowG.append('rect')
.attr('width', eyebrowWidth)
.attr('height', eyebrowHeight)
.attr('x', eyeSpacing)
複製程式碼