学习D3.js(十七)仪表盘

1,826 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

引入D3模块

  • 引入整个D3模块。
<!-- D3模块 -->
<script src="https://d3js.org/d3.v7.min.js"></script>

数据

  • 仪表盘只需要传入一个值。
const data = 95

添加画布

  • 初始化画布。
    // 画布
    const width = 500
    const height = 500
    const margin = 30
    const svg = d3
      .select('.d3Chart')
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .style('background-color', '#1a3055')
    // 图
    const chart = svg.append('g')

比例尺和配置信息

const colorScale = d3.scaleOrdinal(d3.schemeSet3)
  • 颜色比例尺。
const radius = d3.min([width - 4 * margin, height - 4 * margin]) / 2
  • 仪表盘半径。
// 分段值
const nums = [0, 50, 90, 100]
const numLength = 100 // 仪表盘总长度
const anglelength = 280 // 仪表盘 度数
let angleDraw = []
nums.forEach((item, i) => {
  if (i !== nums.length - 1) {
    const angleS = (item / numLength) * anglelength
    const angleE = (nums[i + 1] / numLength) * anglelength
    angleDraw.push({
      startAngle: (angleS - anglelength / 2) * (Math.PI / 180),
      endAngle: (angleE - anglelength / 2) * (Math.PI / 180)
    })
  }
})
  • 设置分段值,计算出仪表盘的分段的弧度值。
let innerArc = 25 // 仪表盘 条形宽度
  • 仪表盘宽度。

绘制仪表盘弧形

// 创建 扇形绘制器
const arc = d3
  .arc()
  .outerRadius(radius)
  .innerRadius(radius - innerArc)
  • 创建扇形绘制器,设置内半径(.innerRadius)值为半径减去仪表盘宽度。只绘制最外层的一条。
chart
  .append('g')
  .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
  .selectAll()
  .data(angleDraw)
  .enter()
  .append('path')
  .attr('class', (d, i) => ' arc-' + i)
  .attr('d', arc)
  .attr('fill', (d, i) => colorScale(i))
  • 绑定弧形分段信息,分段绘制出一个仪表盘弧形。

image.png

const kedu = []
for (let i = -anglelength / 2; i <= anglelength / 2; i += anglelength / 100) {
  kedu.push((i * Math.PI) / 180)
}
  • 使用和上面相同的弧度计算方法并分成100份,得到100个刻度的弧度的位置。
    const ticks = chart
      .append('g')
      .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
      .selectAll()
      .data(kedu)
      .enter()
      .append('g')
      .attr('class', 'ticks')
      .each(drawTicks)
      .each(drawLabels)

    function drawTicks(d, i) {
      if (i === 0 || i === 100) return
      const innerRadius = i % 10 === 0 ? radius - innerArc : radius - innerArc / 3
      d3.select(this)
        .append('line')
        .attr('stroke', '#000000')
        .attr('x1', Math.sin(d) * radius)
        .attr('y1', -Math.cos(d) * radius)
        .attr('x2', Math.sin(d) * innerRadius)
        .attr('y2', -Math.cos(d) * innerRadius)
    }

    function drawLabels(d, i) {
      let textAnchor = 'end'
      if (i === 50) {
        textAnchor = 'middle'
      }
      if (i % 10 === 0) {
        const textRadius = radius - innerArc - 15
        d3.select(this)
          .append('text')
          .attr('class', 'label')
          .attr('x', Math.sin(d) * textRadius)
          .attr('y', -Math.cos(d) * textRadius)
          .attr('dy', 5.5)
          .attr('stroke', '#ffffff')
          .attr('text-anchor', d < -0.01 ? 'start' : textAnchor)
          .text((i / 100) * 100)
      }
    }
  1. .each() 对集合中的单个元素依次进行操作,并获取DOM元素的上下文。
  • 绑定刻度信息,为每个刻度创建绘制组
  • 因为绘制刻度和标签都需要进行一些计算不能直接绘制。
  • 使用.each()获取DOM元素,定制在不同的刻度下进行不同的绘制。

image.png

绘制指针

// 指针位置点 计算
function points() {
  // 顶点
  const upper = Math.floor(radius - innerArc - 50)
  // 两边 中间点
  const short = Math.floor(upper * 0.13)
  // 两边点
  const both = Math.floor(short * 0.6)
  return ['0,' + short, both + ',0', '0,' + -upper, -both + ',0']
}

const pointer = chart
  .append('g')
  .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
  .selectAll()
  .data([data])
  .enter()
  .append('polygon')
  .attr('class', 'pointer')
  .attr('points', points)
  .attr('fill', colorScale(0))
  .attr('transform', 'rotate(' + -0.5 * anglelength + ')')
  • 使用多边形绘制指针,先计算出指针个点坐标位置,通过旋转修改指针指向 0 点。
pointer.transition().duration(1000).attrTween('transform', rotateTween).attrTween('fill', fillTween)

function rotateTween(d) {
  return function (t) {
    return 'rotate(' + ((d * t) / numLength - 0.5) * anglelength + ')'
  }
}
function fillTween(d) {
  return function (t) {
    let i = 0
    while (i < nums.length - 1 && nums[i] < d * t) {
      i++
    }
    return colorScale(i - 1)
  }
}
  • 通过.attrTween()制作指针旋转动画和根据刻度修改指针颜色的动画。

1.gif