如何在React+D3.js柱状图的条形上方叠加数值文本?
在React + D3.js柱状图的条形上方添加数值标签
我用React结合D3.js构建了柱状图,现在想在每个条形上方显示对应数值,但尝试添加文本标签后,虽然DOM里能看到text元素,却在图表中看不到内容。当前组件代码如下:
import { useRef, useEffect } from "react"; import { select, axisBottom, axisLeft, scaleLinear, scaleBand } from "d3"; import styles from './BarChart.module.scss'; export type IBarChartProps = { data: {label: string, value: number}[]; height: number; width: number; } const MAX_STAT = 200; function BarChart({ data, height, width } : IBarChartProps) { const svgRef = useRef<SVGSVGElement | null>(null); useEffect(() => { const svg = select(svgRef.current); const xScale = scaleBand() .domain(data.map((value, index) => index.toString())) .range([0, width]) .padding(0.1); const yScale = scaleLinear() .domain([0, MAX_STAT]) .range([height, 0]); const xAxis = axisBottom(xScale) .ticks(data.length) .tickFormat((_, index) => data[index].label); svg .select("#x-axis") .style("transform", `translateY(${height}px)`) .style("font-size", '16px') .call(xAxis as any); const yAxis = axisLeft(yScale); svg .select("#y-axis") .style("font-size", '16px') .call(yAxis as any); svg.selectAll('g.tick'); svg .selectAll(".bar") .data(data) .join("rect") .attr("class", "bar") .style("transform", "scale(1, -1)") .attr("x", (_, index) => xScale(index.toString()) as any) .attr("y", -height) .attr("width", xScale.bandwidth()) .transition() .attr("fill","#0072BD") .attr("height", (value) => height - yScale(value.value)); svg .selectAll(".bar") .append('text') .text('score') }, [data]); return ( <div className={styles.BarChart__Container} style={{ height: `${height + 50}px`, width: `${width + 50}px` }}> <svg className={styles.BarChart} ref={svgRef} style={{ height: `${height}px`, width: `${width}px` }}> <g id="x-axis" /> <g id="y-axis" /> </svg> </div> ); } export default BarChart;
问题原因
- SVG的
<rect>是图形元素,无法包含<text>子元素,所以append到.bar上的文本不会被渲染 - 条形设置了
transform: scale(1, -1)的翻转效果,文本继承该变换后位置完全错乱
解决方案
单独创建文本元素,和条形矩形分开处理,同时计算正确的坐标位置:
修改useEffect中的文本处理逻辑,替换为以下代码:
useEffect(() => { const svg = select(svgRef.current); const xScale = scaleBand() .domain(data.map((value, index) => index.toString())) .range([0, width]) .padding(0.1); const yScale = scaleLinear() .domain([0, MAX_STAT]) .range([height, 0]); const xAxis = axisBottom(xScale) .ticks(data.length) .tickFormat((_, index) => data[index].label); svg .select("#x-axis") .style("transform", `translateY(${height}px)`) .style("font-size", '16px') .call(xAxis as any); const yAxis = axisLeft(yScale); svg .select("#y-axis") .style("font-size", '16px') .call(yAxis as any); // 处理条形矩形 svg .selectAll(".bar") .data(data) .join("rect") .attr("class", "bar") .style("transform", "scale(1, -1)") .attr("x", (_, index) => xScale(index.toString()) as any) .attr("y", -height) .attr("width", xScale.bandwidth()) .transition() .attr("fill","#0072BD") .attr("height", (value) => height - yScale(value.value)); // 单独处理数值文本标签 svg .selectAll(".bar-label") .data(data) .join("text") .attr("class", "bar-label") // 文本x坐标:条形中间位置 .attr("x", (_, index) => (xScale(index.toString()) as number) + xScale.bandwidth() / 2) // 文本y坐标:条形顶部上方10px处 .attr("y", d => yScale(d.value) - 10) // 文本居中对齐 .attr("text-anchor", "middle") .style("font-size", "14px") .style("fill", "#333") .text(d => d.value); }, [data]);
关键说明
- 用
.bar-label选择器独立创建文本元素,不与条形矩形绑定 x坐标设置为条形的中间位置,确保文本居中y坐标通过yScale(d.value)获取条形顶部的基准位置,再减10px让文本和条形保持间距- 设置
text-anchor: middle保证文本水平居中,同时配置字体样式确保可见 - 文本无需继承条形的翻转transform,直接用yScale计算正向位置即可
内容的提问来源于stack exchange,提问作者Jon_B
相关产品推荐
相关产品推荐

