一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情。
开始绘制
引入D3模块
<!-- 选择器模块 -->
<script src="https://cdn.jsdelivr.net/npm/d3-selection@3"></script>
<!-- 比例尺模块 和 依赖 -->
<script src="https://cdn.jsdelivr.net/npm/d3-array@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-color@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-format@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-interpolate@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-time@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-time-format@4"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-scale@4"></script>
<!-- 坐标轴 -->
<script src="https://cdn.jsdelivr.net/npm/d3-axis@3"></script>
创建数据
- 数据格式随意,方便在绑定时使用。
var bColor = ['#4385F4', '#34A853', '#FBBC05', '#E94335', '#01ACC2', '#AAACC2']
var dataArr = [
{
label: '1月',
value: 10.5,
value2: 20.5
},
{
label: '2月',
value: 70.5,
value2: 22.5
},
{
label: '3月',
value: 60.5,
value2: 30.5
},
{
label: '4月',
value: 10.5,
value2: 20.5
},
{
label: '5月',
value: 20.5,
value2: 40.5
},
{
label: '6月',
value: 30.5,
value2: 30.5
}
]
创建画布
- 创建SVG元素,设置样式
var svg = d3
.select('.d3Chart')
.append('svg')
.attr('width', 500)
.attr('height', 500)
.style('background-color', '#1a3055')
创建比例尺
- 比例尺用于数据转换。简单理解就是输入一个规定好的值,输出规定范围内对应的值。
- 详细介绍可以看之前的文章或看官方文档。
// X轴比例尺
var xScale = d3
.scaleBand()
.range([0, 400])
.domain(dataArr.map((s) => s.label))
.padding(0.4)
// y轴比例尺
var yScale = d3.scaleLinear().range([400, 0]).domain([0, 100])
绘制坐标轴
var chart = svg.append('g').attr('transform', 'translate(50, 40)')
chart.append('g').attr('class', 'xAxis')
.attr('transform', 'translate(15, 400)').call(d3.axisBottom(xScale))
var makeYlines = () =>
d3
.axisLeft()
.scale(yScale)
.tickSize(-400)
.tickFormat((d) => {
return d + '%'
})
chart.append('g').attr('class', 'yAxis').attr('transform', 'translate(15, 0)').call(makeYlines())
// 标签
d3.select('.yAxis')
.append('g')
.attr('transform', 'translate(-40, 0)')
.append('text')
.attr('class', 'axisTextY')
.style('font-size', '24px')
.attr('transform', 'rotate(-90)')
.text('比例(%)')
// 标签居中
d3.select('.axisTextY').attr('x', function () {
return -200 + this.getBoundingClientRect().height / 2
})
d3.selectAll('.d3Chart text').style('fill', '#fff')
d3.selectAll('.d3Chart line').style('stroke', '#fff')
d3.selectAll('.d3Chart path').style('stroke', '#fff')
- 在SVG中创建
g组并设定好位置,后续绘图都在它之上绘制。 - 使用(
axis)坐标轴模块,分别绘制 X轴、Y轴。在创建坐标轴的时候分别设置了class属性,作为唯一标识,获取获取元素使用。 - 根据定义的
class属性,获取坐标轴元素。在其子节点创建文本元素,修改其样式以生成标题。
.getBoundingClientRect()javascript中的方法。返回元素的大小及其相对于视口的位置。
绘制柱状
- 前面数据格式是随意制定的。这里重新组装,便于D3绑定数据。
// 组装数据 便于绘制
const items = dataArr.map((row) => {
let item = []
let index = 0
Object.keys(row).forEach((key) => {
// 非数据 不绘制统计图
if (key !== 'label') {
item.push([row.label, row[key], key, index])
index++
}
})
return item
})
- 绑定数据。
// 绑定数据
const groups = chart.selectAll().data(items)
const bars = groups
.enter()
.append('g')
.selectAll()
.data((d) => d)
- 选中最开始创建的
g元素,绑定数据。 - 使用
.enter().append('g')创建绑定数据个数的子g元素并使数据和元素一一对应。 - 因为数据格式修改为二维数组,最后给每个子
g元素绑定数据。
- 返回结果
bars,简单理解就是选中了所有子g元素,并且给每个子g元素都绑定了数据但是现在数据还没生成对应的元素。
bars
.enter()
.append('rect')
.attr('class', 'rectTool')
.style('fill', (d, i) => bColor[i])
.attr(
'x',
(g, i) => xScale(g[0]) + xScale.bandwidth() / 2 - 4 +
(xScale.bandwidth() / items[0].length + 3) * g[3]
)
.attr('y', (g) => yScale(g[1]))
.attr('width', xScale.bandwidth() / items[0].length)
.attr('height', (g) => 400 - yScale(g[1]))
.enter().append('rect')给每个子g元素的绑定数据生成对应的rect元素。- 然后给每个
rect元素设置样式。 - 这注意:展示的是多列统计图,不能直接使用
.bandwidth()需要计算一下。
添加交互
var tooltips = d3
.select('body')
.append('div')
.style('width', '100px')
.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('.rectTool')
.on('mouseenter', (e, g) => {
tooltips
.html(`月份:${g[0]}<br /> 数据:${g[1]}%`)
.style('position', 'absolute')
.style('left', `${e.clientX}px`)
.style('top', `${e.clientY}px`)
.style('opacity', 1)
})
.on('mouseleave', (e, g) => {
tooltips.style('opacity', 0).style('left', `0px`).style('top', `0px`)
})
- 根据前面设置的
class属性,选中所有的rect元素。给它们添加监听事件,修改提示框的位置和数据。一个简单的多列统计图就完成了。 - 代码地址