Skip to content

D3 入门

D3是什么?

D3(Data-Driven Document)翻译成中文“数据驱动文档”,是一个基于数据操作 DOM 的Javascript库。理解起来,D3 跟 jQuery 很像,不过D3是面向数据可视化的,数据驱动操作DOM;而 jQuery 是面向 web 开发的,直接操作 DOM

D3 查询

D3 提供了两个 API 来查询 DOM,分别是 d3.select() 和 d3.selectAll()。

  • d3.select() 查询单个元素
  • d3.selectAll() 查询多个元素

select 和 selectAll 用法跟 jQuery 相似,传入值可以是 css 选择器,也可以是 DOM 元素。

js
// 查询单个元素
const svg = d3.select('svg')
const svg = d3.select('.className')
// 查询多个元素
const svgs = d3.selectAll('svg')
const svg = d3.selectAll('.className')

D3 设置属性

在 SVG 中有很多常见的属性,例如:id、class、x、y、cx、cy、fill、stroke、width、height、r 等等,而我们可以通过 D3 来设置 SVG 这些常见的属性。

js
// 设置单个元素的属性
d3.select('svg').attr('width', 400).attr('height', 400)

当然 attr 不仅可以设置属性,还可以用来获取某个属性的值

js
const width = svg.attr('width')

D3 数据绑定

数据绑定是将数据绑定到 DOM 元素上,这是 D3 最大的特色,也是 D3 的设计哲学。D3 中数据绑定提供了 datum() 和 data()两个函数。

datum 和 data 的区别就是 datum 这个方法不会进行数据绑定的计算,只能在现有的元素上绑定数据。 例如 datum

js
const data = [2, 4, 6, 8, 10, 12]
const body = d3.select('body')
body.append('p').datum(data).text(d => d)

效果如下图:
d3
datum 只能绑定一个 p 标签,并且全部值只能输出到这个 p 标签上

而 data 方法能自动根据顺序将数据匹配到每个查询到的 P 标签上

html
<body>
    <p></p>
    <p></p>
    <p></p>
    <p></p>
    <p></p>
    <p></p>
</body>
js
const data = [2, 4, 6, 8, 10, 12]
const body = d3.select('body')
body.selectAll('p').data(data).text(d => d)

效果图如下:
d3
data 有两个参数,第一个参数是当前要操作的数据,第二个参数是一个可选函数,主要用于记录当前更新的key,这主要是为了在后续更新中确保更新值的顺序与最初设置的保持一致。

enter

当我们第一次渲染画布时,可能就会遇到有数据却没有图元的情况,那么 D3 会自动“搞清楚”哪些数据是新增的,它会根据新增的数据自动生成相应的图元,这就是 Enter 的作用。当第一次生成图元后,它会自动进行占位,占位的内容需要开发人员自行添加,通过 append 方法。代码如下:

js
const p = svg.selectAll('.class').data(data).enter().append('p').attr(...);

Exit

当我们更新的某条数据被删除时,也就是有图元但是数据没了,D3 就会自动"搞清楚"哪些图元是没有绑定数据的,也就是会执行 Exit 状态。代码如下:

js
const p = svg.selectAll('.class').data(data).exit().remove();

上述的代码中,当我们在执行 exit 方法后,D3 就会自动释放已经不存在的图元,然后我们再通过 remove 方法将相关的内容移除掉。

join(enter, update, exit)

D3 新版本提供了新的方法join,能将上面 enter exit 和默认的 update 功能相结合使用

例如通过 join 来实现 enter 的例子

js
const p = svg.selectAll('.class').data(data).join('p').attr(...);

D3 比例尺

D3 的比例尺通俗的讲就是定义 x 轴和 y 轴,只不过在定义这两个轴上还会多一些额外功能。

例如定义线性比例尺:

js
d3.scaleLinear().domain([0, 1000]).range([0, 100])
  • scaleLinear() 声明为线性
  • domain 定义线上数据区间
  • range 定义线的长度

不过仅仅这样还不够,还需要我们把比例尺渲染到页面上。

js
// 获取 svg dom ,并且设置 svg 高度
const svg = d3.select('svg').attr('width', 400).attr('height', 400)
// 新建 x 轴比例尺
const xScale = d3.scaleLinear().domain([0, 100]).range([0, 350])
// 讲比例尺添加到 svg 上
svg.append('g').attr('transform', 'translate(10, 350)').call(d3.axisBottom(xScale))

效果如下图:
d3

除了线性比例尺外,d3 还提供了很多类型比例尺,例如 scaleTime() 时间刻度的比例尺,scaleBand() 指定文字刻度的比例尺。当然这里只介绍两种,更多可以去官网查看。

接下来我们实现一个完整的 x 轴和 y 轴

