这回我们用祖传的数据来画一个雷达图

绘制边框

参数和数据载入的过程和之前别无二致,准备工作完成之后,我们先来绘制几个同心圆,来作为雷达图的基础边框

1
2
3
4
5
6
7
8
9
const axis = bounds.append("g")

const gridCircles = d3.range(4).map((d, i) => (
axis.append("circle")
.attr("cx", dimensions.boundedRadius)
.attr("cy", dimensions.boundedRadius)
.attr("r", dimensions.boundedRadius * (i / 3))
.attr("class", "grid-line")
))

我们准备在雷达图上显示六种属性,因此接下来我们要画对应的六个坐标轴,和同心圆一样,我们也可以用map来实现

1
2
3
4
5
6
7
8
9
const gridLines = metrics.map((metric, i) => {
const angle = i * ((Math.PI * 2) / metrics.length) - Math.PI * 0.5
return axis.append("line")
.attr("x1", dimensions.boundedWidth / 2)
.attr("x2", Math.cos(angle) * dimensions.boundedRadius + dimensions.boundedWidth / 2)
.attr("y1", dimensions.boundedHeight / 2)
.attr("y2", Math.sin(angle) * dimensions.boundedRadius + dimensions.boundedWidth / 2)
.attr("class", "grid-line")
})

数据绘制

首先我们将要展示的属性标注在画出来的同心圆的边界上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const labels = metrics.map((metric, i) => {
const angle = i * ((Math.PI * 2) / metrics.length) - Math.PI * 0.5
const x =
Math.cos(angle) * (dimensions.boundedRadius * 1.1) + dimensions.boundedWidth / 2
const y =
Math.sin(angle) * (dimensions.boundedRadius * 1.1) + dimensions.boundedHeight / 2
return axis.append("text")
.attr("x", x)
.attr("y", y)
.attr("class", "metric-label")
.style("text-anchor",
i == 0 || i == metrics.length / 2 ? "middle" :
i < metrics.length / 2 ? "start" : "end"
)
.text(metric)
})

text-anchor是对给定点的对齐方式,因为我们是标注在圆周外边的,所以肯定有文本的开头在给定点,文本的结束位置在给定点和文本的中点在给定点三种,因此我们需要根据其下标决定对齐方式

然后将数值在图表上表示出来,这里我们希望画一个闭合的图形,所以采用d3.lineRadial(),需要设置角度和半径,.curve(d3.curveLinearClosed)则将最后一笔连上,形成闭合图形

1
2
3
4
5
6
7
8
9
10
11
12
13
const line = bounds.append("path")
.attr("class", "line")

const drawLine = (day) => {
const lineGenerator = d3.lineRadial()
.angle((metric, i) => i * ((Math.PI * 2) / metrics.length))
.radius((metric, i) => metricScales[i](+day[metric] || 0))
.curve(d3.curveLinearClosed)
const line = bounds.select(".line")
.datum(metrics)
.attr("d", lineGenerator)
.style("transform", `translate(${dimensions.boundedRadius}px, ${dimensions.boundedRadius}px)`)
}

最后我们加入按钮用于切换对应的日期,显示对应的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let activeDayIndex = 0
const title = d3.select("#title")
const dateFormatter = d3.timeFormat("%B %-d, %Y")

const updateChart = () => {
title.text(dateFormatter(dateAccessor(dataset[activeDayIndex])))
drawLine(dataset[activeDayIndex])
}

updateChart()

d3.select("#show-next-day").on("click", e => {
activeDayIndex = (activeDayIndex + 1) % (dataset.length - 1)
updateChart()
})

[演示地址]