You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改D3.js仪表盘图表:延伸弧形、圆角边缘及添加尖端实心圆

D3.js仪表盘样式修改实现方案

修改需求

  • 让弧形向后延伸更大范围
  • 将弧形边缘设置为圆角
  • 在弧形的尖端添加一个实心圆

效果对比

  • 目标效果:目标效果
  • 当前效果:当前效果

初始实现代码

import React from 'react'
import * as d3 from 'd3'

class GaugeChart extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
    this.state = {
      data: [],
      theme: this.props.theme
        ? this.props.theme
        : ['#bde0fe', '#2698f9', '#71bcfd', '#f1f8fe'],
    }
  }

  componentDidMount() {
    var $this = this.myRef.current

    d3.select($this).selectAll('svg').remove()

    const value = this.props.value
    const data = [
      {
        label: 'Completed',
        value: value,
      },
      {
        label: 'Remaining',
        value: 100 - value,
      },
    ]

    const width = parseInt(this.props.width, 10),
      height = parseInt(this.props.height, 10),
      radius = parseInt(this.props.r, 10),
      innerradius = parseInt(this.props.ir, 10)

    var color = d3.scaleOrdinal().range(this.state.theme)

    var arc = d3.arc().outerRadius(radius).innerRadius(innerradius)

    data.forEach(function (d) {
      d.total = +d.value
    })

    var pie = d3
      .pie()
      .startAngle(-90 * (Math.PI / 180))
      .endAngle(90 * (Math.PI / 180))
      .padAngle(0.02) // 扇区间隔
      .sort(null)
      .value(function (d) {
        return d.total
      })

    var svg = d3
      .select($this)
      .append('svg')
      .attr('width', width)
      .attr('height', height + 5)
      .append('g')
      .attr('class', 'piechart')
      .attr('transform', 'translate(' + width / 2 + ',' + height + ')')

    var segments = svg.append('g').attr('class', 'segments')

    var slices = segments
      .selectAll('.arc')
      .data(pie(data))
      .enter()
      .append('g')
      .attr('class', 'arc')

    slices
      .append('path')
      .attr('d', arc)
      .attr('fill', function (d, i) {
        return color(i)
      })
    /*.transition()
          .attrTween('d', function(d) {
            var i = d3.interpolate(d.startAngle + 0.1, d.endAngle);
            return function(t) {
              d.endAngle = i(t);
              return arc(d);
            }
          } )*/

    var arrow = svg.append('g').attr('transform', 'rotate( 0  )')

    arrow
      .append('path')
      .attr('fill', '#ef7e60')
      .attr(
        'd',
        `M -${innerradius - 5},0 -${innerradius - 12},-5 -${
          innerradius - 12
        },5`,
      ) // 绘制三角形指针

    var text = svg
      .append('text')
      .attr('text-anchor', 'middle')
      .attr('dy', 0)
      .attr('fill', '#ef7e60')

    function update(data) {
      text.text(`${Math.round(data[0].value)}%`) // 设置百分比文本

      arrow.transition().attr('transform', `rotate( ${data[0].value * 1.8}  )`) // 旋转到对应数值位置
    }
    update(data)
  }

  render() {
    return <div ref={this.myRef} className="GaugeChart" />
  }
}
export default GaugeChart

完成修改后的实现代码

import React from 'react'
import * as d3 from 'd3'

