D3.js 01
- 说在前面
- 1 概述
- 2 配置Web环境
- 3 HTML
- 4 SVG
- 5 DOM
- 6 JS
- 7 常用接口
- 8 D3语法基础
- 9 使用D3查询SVG
- 10 使用D3设置SVG中属性
- 11 修改整组属性
- 12 使用D3添加与删除SVG元素
- 13 数据读取 —— CSV数据
- 14 D3.js的数值计算
- 15 比例尺
- Scale - Linear
- Scale - Band
- 16 引入坐标轴
- 17 DATA-JOIN
说在前面
最近需要使用到D3.js,所以在B站上找到清华的D3.js教程,同时在这里做相关的自学笔记
原视频链接如下:
【数据可视化编程-使用D3.js(2022)】 https://www.bilibili.com/video/BV1qg411X7bB/?share_source=copy_web&vd_source=773b408053db6c74535b5afe2aa8feb9
1 概述
D3.js是目前最主流的,社区规模最大的,支持定制图元级别可视化效果的框架
相关文档:https://github.com/xswei/d3js_doc
D3画廊:https://observablehq.com/@d3/gallery
其他如Echarts不支持图元级别定制,但是支持图表混搭
之后课程会介绍动画与交互,可视化图表绘制,其他常用接口
D3: Data-Driven Documents
通过D3提供的接口基于数据操控文档(画布)的各个图元
接口约等于D3.js提供的函数调用
主要参考资料:
https://d3js.org/ 官网,文档,样例
https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute SVG 属性参考
https://github.com/d3/d3 官方样例仓库
https://observablehq.com/@d3/gallery 官方样例仓库
https://github.com/xswei/d3js_doc d3.js资源汇总,包括示例、书籍、API文档等
https://github.com/Shao-Kui/D3.js-Demos
2 配置Web环境
D3.js基于JS,用于在Web前端控制HTML中的元素
- 使用VScode,安装Live Server
- 直接应用常用框架:Python Simple HTTP Server(不推荐),Node.js Simple HTTP Server,Flask, Express等
3 HTML
HyperText Markup Language
HTML包含大量元素(标签)
- 元素之间类别不同,如矩形,直线,文本,圆等等
- 元素包含属性,如位置,大小,色调,文本风格等等
<script>
:JS脚本或者脚本链接
D3.js或Echart的编程主要写于此标签中
<svg>
:对于D3最重要的标签
主要操作的对象画布,同时包括所有图元应用的标签,如<rect>, <circle>, <path>
等
D3.js是JS的外库,必须先将其导入
如同Python的import
Java的import
C/C++的include
node.js的require
这里通过Script标签导入
- 直接通过互联网链接 -
<script src="https://d3js.org/d3.v7.min.js"></script>
- 通过本地服务器链接 -
./d3.min.js
- 通过unpkg链接 -
https://unpkg.com/browse/d3@5.15.0/dist/d3.js
尽量使用本地的d3.min.js库
4 SVG
D3的绘制画布
Scalable Vector Graphics 可缩放矢量图形
5 DOM
文档对象模型
对于根节点的操作会影响子节点
常用父节点<svg>
中的<g>
- Axis可以封装为一个group
- Legend(图例)可以封装为一个group
6 JS
解释型
不需要编译
JS语句类似于C与C++
变量声明不需指定类型 int double function等,直接let, var const
运算操作基本等同C,C++和Java
下面是函数定义:
function abc(a) { return a + 5; }
const p = function(a, b){return a + b;}
let f = datum => datum.value;
let myFunction = (a, b) => a + b
let f = (d, i) => { console.log(d);return d + I;}
一个变量可以是一个函数
类似于C/C++中的函数指针
const myFunction = function(a, b){ return a + b; }
回调(CallBack)
JS脚本中常见将函数作为变量输入用于实现异步编程
setTimeout( funciton() {console.log('hello world')
}, 1000);
在D3中存在大量类似调用
- 将函数作为参数给图元
- 为每个数据点指定不同颜色
- 配置坐标轴
7 常用接口
模版字符串:
let a = 10;
let myString = `abc-${a}`;
// myString最终为'abc-10'
数组 a = [1, 2, 3]
对象 a = {name: ‘Zane’, age: 24, lab: ‘cs’}
D3数据可视化常见对象数组
a = [{name:'Zane', age: 20, dept: 'cs'},{name:'LJD', age: 21, dept: 'cs'},{name: 'LH', age: 22, dept: 'ee'}]
数组排序 a.sort()
- 可以通过加入回调函数来替代缺省的排序方案,如为日期排序
- a.sort(function(a, b){ return new Date(b.date) - new Date(a.date); }
数组查询 a.find()
a.find( d => d.name === ‘Zane’)
将字符串转为数值 +(‘3.14’)
D3.js经常读取CSV,JSON等文件,涉及大量数组,对象操作
8 D3语法基础
使用D3获取,修改,增加以及删除节点(图元)
数据读取 - CSV
数值计算
比例尺:1. 线性比例尺 Linear Scale
2. 条带比例尺 Band Scale
坐标轴绘制: Margin
Data-Join基础
基于D3与Data-Doin绘制柱状图
9 使用D3查询SVG
d3.select(‘#rect1’) 永远返回第一个
d3.selectAll(‘.class1’) 返回所有
ID前加#,Class前加.,标签前不加
层级查询
d3.select(‘#maingroup rect’)
d3.select(‘.tick text’)
d3.selectAll(‘#secondgroup rect’)
10 使用D3设置SVG中属性
常见属性:
- id, class (特殊属性,可使用.attr设置)
- x, y, cx, cy (注意屏幕坐标系)
- fill, stroke
- height, width, r (圆的半径)
- transform -> translate, rotate, scale
SVG的属性非常多,并且属性的取值范围和类型各不相同
- 尽可能记住常见的属性来提高编程速度
- 遇见不认识的or想要设置某个属一定要查阅文档
https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute
<rect id='rect3' class='class1' stroke='black' height='200' width='66' fill='#7289AB' x='300' y='-100'>
屏幕空间的坐标系与常见坐标系不同
左上方为原点
y,x分别垂直向下、水平向右
element.attr(…)
设置元素属性: 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(…)返回的是选择的图元本身
11 修改整组属性
DOM
父节点的属性会影响子节点
子节点的属性相对于父节点
以下代码可以直接移动组内的所有元素
d3.select(‘#maingroup’).attr(‘transform’, ‘translate(200, 100)’)
12 使用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()
- 小心使用
- 因为将移除整个标签
- d3.select(‘#rect1’).remove()
在debug过程中可以使用’opacity’属性hack出移除效果
- element.attr(‘opacity’, ‘0’)
13 数据读取 —— CSV数据
第一行是属性列表,后面每一行对应一条数据
CSV本质上是纯文本,区别于EXCEL格式
-
d3.csv(…):
- 读取目标路径下的某一CSV文件
- 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,是读取后的数据
-
JavaScript异步机制
- d3.csv是异步函数,即便没有读取完毕数据,后面的代码依旧继续执行
- d3.csv被调用后的返回值是一个JavaScript的Promise对象(object)
- Promise 询问:数据读取完毕后需要做什么,做什么即对应.then()中函数的内容
读取后的数据格式(接口)与原本的CSV结构不同
14 D3.js的数值计算
数据可视化常常涉及对数据的处理与计算
下面三个接口分别用于计算数组的最大值,最小值,[最小值, 最大值]
-
d3.max(array)
-
返回数组中的最大值
-
d3.min(array)
-
返回数组中的最小值
-
d3.extent(array)
-
同时以数组的形式返回最小值与最大值
数组中的内容可以是任意对象
- 每个对象可能包含多个属性
- 具体需要那个属性的最值可以通过回调函数提示d3.max, d3.min与d3.extent
15 比例尺
比例尺用于将实际数据空间映射到屏幕(画布)空间,也即两个空间转化
用于映射数据和创建坐标轴,区别主要在于数据的尺度不同
Scale - Linear
d3.scaleLinear():
- 定义一个线性比例尺,返回的是一个函数
- 如 let scale = d3.scaleLinear(); // scale是函数
scale.domain([min_d, max_d]).range([min, max]):
- 设置比例尺的定义域与值域
- 线性比例尺的定义域和值域都是连续的(Continuous),需要给出最大值与最小值
- 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():
- 定义一个条带比例尺,返回一个函数
- let scale = d3.scaleBand();
scale.domain(array).range([min, max]):
- 设置比例尺的定义域和值域
- Band比例尺的定义域是离散的(Discrete),值域是连续的
- 如 const scale = d3.scaleBand().domain([‘a’, ‘b’, ‘c’]).range([0, 120]);
- 比例尺本质上是函数:
- scale(‘b’) // 40
- scale(‘c’) // 80
d3.scaleBand()常常结合JS的array.map接口一同使用:
- let a = [{name: ‘Zane’, value: 6}, {name: ‘LiHao’, value: 7}, {name:‘Ljd’, value: 8}]
- a.map(d => d.name) // [‘Zane’, ‘LiHao’, ‘Ljd’]
- const yScale = d3.scaleBand()
- .domain(data.map(d => d.name))
- .range([0, innerHeight])
- .padding(0.1)
scale.padding(0.1):
设置条带间距占各自区域的比重
scale.bandwidth():
返回条带长度
16 引入坐标轴
一个坐标轴为一个group,也即<g>
通常需要两个坐标轴
坐标轴中包含:
- 一个
<path>
用于横跨坐标轴的覆盖范围 - 若干个刻度
.tick
,每个刻度也是一个group - 每个刻度下属包含一个
<line>
和一个<text>
<line>
用于展示轴线,如左到右或上到下<text>
用于展示刻度值,如实数,姓名,日期
- (可选)一个标签用于描述坐标轴
坐标轴的定义需要比例尺
定义坐标轴(获得结果仍是函数)
- const yAxis = d3.axisLeft(yScale);
- const xAxis = d3.axisBottom(xScale);
- axisLeft: 左侧坐标轴
- axisBottom: 底侧坐标轴
- 坐标轴的刻度对应比例尺的定义域
- 坐标轴在画布的绘制对应比例尺的值域
- 仅仅是对坐标轴的定义,没有绘制
绘制坐标轴
- const yAxisGroup = g.append(‘g’).call(yAxis);
- const xAxisGroup = g.append(‘g’).call(xAxis);
- 实际配置后会发现
<g>
中增加了与坐标轴相关的元素
任何坐标轴在初始化之后会默认放置在坐标原点,需要进一步平移
关于 selection.call(…)
- 函数的输入为另一个函数
- 另一个函数以selection本身(也即图元)作为输入
- 另一个函数将根据函数体的内容修改selection对应的图元
- 定义一个空白的
<g>
,D3会帮助我们定义好另一个函数,我们通过.call(…)让<g>
得以在另一个函数中修改- const yAxis = d3.axisLeft(yScale);
- const yAxisGroup = g.append(‘g’).call(yAxis);
配置坐标轴
可以对坐标轴的风格进行修改
- 坐标轴本质上是图元的集合
- d3.selectAll(‘.tick text’).attr(‘font-size’, ‘2em’);
- .tick是D3对于坐标轴定义的统一class
坐标轴的标签加入不在D3.Axis接口的负责范围内:
- 通过对坐标轴的
<g>
标签.append(‘text’)来实现 - 左(纵)轴坐标需要.attr(‘transform’, ‘rotate(-90)’)来旋转
- 纵轴坐标旋转后,x/y会颠倒甚至取值范围相反
- 回忆DOM: 父节点属性会影响子节点,而坐标轴默认的’fill’属性是’none’,因此请一定手动设置文字颜色.attr(‘fill’, ‘black’)
Margin
SVG对于D3.js是一个画布
SVG范围外的任何内容属于画布之外,浏览器不予显示
- 而坐标轴通常初始化在所在父节点的左上角
定义Margin:
- const margin = {top: 60, right: 30, bottom: 60, left: 200}
计算实际操作的inner长/宽
- const innerWidth = width - margin.left - margin.right;
- const innerHeight = height - margin.top - margin,.bottom;
在SVG下额外定义一个组作为新的根节点
- const g = svg.append('g').attr('id', 'maingroup')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
HTML确实在样式表中提供margin属性,然而设置其他图元的位置,仍
需要计算innerWidth(Height)
调用示例
const yAxis = d3.axisLeft(yScale) // .tickSize(-innerWidth);
const xAxis = d3.axisBottom(xScale) // .tickSize(-innerHeight);const yAxisGroup = g.append('g').call(yAxis)
.append('text')
.text('Name')
.attr('font-size', '3em')
.attr('transform', 'rotate(-90)') // y-axis label needs an additional transform;
.attr('x', -innerHeight / 2)
.attr('y', -120)
.attr('fill', 'black')
const xAxisGroup = g.append('g').call(xAxis)
.attr('transform', `translate(${0}, ${innerHeight})`)
.append('text')
.text('Value')
.attr('font-size', '3em')
.attr('x', innerWidth / 2)
.attr('y', 50)
.attr('fill', 'black');d3.selectAll('.tick text).attr('font-size', '2em');g.append('text').text('Members of CSCG').attr('font-size', '3em')
.attr('x', innerWidth / 2 - 200).attr('y', -10)
17 DATA-JOIN
本质上是将数据与图元进行绑定
以数据为中心的可视化操作(Data-Driven)
- 根据数据自动调整图元属性
- .attr(…)接口可以基于图元自己绑定的数据自动调整属性值
数据发生变化时可以自动对图元增删改查
- 不需要手动添加,修改,删除图元
- 根据数据的增删改,自动增删改图元
d3.selectAll(‘.class’).data(dataArray)
dataArray在保证是数组的前提下可以是任何形式如数值数组,对象数组
。。。