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

D3.js如何在Y轴自动刻度外额外添加均值对应的刻度与标签

问题

我正在使用d3创建SVG,实现代码如下:

复现代码

JavaScript

// 1 DATA 
const data = [
    { "x": 50, "y": 0.10 },
    { "x": 100, "y": 0.15 },
    { "x": 150, "y": 0.25 },
    { "x": 200, "y": 0.35 },
    { "x": 250, "y": 0.45 },
    { "x": 300, "y": 0.55 },
    { "x": 350, "y": 0.65 },
    { "x": 400, "y": 0.75 },
    { "x": 450, "y": 0.85 },
    { "x": 500, "y": 0.87 },
    { "x": 550, "y": 0.92 },
    { "x": 600, "y": 0.98 }
]

height = 400,
  width = 720;

padding = {
  top: 70,
  bottom: 50,
  left: 70,
  right: 70
}


const boundHeight = height - padding.top - padding.bottom;
const boundWidth = width - padding.right - padding.left;

// 2 CREATE SCALE 
const scaleY = d3.scaleLinear()
  .range([boundHeight, 0])
  .domain(d3.extent(data, d => d.y))

// 3 SVG
const svgns = 'http://www.w3.org/2000/svg'
const svg = d3.select('svg')

svg
  .attr('xmlns', svgns)
  .attr('viewBox', `0 0 ${width} ${height}`)

//create bound element
bound = svg.append('g')
  .attr('class', 'bound')
  .style('transform', `translate(${padding.left}px,${padding.top}px)`)


bound.append('g').attr('class', 'yAxis1')
  .append('g').attr('class', 'yAxisLeft')
  .call(d3.axisLeft(scaleY).tickSizeInner([(-(boundWidth))])
    .tickFormat(d3.format(",.0%")))

console.log(d3.mean(data.map(d => d.y)))

HTML

<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>

<svg></svg>
<div id="container" class="svg-container"></div>
<script src="next.js"></script>

经计算,data.y的均值为0.5724999999999999,需要在自动生成的y-axis刻度与标签之外,额外为该均值创建对应的刻度线与标签。


实现方法

有两种常用方案,按需选择即可:

方案1:轴渲染完成后手动追加元素

不改动原有轴生成逻辑,在y轴渲染完成后,手动往轴的<g>容器里加均值对应的刻度线和文本,灵活度最高:

  • 先算出y值均值,用已有的scaleY把均值映射为SVG纵向坐标
  • 追加刻度线,长度和原有内刻度保持一致(横跨整个绘图区宽度),可加虚线样式和普通刻度做区分
  • 追加刻度标签,对齐规则、格式和原有轴标签统一
  • 给均值元素加独立class,方便后续自定义样式

核心代码,追加在原有y轴渲染逻辑之后即可:

// 计算均值
const yMean = d3.mean(data, d => d.y);
const yAxisG = bound.select('.yAxisLeft');

// 追加均值刻度线
yAxisG.append('line')
  .attr('class', 'tick mean-tick')
  .attr('stroke', '#000')
  .attr('y1', scaleY(yMean))
  .attr('y2', scaleY(yMean))
  .attr('x1', 0)
  .attr('x2', -boundWidth)
  .style('stroke-dasharray', '4 2'); // 虚线样式区分普通刻度,不需要可删掉

// 追加均值标签
yAxisG.append('text')
  .attr('class', 'tick mean-label')
  .attr('fill', '#000')
  .attr('y', scaleY(yMean))
  .attr('x', -9)
  .attr('dy', '0.32em')
  .attr('text-anchor', 'end')
  .text(`均值 ${d3.format(",.1%")(yMean)}`);

方案2:通过轴原生tickValues配置实现

利用d3轴生成器的tickValues方法,把均值追加到默认刻度列表里,让轴自动渲染均值刻度,样式对齐完全和原生刻度一致:

const yMean = d3.mean(data, d => d.y);
// 拿到线性比例尺自动生成的默认刻度
const defaultTicks = scaleY.ticks();
// 把均值加入刻度列表
const allTicks = [...defaultTicks, yMean];

bound.append('g').attr('class', 'yAxis1')
  .append('g').attr('class', 'yAxisLeft')
  .call(d3.axisLeft(scaleY)
    .tickValues(allTicks)
    .tickSizeInner([(-boundWidth)])
    .tickFormat(d => {
      // 均值单独格式化,其余刻度用原有百分比格式
      if (Math.abs(d - yMean) < 1e-9) return `均值 ${d3.format(",.1%")(d)}`;
      return d3.format(",.0%")(d);
    }))
  // 给均值刻度加特殊样式
  .selectAll('.tick')
  .filter(d => Math.abs(d - yMean) < 1e-9)
  .classed('mean-tick', true)
  .select('line')
  .style('stroke-dasharray', '4 2');

注意:JS存在浮点数精度问题,筛选均值刻度时不要直接用===判断相等,加一个极小的误差阈值(比如上面代码里的1e-9)避免匹配失败。


内容的提问来源于stack exchange,提问作者smpa01

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 22:36:21