導(dǎo)讀
此文乃<Moreal D3.js Wiki>的學(xué)習(xí)筆記(https://kb.moreal.co/d3/), 簡略看了一下感覺甚好. 以下這段話摘自此網(wǎng)站:
What is this document and who is it intended for?
Since Mike Bostock, creator of the D3 library, shared the excellent D3.js library with all of us, we thought we should give something back in return to the community. Although this document has been initially created for internal use, we felt that sharing it might help some people struggling to get the grasp of the basics (and even some more advanced features) of the vast D3.js library. There might be more efficient ways to handle specific functions, but while we gain more experience, this document will keep evolving with us. Enjoy!
D3 Basic Usage
D3采用鏈?zhǔn)綄懛? select/selectAll選擇元素, enter新增元素:
const dataset = [4, 8, 7, 42, 3];
d3.select('.chart').append('div')
.selectAll('div')
.data(dataset)
.enter().append('div')
.attr('class', 'd3box')
.text(d => d);
- 通過append新增一個div.
- 在selectAll虛擬創(chuàng)建N個div(N由data決定)
- 通過data函數(shù), 將數(shù)據(jù)源綁定到dom元素上, 通過enter().append的操作, 將之前虛擬創(chuàng)建的div按照數(shù)組順序, 一一創(chuàng)建出來.
-
給創(chuàng)建出來的每個div, 賦值class和text.
image.png
這里實(shí)際上涉及到d3中重要的三個函數(shù):
- enter: 提供數(shù)據(jù), dom中不存在匹配的元素, 新增.
- update: 提供數(shù)據(jù), dom中存在匹配的元素, 更新.
- exit: 提供元素, 沒有匹配的數(shù)據(jù), 刪除.
d3.selectAll('.d3box').data([32, 12])
.text(d => d)
.exit().remove();
這時候, 非[32, 12]的dom元素將會被刪除.
d3.selectAll('.d3box').data([32, 12, 32, 45, 67, 31, 34, 2])
.enter().append('div')
.attr('class', 'd3box')
.text(d => d);
這時候, 多余出來的數(shù)據(jù), 將被更新到dom上.
Chart Dimensions
我們在繪圖時候, 最好要增加繪圖區(qū)域的margin(外間距). 主要原因有兩個: 1. 為了美觀. 2. 如果存在坐標(biāo)軸, 則正好存在空間繪制.
所以一般代碼都會這么寫:
const width = 960;
const height = 500;
const margin = {top: 20, right: 20, bottom: 20, left: 20};
d3.select('svg')
.attrs({
width: width + margin.left + margin.right,
height: height + margin.top + margin.bottom
});
Labels
一個繪圖, 通常會由label說明:
const xaxisLabel = svg.append('text')
.attrs({
class: 'd3-chart-label label-x-axis',
x: width / 2,
y: height + (margin.bottom / 2),
dy: '1em',
'text-anchor': 'end'
})
.text('x Axis Label');
const yaxisLabel = svg.append('text')
.attrs({
class: 'd3-chart-label label-y-axis',
x: -height / 2,
y: margin.left,
dy: '1em',
transform: 'rotate(-90)',
'text-anchor': 'middle',
})
.text('y Axis Label');
xaxisLabel比較好理解, 將文本水平放置在位置(width / 2, height + (margin.bottom / 2))的位置. 但是由于yaxisLabel的文字是豎向的, 所以我們可以先將文本放在(-height / 2, margin.left)上, 再逆時針旋轉(zhuǎn)90度(rotate(-90))即可.
Scales
Scales通常用于處理domain和range的關(guān)系.
const dataset = [
{"name":"Nick", "level":1232},
{"name":"George", "level":922},
{"name":"Alekos", "level":1651},
{"name":"Kostas", "level":201}
];
const opacityScale = d3.scaleLinear()
.domain([0, d3.max(dataset, d => d.level)])
.range([.2, 1]);
const colorScale = d3.scaleOrdinal()
.domain(["Nick", "George", "Alekos", "Kostas"])
.range(["red", "orange", "yellow", "green"]);
這里通過scaleLinear將[0, 1651]線性映射到[.2, 1]上. 而通過scaleOrdinal將名字和顏色一一對應(yīng)起來.
Axes
常用的坐標(biāo)軸可分為以下幾類:1. 線性坐標(biāo)軸, 例如從0, 1, 2, ...,10作為x軸. 2. 時間坐標(biāo)軸.
const width = 500;
const height = 500;
const margin = {top: 20, right: 20, bottom: 20, left: 20};
const points = [3, 6, 2, 7, 9, 1];
const svg = d3.select('svg').attrs({
width: width + margin.left + margin.right,
height: height + margin.top + margin.bottom
});
const x = d3.scaleLinear().domain([0, 10]).range([margin.left, width + margin.left]);
const y = d3.scaleLinear().domain([d3.min(points), d3.max(points)]).range([height + margin.top, margin.top]);
const gx = svg.append('g').attr('transform', `translate(0,${height + margin.top})`);
const gy = svg.append('g').attr('transform', `translate(${margin.left}, 0)`);
const xAxis = d3.axisBottom(x);
const yAxis = d3.axisLeft(y);
gx.call(xAxis);
gy.call(yAxis);
svg.selectAll('circle').data(points)
.enter().append('circle')
.attrs({cx: (d, i) => x(i), cy: d => y(d), r: 5})
.style('fill', 'green');
這里簡單實(shí)現(xiàn)了一個散點(diǎn)圖, 效果如下:
代碼簡單解釋如下:
- 使用scaleLinear將x軸的[0, 10]映射到寬度[margin.left, width + margin.left]上, 將y軸的[min, max]映射到高度[height + margin.top, margin.top]上. margin的存在導(dǎo)致x/y軸有空間進(jìn)行繪制.
- 為x/y創(chuàng)建繪圖空間gx/gy, 并設(shè)置位置(transform, translate)
- 使用d3.axisBottom/axisLeft分別創(chuàng)建下x坐標(biāo)軸和y坐標(biāo)軸, 并在gx/gy中call, 生成坐標(biāo)軸.
- 繪制散點(diǎn)圖.
Tooltips
任何一個繪圖, 都應(yīng)該存在tooltips, 最簡單的實(shí)現(xiàn)方式是加入: title.
svg.selectAll('circle').data(points)
.enter().append('circle')
.attrs({cx: (d, i) => x(i), cy: d => y(d), r: 5})
.style('fill', 'green')
.append('title')
.text(String);
如果需要更加智能化, 則需要手動編寫mousemove/mouseout函數(shù), 然后定義一個tooltips的div, move時候顯示, out后隱藏.
Line Chart
一個簡單的線圖代碼如下:
const width = 500;
const height = 500;
const margin = {top: 20, right: 20, bottom: 20, left: 20};
const dataset = [
{
'date': '2011-07-01T19:14:34.000Z',
'value': 58.13
},
{
'date': '2011-07-01T19:13:34.000Z',
'value': 53.98
},
{
'date': '2011-07-01T19:12:34.000Z',
'value': 67.00
},
{
'date': '2011-07-01T19:11:34.000Z',
'value': 89.70
},
{
'date': '2011-07-01T19:10:34.000Z',
'value': 99.00
}
];
const parseDate = d3.timeParse('%Y-%m-%dT%H:%M:%S.%LZ');
dataset.forEach(d => {
d.date = parseDate(d.date);
d.value = +d.value;
});
const svg = d3.select('svg').attrs({
width: width + margin.left + margin.right,
height: height + margin.top + margin.bottom
});
const x = d3.scaleTime().domain(d3.extent(dataset, d => d.date)).range([margin.left, margin.left + width]);
const y = d3.scaleLinear().domain([0, 1.05 * d3.max(dataset, d => d.value)]).range([margin.top + height, margin.top]);
const gx = svg.append('g').attr('transform', `translate(0, ${height + margin.top})`);
const gy = svg.append('g').attr('transform', `translate(${margin.left}, 0)`);
const xAxis = d3.axisBottom(x).ticks(5);
const yAxis = d3.axisLeft(y).ticks(4);
gx.call(xAxis);
gy.call(yAxis);
const line = d3.line()
.x(d => x(d.date))
.y(d => y(d.value));
svg.append('path').data([dataset])
.styles({
fill: 'none',
stroke: 'steelblue',
'stroke-width': '2px'
})
.attr('d', line);
效果圖如下:
- 使用d3.timeParse將字符串時間格式化為Date類型.
- 提供margin用于繪制坐標(biāo)軸.
- 使用d3.axisBottom/d3.axisLeft繪制底部坐標(biāo)軸和坐標(biāo)坐標(biāo)軸. 使用ticks(N)進(jìn)行間隔設(shè)置.
- 使用path進(jìn)行線條的繪制.
Line Chart with Vertical Line
我們要達(dá)到一個效果, 鼠標(biāo)移上去后, 會有一條豎直的線:
const focus = svg.append('g').append('line')
.styles({ display: 'none', 'stroke-width': 2, stroke: 'black' });
svg.on('mousemove', function () {
const x = d3.mouse(this)[0];
if (x >= margin.left && x <= margin.left + width) {
focus.attrs({ x1: x, y1: height + margin.top, x2: x, y2: margin.top })
.style('display', '');
} else {
focus.style('display', 'none');
}
}).on('mouseout', function () {
focus.style('display', 'none');
});
原理很簡單. 我們創(chuàng)建一個line, 然后隱藏它. 在mousemove/mouseout情況下顯示/隱藏它即可.
Line Chart with Area Fill
如果想繪制面積圖, 則需要做以下兩點(diǎn):
- 填充顏色需要設(shè)置: style('fill', '#f1f1f1')
- 使用d3.area代替d3.line, 并且需要提供x, y0, y1, 形成線條. 無數(shù)個x就形成了面積圖.
const area = d3.area()
.x(d => x(d.date))
.y0(y(0))
.y1(d => y(d.value));
svg.append('path').data([dataset])
.styles({
fill: '#f1f1f1',
stroke: 'steelblue',
'stroke-width': '2px'
})
.attr('d', area);
Line Chart with Brush Zoom
如果想得到畫刷的效果, 可直接使用d3.brushX:
svg.append('g').attr('class', 'brush')
.call(d3.brushX().extent([[margin.left, margin.top], [width + margin.left, height + margin.top]]));