持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
开始绘制
引入D3模块
- 引入整个D3模块。
<script src="https://d3js.org/d3.v7.min.js"></script>
数据
- 定义数据。数据格式不合理,可在使用时转换为需要的格式。
var dataArr = [
{
label: '1月',
value: 10.5
},
{
label: '2月',
value: 70.5
},
{
label: '3月',
value: 60.5
},
{
label: '4月',
value: 10.5
},
{
label: '5月',
value: 20.5
},
{
label: '6月',
value: 30.5
}
]
添加画布
- 初始化画布。
var width = 700
var height = 700
var margin = 60
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})`)
创建比例尺
- 根据需求创建比例迟。
// 序数比例尺 - 颜色
let colorScale = d3.scaleOrdinal().domain(d3.range(0, dataArr.length)).range(d3.schemeCategory10)
// 高度的 1/4 用于唤醒半径
var radius = height / 4
绘制扇形
// 饼图(pie)生成器 计算图所需要的角度信息
// .padAngle() 饼图扇形之间的间隔设置
// .startAngle() 起始角度设置
// .endAngle() 终止角度设置
// 最后传入数据 返回饼图数据
let drawData = d3
.pie()
.value(function (d) {
return d.value
})
.padAngle(0.02)
.startAngle(0)
.endAngle(Math.PI * 2)(dataArr)
// .arc() 是shape中的 弧形生成器
// innerRadius() 设置内半径
// outerRadius() 设置外半径
// cornerRadius() 设置拐角圆滑
let arc = d3.arc().innerRadius(50).outerRadius(radius).cornerRadius(5)
- 在创建饼图生成器时,传入数据提前准换好饼图数据。
const arcs = chart
.append('g')
.attr('transform', 'translate( ' + (radius * 2 - margin) + ', ' + (radius * 2 - margin) + ' )')
arcs
.selectAll()
.data(drawData)
.enter()
.append('path')
.attr('class', 'pieArc')
.attr('stroke', 'steelblue')
.attr('stroke-width', 1)
.attr('fill', function (d) {
return colorScale(d.index)
})
.attr('d', function (d, i) {
// 根据 pie 数据 计算路径
return arc(d)
})
.transition()
.duration(1000)
.attrTween('d', function (d) {
// 初始加载时的过渡效果
let fn = d3.interpolate(
{
endAngle: d.startAngle
},
d
)
return function (t) {
return arc(fn(t))
}
})
- 创建饼图绘制组
g
,设置好组位置。 - 绑定好饼图数据,通过弧形生成器绘制扇形。
- 最后添加上弧形过度动画。
绘制文本数据
// 求和
let sum = d3.sum(dataArr, (d) => d.value)
- 获取总和,用于计算扇形占比。
arcs
.selectAll()
.data(drawData)
.enter()
.append('text')
.attr('transform', function (d) {
// arc.centroid(d) 将文字平移到弧的中心
return 'translate(' + arc.centroid(d) + ') '
})
// 文字开始点在文字中间
.attr('text-anchor', 'middle')
.attr('fill', '#fff')
// 文字垂直居中
.attr('dominant-baseline', 'central')
.attr('font-size', '10px')
// 格式化文字显示格式
.text(function (d) {
return ((d.data.value / sum) * 100).toFixed(2) + '%'
})
- 文本数据属于饼图的,添加在饼图绘制组
g
下。 - 绑定饼图数据,获取扇形的中心位置。
- 最后设置文本时计算占比。
绘制标签
- 这里和前面的基础饼图一样。创建一个大几倍的扇形生成器,取其中心绘制标签。
- 计算文本偏移量,设置文本偏移。
// 文本
const arc2 = d3
.arc()
.outerRadius(radius * 2.5)
.innerRadius(0)
/*
* 计算文本水平偏移
**/
const textOffsetM = 10
const scaleTextDx = d3
.scaleLinear()
.domain([0, Math.PI / 2])
.range([textOffsetM, textOffsetM * 3])
function computeTextDx(d) {
// 计算文本水平偏移
const middleAngle = (d.endAngle + d.startAngle) / 2
let dx = ''
if (middleAngle < Math.PI) {
dx = scaleTextDx(Math.abs(middleAngle - Math.PI / 2))
} else {
dx = -scaleTextDx(Math.abs(middleAngle - (Math.PI * 3) / 2))
}
return dx
}
arcs
.selectAll()
.data(drawData)
.enter()
.append('text')
.attr('class', (d) => `text${d.index}`)
.attr('text-anchor', (d) => {
// 根据弧度 设置 文本排列方式
return (d.endAngle + d.startAngle) / 2 > Math.PI ? 'end' : 'start'
})
.attr('stroke', 'steelblue')
.attr('dy', '0.35em')
.attr('dx', computeTextDx)
.attr('transform', (d) => {
return 'translate(' + arc2.centroid(d) + ')'
})
.text((d) => d.data.label + ': ' + d.data.value)
- 同理的绘制连线方式也是一样。
- 根据饼图数据计算出连线点,创建线生成器,绘制连线。
// 生成连线的点
const linePoints = drawData.map((d) => {
const line = []
const tempPoint = arc2.centroid(d)
const tempDx = computeTextDx(d)
const dx = tempDx > 0 ? tempDx - textOffsetM : tempDx + textOffsetM
line.push(arc.centroid(d))
line.push(tempPoint)
line.push([tempPoint[0] + dx, tempPoint[1]])
return line
})
const generateLine = d3
.line()
.x((d) => d[0])
.y((d) => d[1])
arcs
.selectAll()
.data(linePoints)
.enter()
.insert('path', ':first-child')
.classed('line', true)
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('d', generateLine)
添加交互
function arcTween(outerRadius) {
return function () {
d3.select(this)
.transition()
.attrTween('d', function (d) {
if (outerRadius > 0) {
d3.select(`.text${d.index}`).attr('stroke', '#AAACC2')
} else {
d3.select(`.text${d.index}`).attr('stroke', 'steelblue')
}
let interpolate = d3.interpolate(radius, radius + outerRadius)
return function (t) {
let arcT = d3.arc().innerRadius(50).outerRadius(interpolate(t)).cornerRadius(5)
return arcT(d)
}
})
}
}
d3.selectAll('.pieArc').on('mouseover', arcTween(20)).on('mouseout', arcTween(0))
- 通过扇形标识符获取扇形对象组。
- 创建交互函数,在监听事件中获取对应的扇形对象。
- 通过扇形对象获取绑定的饼图数据。
- 通过唯一标识符修改文本标签颜色,和当前扇形对象的大小。
- 代码地址