class GaugeChart extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
    this.state = {
      data: [],
      theme: this.props.theme
        ? this.props.theme
        : ['#bde0fe', '#2698f9', '#71bcfd', '#f1f8fe'],
    }
  }

  componentDidMount() {
    var $this = this.myRef.current

    d3.select($this).selectAll('svg').remove()

    const value = this.props.value
    const data = [
      {
        sortIndex: 0,
        value: value,
        fill: 'blue',
      },
      {
        sortIndex: 0,
        value: 100 - value,
        fill: '#EAEDF6',
      },
    ]
    let arcThickness = 4
    let size = 50
    let transitionDuration = 750

    const t = d3.transition().duration(350)
    const arc = d3
      .arc()
      .innerRadius(size / 2 - arcThickness)
      .outerRadius(size / 2)
      .cornerRadius(arcThickness)

    const pie = d3
      .pie()
      .value((d) => d.value)
      .startAngle(-Math.PI / 1.45)
      .endAngle(Math.PI / 1.45)
      .sort((a, b) => d3.ascending(a.sortIndex, b.sortIndex))

    const initData = pie(JSON.parse(JSON.stringify(data)))
    const arcs = pie(data)

    function arcTween(d, bIsLastArc) {
      var interpolateStart = d3.interpolate(-Math.PI / 1.45, d.startAngle)
      var interpolateEnd = d3.interpolate(
        bIsLastArc ? d.endAngle : -Math.PI / 1.45,
        d.endAngle,
      )

      return function (t) {
        d.startAngle = interpolateStart(t)
        d.endAngle = interpolateEnd(t)
        return arc(d)
      }
    }
    const svg = d3
      .select($this)
      .append('svg')
      .attr('viewBox', [-size / 2, -size / 2, size, size])
    // 添加定义和数值点的阴影
    svg.append('defs')
      .html(`<filter id="dot-shadow" x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox">
      <feDropShadow dx="3" dy="3" stdDeviation="8" flood-color="${initData[0].data.fill}" flood-opacity="0.25" />
    </filter>`)

    // 仪表盘骨架

    const arcSkeleton = d3
      .arc()
      .innerRadius(size / 2 - arcThickness)
      .outerRadius(size / 2)
      .startAngle(-90 * (Math.PI/131))
      .cornerRadius(arcThickness)
      .endAngle(90 * (Math.PI/131))

    svg
      .append('path')
      .attr('class', 'arc-skeleton')
      .attr('d', arcSkeleton)
      .attr('fill', '#EAEDF6')

    const gauges = svg
      .selectAll('path.gaugechart')
      .data(arcs)
      .join('path')
      .attr('class', 'gaugechart')
      .attr('fill', (d) => d.data.fill)
      .attr('d', arc)

    gauges
      .transition()
      .duration(transitionDuration)
      .attrTween('d', (d, i) => arcTween(d, i == arcs.length - 1))

    // 在第一个弧形的末端添加圆点标记
    const dot = svg
      .selectAll('circle')
      .data([
        Object.assign(initData[0], {
          startAngle: initData[0].endAngle - 0.2,
          endAngle: initData[1].startAngle,
          stroke: initData[initData.length - 1].data.fill,
        }),
      ])
      .join('circle')
      .attr('fill', '#fff')
      .attr('r', arcThickness * 0.3)
      .attr('stroke', (d) => d.stroke)
      .attr('stroke-width', 0.1)
      .attr('style', 'filter:url(#dot-shadow)')
      .attr('transform', (d) => `translate(${arc.centroid(d)})`)

    dot
      .transition()
      .duration(transitionDuration)
      .attrTween('transform', (d) => {
        const interpolateStart = d3.interpolate(-Math.PI / 1.45, d.startAngle)
        const interpolateEnd = d3.interpolate(-Math.PI / 1.45, d.endAngle)
        return function (t) {
          d.startAngle = interpolateStart(t)
          d.endAngle = interpolateEnd(t)
          return `translate(${arc.centroid(d)})`
        }
      })

    svg
      .append('text')
      .attr('transform', `translate(0, ${(0.1 * size) / 2})`)
      .attr('font-size', '0.7rem')
      .attr('text-anchor', 'middle')
      .attr('fill', 'black')
      .text(data[0].value)

    svg
      .append('text')
      .attr('transform', `translate(0, ${0.15 * size})`)
      .attr('font-size', '0.2rem')
      .attr('text-anchor', 'middle')
      .attr('fill', 'grey')
      .text('Volts')
  }

  render() {
    return <div ref={this.myRef} className="GaugeChart" />
  }
}
export default GaugeChart

修改说明

  1. 扩大弧形范围:调整pie()的startAngle和endAngle参数为-Math.PI / 1.45和Math.PI / 1.45,让弧形覆盖更宽的角度区间。
  2. 弧形圆角处理:创建arc生成器时添加.cornerRadius(arcThickness)配置,为弧形内外边缘设置与厚度一致的圆角。
  3. 添加尖端实心圆:通过svg.append('circle')创建圆形元素,利用arc.centroid()计算弧形末端位置,配合过渡动画让圆点随弧形变化同步移动,同时添加阴影效果增强视觉层次感。

内容的提问来源于stack exchange,提问作者The Old County

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 05:37:03