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

如何在D3中正确设置环形X轴标签?解决重叠与旋转问题

问题描述

我用以下D3代码生成环形X轴标签:

const xAxis = (g) => g
  .attr("font-family", "sans-serif")
  .attr("font-size", 10)
  .call((g) => g.selectAll("g")
    .data(d3.range(0, 24)) // 创建小时数组
    .join("g")
      .each((d, i) => d.id = `hour-${i}`)
      .call((g) => g.append("path")
          .attr("stroke", "#000")
          .attr("stroke-opacity", 0.2)
          .attr("d", (d) => `
            M${d3.pointRadial(x(d * 60), innerRadius)}
            L${d3.pointRadial(x(d * 60), outerRadius)}
          `))
      .call((g) => g.append("path")
          .attr("id", (d) => `hour-${d}`)
          .datum((d) => [d * 60, (d + 1) * 60])
          .attr("fill", "none")
          .attr("d", ([a, b]) => `
            M${d3.pointRadial(x(a), innerRadius)}
            A${innerRadius},${innerRadius} 0,0,1 ${d3.pointRadial(x(b), innerRadius)}
          `))
      .call((g) => g.append("text")
        .append("textPath")
          .attr("startOffset", 6)
          .attr("href", (d) => `#hour-${d}`)
          .text((d) => d.toString().padStart(2, "0"))
          .attr("text-anchor", "middle")
          .attr("alignment-baseline", "middle")
          .attr("transform", (d) => {
            const angle = x(d * 60);
            const radius = innerRadius - 20; // 缩小半径让标签更靠近中心
            return `rotate(${(angle * 180 / Math.PI - 90)}) translate(${radius},0) rotate(${(90 - angle * 180 / Math.PI)})`;
          })
      )
  );

当前效果中,00和12标签与径向刻度线重叠,同时大部分标签处于旋转状态,可读性差。我需要:

  • 让所有标签保持水平(非旋转)状态,提升可读性
  • 避免标签与刻度线重叠,且标签位于对应刻度线的中心位置
解决方案

1. 核心修改思路

移除依赖圆弧路径的textPath,直接通过坐标计算定位文本,同时调整标签半径避免与刻度线重叠,强制所有文本保持正立可读状态。

2. 修改后的完整代码

const xAxis = (g) => g
  .attr("font-family", "sans-serif")
  .attr("font-size", 10)
  .call((g) => g.selectAll("g")
    .data(d3.range(0, 24))
    .join("g")
      // 保留径向刻度线
      .call((g) => g.append("path")
          .attr("stroke", "#000")
          .attr("stroke-opacity", 0.2)
          .attr("d", (d) => `
            M${d3.pointRadial(x(d * 60), innerRadius)}
            L${d3.pointRadial(x(d * 60), outerRadius)}
          `))
      // 添加水平标签,无需textPath
      .call((g) => g.append("text")
          .text((d) => d.toString().padStart(2, "0"))
          .attr("text-anchor", "middle")
          .attr("alignment-baseline", "middle")
          .attr("x", (d) => {
            const angle = x(d * 60);
            // 标签半径比innerRadius小30,避免与刻度线重叠
            return Math.cos(angle) * (innerRadius - 30);
          })
          .attr("y", (d) => {
            const angle = x(d * 60);
            return Math.sin(angle) * (innerRadius - 30);
          })
          // 强制文本正立,不旋转
          .attr("transform", (d) => {
            const angle = x(d * 60) * 180 / Math.PI;
            // 对下半部分标签旋转180°,保证文本始终可读
            const rotateAngle = angle > 90 && angle < 270 ? angle + 180 : angle;
            const centerX = Math.cos(x(d*60))*(innerRadius-30);
            const centerY = Math.sin(x(d*60))*(innerRadius-30);
            return `rotate(${rotateAngle}, ${centerX}, ${centerY})`;
          })
      )
  );

3. 关键改动说明

  • 移除冗余元素:删除用于textPath的圆弧路径代码,简化结构
  • 坐标定位:用三角函数计算标签的精准位置,通过innerRadius - 30将标签放在刻度线内侧,避免重叠
  • 文本正立处理:判断标签角度,对下半部分(90°-270°)的标签旋转180°,确保所有文本都是正立可读的
  • 灵活调整:如果00和12标签仍有重叠,可单独调整它们的半径:
    .attr("x", (d) => {
      const angle = x(d * 60);
      const labelRadius = (d === 0 || d === 12) ? innerRadius - 35 : innerRadius - 30;
      return Math.cos(angle) * labelRadius;
    })
    .attr("y", (d) => {
      const angle = x(d * 60);
      const labelRadius = (d === 0 || d === 12) ? innerRadius - 35 : innerRadius - 30;
      return Math.sin(angle) * labelRadius;
    })
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 09:37:10