js
const margin = { top: 50, right: 50, bottom: 50, left: 50 }
const chartWidth = 400 - margin.left - margin.right
const chartHeight = 400 - margin.top - margin.bottom

const svg = d3.select('svg').attr('width', 400).attr('height', 400)
const chart = svg.append('g')
    .attr("transform", `translate(${margin.left},${margin.top})`)

const yScale = d3.scaleLinear().domain([0, 100]).range([chartHeight, 0])
const xScale = d3.scaleBand()
    .domain(['2021', '2022', '2023', '2024'])
    .range([0, chartWidth])
    .padding(.2)

chart.append('g').attr('transform', `translate(0, ${chartHeight})`).call(d3.axisBottom(xScale))
chart.append('g').call(d3.axisLeft(yScale))

效果如下图:
d3

D3 线段

线段生成器 Lines 基于给定的坐标点生成曲线或线段,常用于构成一些可视化图形。

在 D3 上实现线段效果需要分两步走::

  1. 创建一个线段生成器
js
const lineGenerator = d3.line()

line 接收两个参数 d3.line([xFunc], [yFunc]),分别是传递横坐标和纵坐标的读取函数 xFunc 和 yFunc 作为入参。如果不传入参数那么就默认数组中的第一个参数是横坐标,第二个参数是纵坐标

js
// 创建一个线段生成器,并设置了相应的参数
cosnt lineGenerator = d3.line(
    (d)=> d.xValue,
    (d)=> d.yValue
)
  1. 画线段 添加 path 标签,并且将上一步的线段生成器传入即可
js
d3.append('path')
    .datum([[10, 60], [40, 90]])
    .attr('d', lineGenerator)
    .attr('stroke', '#5671c3')
    .attr('stroke-width', '2')
    .attr('fill', 'none')

line 相关属性

  • line.x(xFunc) 设置横坐标读取函数,该函数会在调用线段生成器时,为数组中的每一个元素都执行一次,以返回该数据所对应的横坐标。(该函数有三个入参: 当前的元素 d,该元素在数组中的索引 i,整个数组 data)
  • line.y(xFunc) 设置纵坐标读取函数,设置方式和横坐标读取函数一样。
  • line.defined(definedState) 设置数据完整性检验函数,该函数会在调用线段生成器时,为数组中的每一个元素都执行一次,返回布尔值,以判断该元素的数据是否完整。
  • line.curve(curveInterpolator) 设置两点之间的曲线插值器
  • line.context(parentDOM) 用于设置父容器。

D3 扇形生成器

扇形生成器 Arcs 用以生成一个个扇形或环形,然后将多个扇形或环形拼起来就构成了饼图或圆环图。

生成饼图或圆环图在 D3 中分三步走:

  1. 创建扇形生成器
js
const arc = d3.arc()
    .innerRadius(0) // 内环半径
    .outerRadius(100) // 外环半径
    // .startAngle(0) // 开始角度 一般让 D3 自己计算
    // .endAngle(Math.PI / 2) // 结束角度 一般让 D3 自己计算
  1. 创建扇形数据
js
const color = ["#142403 ", "#32520e ", "#226232 ", "#207134", "#2C9747 ", "#36BF59 ", "#57D177", "#7FDD97 "]
const data = [1, 1, 2, 3, 5, 8, 13, 21]
// 如果是特殊数据可以指定某个值 d3.pie().sort(null).value(d => d.value)
const pie = d3.pie().sort(null)
const pieData = pie(data)
// pie 之后的数据格式
// {"data":  1, "value":  1, "index": 6, "startAngle": 6.050474740247008, "endAngle": 6.166830023713296, "padAngle": 0}
  1. 生成饼图或圆环图
js
d3.select('svg')
    .attr('width', 400)
    .attr('height', 400)
    .append('g')
    // 生成的扇形的中心坐标默认是 (0, 0),可以为扇形的父容器(一般是元素 <g>)设置 transform 属性,移动到目标位置
    .attr('transform', 'translate(100,100)')
    .selectAll('path')
    .data(pieData)
    .join('path')
    .attr('d', arcGenerator)
    .style("fill", (d, i) => color[i])
  1. 效果图 d3

arc 相关属性

  • arc.centroid() 计算扇形的「中点」,即位于弧度是 (startAngle+endAngle)/2, 半径是 (innerRadius+outerRadius)/2 的点
  • arc.cornerRadius([radius]) 设置圆角的半径,对于扇形则设置外侧的两个角;对于环形则设置内外侧的四个角。但是圆角的半径不会大于 (outerRadius−innerRadius)/2 而且当两个相邻的圆角靠得太近时,圆角半径会减小,一般出现在环形的弧长小于 π\piπ 的内侧圆角。
  • arc.padAngle([angle]) 设置扇形的间隔角度
  • arc.padRadius([radius]) 设置扇形的间隔半径