何大小成

D3 · 一个散点图

举一个小小的例子——散点图(D3.V4版本开发),来说明如何结合数据,坐标轴,比例尺,过度动画,事件交互

准备数据 & SVG区域

数据

一组圆心坐标,作为散点。

1
2
3
4
5
6
7
8
9
10
11
const center = [
[5, 5],
[73, 8],
[48, 9],
[11, 32],
[88, 25],
[75, 12],
[5, 50],
[22, 3],
[40, 10]
]

svg区域

添加一个svg区域,并定义外边框。
padding: 图形与画布的边界距离。

1
2
3
4
5
6
7
8
9
10
// svg宽高
const width = 500
const height = 500
// 外边框
const padding = {
top: 50,
bottom: 50,
left: 50,
right: 50
}

比例尺

定义比例尺,在绘制散点和坐标轴时会用到,其用法如下:

1
2
const linear = d3.scaleLinear().domain([0, 500]).range([0, 100])
linear(50) //

domain() 线性比例尺获取或设置定义域
range() 线性比例尺获取或设置值域
linear(x) 输入定义域的值x,返回值域对应的值

散点图的比例尺:

1
2
3
4
5
6
// xy轴宽度
const xAxisWidth = 300
const yAxisWidth = 300
// 比例尺
let xScale = d3.scaleLinear().domain([0, 100]).range([0, xAxisWidth])
let yScale = d3.scaleLinear().domain([0, 100]).range([0, yAxisWidth])

坐标轴


D3提供了坐标轴的制作方法,需要配合比例尺一起使用。 D3所绘制的坐标轴由<path> <line> <text>三种元素组成:

元素 绘制
<path> 主直线
<line> 刻度
<text> 刻度文字

其用法如下(比例尺取上面xScale的值):

1
let xAxis =  d3.axisBottom(xScale).ticks(5)

ticks() 设定或获取坐标轴上指定的宽度

散点图的坐标轴:

1
2
3
4
5
6
7
8
9
10
11
12
let xAxis = d3.axisBottom(xScale).ticks(5)
let yAxis = d3.axisLeft(yScale).ticks(5)
// xy轴的生成器
yScale.range([yAxisWidth, 0])
// 绘制xy轴
svg.append('g')
.attr('transform', `translate(${padding.left}, ${height - padding.bottom})`)
.call(xAxis)
svg.append('g')
.attr('transform', `translate(${padding.left}, ${height - padding.bottom - yAxisWidth})`)
.call(yAxis)
yScale.range([0, yAxisWidth])

过渡 & 动画

将绘制散点的代码都写进drawCircle()函数里面,其中包含update(更新),enter(添加),exit(删除)三部分的处理方法。

  1. 添加新点时,从坐标原点过度到目标的位置
  2. 更新点时,坐标系中的散点过渡到新的位置
  3. 点被删除时,点先慢慢变成白色,然后删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const t = d3.transition().duration(700).ease(d3.easeLinear)
function drawCircle() {
let circleUpdate = svg.selectAll('circle').data(center) // 绑定数据,获取update
let circleEnter = circleUpdate.enter() // 获取enter
let circleExit = circleUpdate.exit() // 获取exit
// update部分:缓缓移动到新目标
circleUpdate.transition(t)
.attr('cx', d => padding.left + xScale(d[0]))
.attr('cy', d => height - padding.bottom - yScale(d[1]))
// update部分:缓缓移动到新目标
circleEnter.append('circle')
.attr('fill', 'black')
.attr('cx', padding.left)
.attr('cy', height - padding.bottom)
.attr('r', 7)
.transition(t)

.attr('cx', d => padding.left + xScale(d[0]))
.attr('cy', d => height - padding.bottom - yScale(d[1]))
// exit部分:缓缓移动到新目标
circleExit.transition(t)
.attr('fill', 'white')
.remove()
}

交互

D3的选择集可以通过on来为事件添加监听器。
d3.select(this) 改变响应事件的元素,this就是事件被触发的元素。
注意的是:
过渡对象是没有on()的,不能为过渡对象设置监听器。这样写是不正确的:

1
2
3
svg.select(this)
.transition()
.on('click',function(){})

所以设定监听器要在transtion()之前

1
2
3
svg.select(this)
.on('click',function(){})
.transition()

散点图里:修改update部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
circleEnter.append('circle')
.attr('fill', 'black')
.attr('cx', padding.left)
.attr('cy', height - padding.bottom)
.attr('r', 7)
// 交互事件
// 1.鼠标悬浮在元素 元素变成黄色
.on('mouseover', function() {
d3.select(this).attr('fill', 'yellow')
})
// 2.鼠标离开元素 元素变回原来的黑色
.on('mouseout', function() {
d3.select(this).transition(t).attr('fill', 'black')
})
.transition(t)
.attr('cx', d => padding.left + xScale(d[0]))
.attr('cy', d => height - padding.bottom - yScale(d[1]))

最终演示效果:

See the Pen BrjOde by 何大小成 (@hopkinson) on CodePen.