如何修改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
修改说明
- 扩大弧形范围:调整
pie()的startAngle和endAngle参数为-Math.PI / 1.45和Math.PI / 1.45,让弧形覆盖更宽的角度区间。 - 弧形圆角处理:创建
arc生成器时添加.cornerRadius(arcThickness)配置,为弧形内外边缘设置与厚度一致的圆角。 - 添加尖端实心圆:通过
svg.append('circle')创建圆形元素,利用arc.centroid()计算弧形末端位置,配合过渡动画让圆点随弧形变化同步移动,同时添加阴影效果增强视觉层次感。
内容的提问来源于stack exchange,提问作者The Old County
相关产品推荐
相关产品推荐

