携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
引入D3模块
- 引入整个D3模块。
<!-- D3模块 -->
<script src="https://d3js.org/d3.v7.min.js"></script>
数据
- 自定义数据格式。
const dataTree = {
name: '太刀',
children: [
{
name: '矿石',
children: [
{
name: '结晶矿',
children: [
{ name: '蓝矿', num: 10 },
{ name: '黑铁矿', num: 3 },
{ name: '白灰矿', num: 4 }
]
}
]
},
{
name: '木材',
children: [
{
name: '稀木',
children: [
{ name: '钴木', num: 4 },
{ name: '黑木', num: 2 }
]
},
{
name: '水木',
children: [{ name: '蓝木', num: 4 }]
}
]
},
{
name: '宝石',
children: [
{
name: '太阳类',
children: [
{ name: '日金石', num: 6 },
{ name: '熔岩石', num: 1 }
]
},
{
name: '深海类',
children: [
{ name: '寒铁石', num: 2 },
{ name: '金晶石', num: 3 },
{ name: '玄冰结晶', num: 2 }
]
}
]
}
]
}
添加画布
- 初始化画布。
var width = 900
var height = 600
var margin = 30
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(${2 * margin}, ${2 * margin})`)
比例尺
const colorScale = d3.scaleOrdinal(d3.schemeSet3)
- 颜色比例尺。
const rootTree = d3
.hierarchy(dataTree)
.sum((d) => d.num) // 计算绘图属性value的值 -求和 其子节点所有.num属性的和值
.sort((a, b) => a.value - b.value) // 根据 上面计算出的value属性 排序
- 修改数据信息,为其添加树形结构信息并排序。
const pack = d3.pack().size([width - 4 * margin, height - 4 * margin])(rootTree)
- 使用
.pack()创建圆形布局信息,并对数据添加位置信息。
绘制圆形
const rectChart = chart.append('g')
- 创建绘制组。
const rectChartG = rectChart
.selectAll()
.data(pack.descendants())
.enter()
.append('g')
.attr('class', (d, i) => 'g g-' + i)
.descendants()返回后代节点数组。- 对每一个节点创建单独的绘制组。
rectChartG
.append('circle')
.attr('class', 'circle')
.attr('cx', (d) => d.x)
.attr('cy', (d) => d.y)
.attr('r', (d) => d.r)
.attr('fill', (d, i) => colorScale(d.data.name))
- 绘制圆形。
rectChartG
.append('text')
.attr('class', 'text')
.attr('transform', (d) => 'translate(' + d.x + ',' + d.y + ')')
.text((d) => d.data.name)
.attr('fill', '#000000')
.attr('text-anchor', 'middle')
.attr('dy', function () {
return this.getBBox().height / 4
})
.text(function (d) {
if (d.children) return
if (textWidthIsOk(d, this)) {
return d.data.name
} else {
return '...'
}
})
// 检测文本长度是否合适
function textWidthIsOk(d, text) {
const textWidth = text.getBBox().width
if (d.r * 2 >= textWidth) return true
return false
}
- 绘制文本。
- 注意:这里需要文本超出...,需要先加载一次文本才能获取文本长度。
添加交互
var tooltips = d3
.select('body')
.append('div')
.style('width', 'auto')
.style('height', '40px')
.style('background-color', '#fff')
.style('dispaly', 'flex')
.style('justify-content', 'center')
.style('padding', '10px')
.style('border-radius', '5px')
.style('opacity', 0)
d3.selectAll('.g > .circle')
.on('mouseover', function (e, d) {
tooltips
.html(`类型:${d.data.name}<br /> 数据:${d.value}%`)
.style('position', 'absolute')
.style('left', `${e.clientX}px`)
.style('top', `${e.clientY}px`)
.style('opacity', 1)
d3.select(e.target).attr('fill', 'white')
})
.on('mouseleave', function (e, d) {
tooltips.style('opacity', 0).style('left', `0px`).style('top', `0px`)
d3.select(e.target).attr('fill', colorScale(d.data.name))
})
- 创建信息框,监听鼠标事件,获取对象信息展示在对应位置。
- 文本对象在圆形对象上面,鼠标移入文本时效果就会消失。监听文本,修改圆形对象的样式。
d3.selectAll('.g > .text')
.on('mouseover', function (e, d) {
tooltips
.html(`类型:${d.data.name}<br /> 数据:${d.value}%`)
.style('position', 'absolute')
.style('left', `${e.clientX}px`)
.style('top', `${e.clientY}px`)
.style('opacity', 1)
d3.select(e.target.previousSibling).attr('fill', 'white')
})
.on('mouseleave', function (e, d) {
tooltips.style('opacity', 0).style('left', `0px`).style('top', `0px`)
d3.select(e.target.previousSibling).attr('fill', colorScale(d.data.name))
})
- 前面把文本对象和圆形对象,绘制在一组中。通过
e.target.previousSibling获取前一个兄弟节点,选中它直接修改样式就行。