目录
前言
本笔记是基于清华大学张少魁讲授的计算机系2022年春季的《数据可视化》课程视频和pdf
课程链接:哔哩哔哩
课程github: github
1、导入D3.js
通过script标签导入:
- 通过互联网链接:https://d3js.org/d3.v5.min.js
- 通过本地文件:./d3.min.js
2、D3基础语法
引入SVG
<svg style='display: block; margin: 0 auto;'>
<g transform='translate(0,60)'>
<rect width=100 height=100 fill='#EEEEEE' />
<circle r=15 fill='#72bf67' cx=25 cy=30 />
<circle r=15 fill='rgb(100, 149, 237)' cx=75 cy=30 />
<g transform='translate(15,60) rotate(10)'>
<path d="M0,0 A40,40 10 0,0 65,0" fill='none' stroke='gray' stroke-width=5 />
</g>
</g>
</svg>
使用D3查询SVG
d3.select(…)
•
d3.select(‘#rect1’)
•
查询ID为’rect1’的元素
•
#表示后面的字符串是一个ID
•
只找一个,若有重名也只返回第一个
d3.selectAll(…)
•
d3.selectAll(‘.class1’)
•
查询所有class是’class1’的元素
•
d3.selectAll(‘rect’)
•
查询所有标签是’rect’的元素(rect为SVG中的矩形标签)
•
有多少返回多少
•
可配合Data-Join选取‘不存在’的图元
• ID前加‘#’,Class前加‘.’ ,标签名前不加符号。
基于层级的查询:
•
d3.select(‘#maingroup rect’)
•
d3.selectAll('.tick text’)
•
d3.selectAll(‘#secondgroup rect’)
•
如:’#secondgroup rect’
•
首先会找到id为secondgroup的标签
•
进一步找到secondgroup的子标签中是rect的
•
仍然是对rect做查询,只是结果通过父标签做了筛选
•
d3.select(…)也可用于查询类别,如
•
d3.select(‘.class1’)
•
但只会返回找到的第一个元素
• 因此对于class、标签名称的查询建议使用d3.selectAll
• 对于特定某一个元素的查询建议使用d3.select
使用D3设置SVG中的属性
常见的属性
•
id, class(特殊的属性,可以使用.attr设置)
•
x, y, cx, cy (注意屏幕的坐标系!见下页)
•
fill, stroke
•
height, width, r (圆的半径)
•
transform -> translate, rotate, scale
屏幕空间的坐标系与常见坐标系不同
•
左上方为原点
•
Y、X分别垂直向下、水平向右
设置元素的属性: element.attr(‘attr_name’, ‘attr_value’)
•
两个参数:属性名、设置的值
•
rect1.attr(‘y’, ‘100’)
•
d3.select(‘#rect1’).attr(‘y’, ‘100’)
获取元素的属性: element.attr(‘attr_name’)
•
一个参数:属性名
链式调用
•
selection.attr(…).attr(…).attr(…)
•
.attr(…)返回的是选择的图元本身
修改整组属性
DOM
•
父节点的属性会影响子节点
•
子节点的属性会相对于父节点
下方代码可以直接移动组内所有元素
•
d3.select('#maingroup’)
•
.attr('transform', 'translate(200, 100)')
使用D3 添加&删除 SVG元素
element.append(…)
•
const myRect = svg.append(‘rect’);
•
const myRect = d3.select(‘#mainsvg’).append(‘rect’)
•
const myRect = d3.select(‘#mainsvg’).append(‘rect’).attr(‘x’, ‘100’)
D3的链式添加(调用)
•
const myRect = d3.select(‘#mainsvg’).append(‘g’).attr(‘id’, ‘maingroup’)
•
.append(‘rect’).attr(‘fill’, ‘yellow’)
element.remove()
•
请小心使用
•
会移除整个标签
Tip:在debug的过程中可以考虑使用’opacity’属性hack出移除的效果
•
element.attr(‘opacity’, ‘0’)
数据的读取 – CSV数据
d3.csv(…):
•
读取目标路径下的某一个CSV文件。
•
e.g., d3.csv(‘static/data/hello.csv’);
d3.csv是一个JavaScript异步函数:
•
不可以直接获得它的返回值
,如:
•
let myData = d3.csv(‘static/data/hello.csv’);
❌
d3.csv(‘path/to/data.csv’).then(
data
=> { // ‘数据读取后的代码逻辑’ } )
•
要通过.then(
data
=> {…} )的方式来获得读取后的数据。
•
then(…)中的 ‘
data
=> {…}’ 是一个
函数
。
•
此
函数
接受的
输入
(
参数
),即
data
,为读取后的数据。
d3.csv('test.cvs').then(data => {
console.log(data);
});
JavaScript异步机制(下述不做要求):
•
d3.csv作为异步函数,即便没有读取好数据,后面的代码也会继续执行。
•
d3.csv被调用后,其返回值是一个JavaScript的‘Promise’对象(object)。
•
Promise‘询问’:数据读取好了之后要
做什么
?‘
做什么
’即对应.then()中
函数
的内容。
D3.js的数值计算
d3.max(array)
•
返回数组中的最大值。
•
e.g., d3.max([5,4,6,1,8,16,9]) // 16
d3.min(array)
•
返回数组中的最小值。
•
d3.min([5,4,6,1,8,16,9]) // 1
d3.extent(array)
•
同时返回最小值与最大值,以数组的形式,即[最小值,最大值]。
•
d3.extent([5,4,6,1,8,16,9]) // [1, 16]
数组中的内容可以是任意对象:
•
每个对象可能包含多个属性。
•
具体取哪个属性的最大值通过回调函数来提示d3.max、d3.min与d3.extent。
•
e.g.,
•
let a = [
{name: 'Shao-Kui', age:25, height: 176}, {name:'Wen-Yang', age:24, height: 180},
{name:'Liang Yuan', age: 29, height: 172}, {name:'Wei-Yu', age:23, height: 173}]
•
d3.max(a, d => d.age) // 29
•
d3.max(a, d => d.height) // 180
•
d3.extent(a, d => d.height) // [172, 180]
•
d3.min(a, d => d.age) // 23
比例尺
•
比例尺用于把实际
数据空间
映射到
屏幕(画布)空间
,即两个空间的转化。
•
常用于映射数据and创建坐标轴。
•
区别主要在于数据的尺度不同。
Scale - Linear
d3.scaleLinear():
•
定义一个线性比例尺,返回的是一个
函数
。
•
e.g., let scale = d3.scaleLinear(); // scale为函数
scale.domain([min_d, max_d]).range([min, max]):
•
设置比例尺的
定义域
与
值域
。
•
线性比例尺的定义域和值域都是连续的(Continuous),需分别给出最大值与最小值。
•
e.g.,
const scale = d3.scaleLinear().domain([20, 80]).range([0, 120]);
比例尺本质上是一个函数
:
•
scale(20) // 0
•
scale(50) // 60
常结合读取的数据与d3.max等接口连用:
•
const xScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([0, innerWidth]);
Scale - Band
d3.scaleBand():
•
定义一个‘条带’比例尺,返回的是一个
函数
。
•
e.g., let scale = d3.scaleBand();
scale.domain(array).range([min, max]):
•
设置比例尺的定义域与值域。
•
Band比例尺的定义域是离散的(Discrete),值域是连续的。
•
e.g.,
const scale = d3.scaleBand().domain([‘a’, ‘b’, ‘c’]).range([0, 120]);
比例尺本质上是一个函数:
•
scale('b') // 40
•
scale(‘c') // 80
scale.padding(0.1):
•
设置条带的间距占各自区域的比重。
scale.bandwidth():
•
返回条带的长度。
d3.csv('platform_globalsale.csv').then(data => {
// calculationg scales:
yScale.domain(data.map(yValue)).range([0, innerHeight]).padding(0.1);
xScale.domain([0, d3.max(data, xValue)]).range([0, innerWidth]);
// data-join for rectangles:
mainGroup.selectAll('rect').data(data).join('rect')
.attr('height', yScale.bandwidth()).attr('width', d => xScale(xValue(d)))
.attr('x', 0).attr('y', d => yScale(yValue(d)));
// adding axes:
const xAxisMethod = d3.axisBottom(xScale);
const yAxisMethod = d3.axisLeft(yScale);
const xAxisGroup = mainGroup.append('g').call(xAxisMethod);
const yAxisGroup = mainGroup.append('g').call(yAxisMethod);
xAxisGroup.attr('transform', `translate(${0}, ${innerHeight})`);
})
待续。。。