学习D3.js(十四)漏斗图

560 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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多边形来绘制梯形。

image.png

绘制文本

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')
  • 计算出每个文本的位置,绘制到梯形中间。

image.png

添加交互

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对象。

4.gif