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
相关产品推荐
相关产品推荐

