持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
开始绘制
引入D3模块
- 引入整个D3模块。
<script src="https://d3js.org/d3.v7.min.js"></script>
数据
- 自定义数据,用户绘图使用。
var dataArr = [
{
label: '1月',
value: [15.5, 10]
},
{
label: '2月',
value: [10.5, 60]
},
{
label: '3月',
value: [40.5, 100]
},
{
label: '4月',
value: [50.5, 40]
},
{
label: '5月',
value: [20.5, 10]
},
{
label: '6月',
value: [90.5, 20]
}
]
添加画布
- 初始化画布。
var width = 450
var height = 450
var margin = 20
var svg = d3
.select('.d3Chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.style('background-color', '#1a3055')
// 图
var chart = svg.append('g').attr('transform', `translate(${margin}, ${margin})`)
绘制雷达图层级
/**
* @param vertexNum 顶点数
* @param radius 图半径
* @param tickNum 层级数
* */
function getPolygonPoints(vertexNum, radius, tickNum) {
const points = [] // 点数组
let polygon = [] // 多边形
// 计算 顶点间的弧度
const anglePiece = (Math.PI * 2) / vertexNum
// 每个层级高度
const radiusReduce = radius / tickNum
for (let r = radius; r > 0; r -= radiusReduce) {
polygon = []
// 获取 每个层顶点位置
for (let i = 0; i < vertexNum; i++) {
polygon.push(Math.sin(i * anglePiece) * r + ',' + Math.cos(i * anglePiece) * r)
}
points.push(polygon.join(' '))
}
return points
}
const points = getPolygonPoints(dataArr.length, 100, 5)
- 层级由多个,正多边形组成。
- 通过设定好的参数,计算出每个层级的,多边形顶点的坐标。
const axes = chart
.append('g')
.attr('class', 'axes')
.attr('transform', 'translate(' + (width - margin * 4) / 2 + ',' + (height - margin * 2) / 2 + ')')
- 创建雷达图绘制组
g
,设置好组的位置。
axes
.selectAll()
.data(points)
.enter()
.append('polygon')
.attr('class', 'axis')
.attr('points', (d) => d)
.attr('fill', (d, i) => (i % 2 === 0 ? 'white' : '#ddd'))
.attr('stroke', 'gray')
- 绑定计算出来的多边形点数据。
- 通过SVG的
polygon
元素,交替颜色绘制多边形。
// 得到顶层 多边形 顶点
const pointsTop = points[0].split(' ').map((d) => d.split(','))
const line = d3.line()
axes
.selectAll()
.data(pointsTop)
.enter()
.append('path')
.attr('class', 'line')
.attr('d', (d) => {
return line([
[0, 0],
[d[0], d[1]]
])
})
.attr('stroke', 'gray')
- 获取顶层正多边形的顶点。
- 雷达图绘制组的原点和多边形顶点连线。
绘制标签
// 文本
function computeTextAnchor(num, i) {
const angle = (i * 360) / num
if (angle === 0 || Math.abs(angle - 180) < 0.01) {
return 'middle'
} else if (angle > 180) {
return 'end'
} else {
return 'start'
}
}
axes
.selectAll()
.data(dataArr)
.enter()
.append('text')
.attr('fill', '#fff')
.attr('x', (d, i) => Math.sin((i * Math.PI * 2) / dataArr.length) * 120)
.attr('y', (d, i) => Math.cos((i * Math.PI * 2) / dataArr.length) * 120)
.attr('text-anchor', (d, i) => computeTextAnchor(dataArr.length, i))
.attr('dy', 6.5) // 对齐文字中部
.text((d) => d.label)
- 创建文本排列方式计算的方法。
- 和计算层顶点位置的方式一样,使用三角函数计算文本坐标。需要比层半径多出一些距离,设置文本在图形外。
绘制数据
let newData = []
dataArr[0].value.forEach((item, j) => {
const row = []
dataArr.forEach((item, i) => {
row.push(item.value[j])
})
newData.push(row)
})
- 装换数据格式,方便绘图。
const colorScale = d3.scaleOrdinal(d3.schemeSet2)
- 创建颜色获取对象。
// 计算多边形的顶点并生成顶点圆圈
function generatePolygons(d, index) {
const points = []
const anglePiece = (Math.PI * 2) / d.length
d.forEach((item, i) => {
const x = Math.sin(i * anglePiece) * item
const y = Math.cos(i * anglePiece) * item
//添加交点圆圈
axes
.append('circle')
.attr('fill', 'white')
.attr('stroke', colorScale(index))
.attr('cx', 0)
.attr('cy', 0)
.attr('r', 3)
.transition()
.duration(1000)
.attr('cx', x)
.attr('cy', y)
points.push(x + ',' + y)
})
return points.join(' ')
}
- 使用三角函数计算出数据顶点的位置。
- 数据顶点还需要绘制小圆点,在计算出位置后直接在顶点位置,绘制小圆点。
axes
.selectAll()
.data(newData)
.enter()
.append('polygon')
.attr('class', 'polygon')
.attr('fill', 'none')
.attr('stroke', (d, i) => colorScale(i))
.attr('stroke-width', '2')
.attr('points', (d, i) => {
const miniPolygon = []
d.forEach(() => {
miniPolygon.push('0,0')
})
return miniPolygon.join(' ')
})
.transition()
.duration(1000)
.attr('points', generatePolygons)
- 绑定数据,绘制数据多边形展示。
添加交互
d3.selectAll('.polygon')
.on('mouseover', function (e, d) {
d3.select(e.target).attr('stroke-width', '5')
})
.on('mouseleave', function (e, d) {
d3.select(e.target).attr('stroke-width', '2')
})
- 这里只添加了一个简单的加粗交互。