携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
开始绘制
引入D3模块
- 引入整个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 = 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})`)
创建比例尺
var widthScale = d3
.scaleLinear()
.domain([0, d3.max(dataArr, (d) => d.value)])
.range([0, width * 0.5])
- 以数据的最大值创建,宽度比例尺。
var colorScale = d3.scaleOrdinal(d3.schemeSet3)
- 颜色比例尺。
var handleData = dataArr
.sort((a, b) => b.value - a.value)
.map((d, i, array) => {
// 获取下一个数据的 值 最后一个 值设置为0
if (i !== array.length - 1) {
d.nextValue = array[i + 1].value
} else {
d.nextValue = 0
}
d.index = i
return d
})
- 对数据排序,值大的排在前面。为每个数据对象,添加下一个数据的值,用于梯形坐标计算。
绘制梯形
var funnelChart = chart.append('g').attr('transform', 'translate(' + (width - 2 * margin) / 2 + ',0)')
- 创建梯形绘制组,移动到画布中间开始绘制。
/**
* 计算梯形的点坐标
* */
function getPoints(topWidth, bottomWidth, height) {
const points = []
points.push(-topWidth / 2 + ',' + 0)
points.push(topWidth / 2 + ',' + 0)
if (bottomWidth === 0) {
// 最后 一个 以三角行结尾
points.push(0 + ',' + height)
} else {
points.push(bottomWidth / 2 + ',' + height)
points.push(-bottomWidth / 2 + ',' + height)
}
return points.join(' ')
}
- 通过使用比例尺后的值,计算出每个梯形边顶点的位置。
// 梯形高度
var funnelHeight = 60
funnelChart
.selectAll()
.data(handleData)
.enter()
.append('polygon')
.attr('class', (d, i) => 'trap + trap-' + i)
.attr('points', (d) => getPoints(widthScale(d.value), widthScale(d.nextValue), funnelHeight))
.attr('transform', (d, i) => 'translate(0,' + i * (5 + funnelHeight) + ')')
.attr('fill', (d) => colorScale(d.label))
- 通过
points
多边形来绘制梯形。
绘制文本
funnelChart
.selectAll()
.data(handleData)
.enter()
.append('text')
.attr('class', (d, i) => 'label + label-' + i)
.text((d) => d.label)
.attr('text-anchor', 'middle')
.attr('x', 0)
.attr('y', function (d, i) {
return i * (5 + funnelHeight) + funnelHeight / 2 + this.getBBox().height / 4
})
.attr('stroke', '#000000')
- 计算出每个文本的位置,绘制到梯形中间。
添加交互
d3.selectAll('.trap')
.on('mouseover', function (e, d) {
d3.select(e.target).attr('fill', 'white')
})
.on('mouseleave', function (e, d) {
d3.select(e.target).attr('fill', colorScale(d.label))
})
d3.selectAll('.label')
.on('mouseover', function (e, d) {
d3.select('.trap-' + d.index).attr('fill', 'white')
})
.on('mouseleave', function (e, d) {
d3.select('.trap-' + d.index).attr('fill', colorScale(d.label))
})
- 监听梯形DOM对象时,能直接获取到对应的对象修改。当鼠标移入文本时就没有效果,因为文本在梯形上层,所以在创建梯形时给它们都设置一个唯一标识。用于对文本监听时能方便修改梯形DOM对象。