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

D3.js分级统计图:如何将文本定位到SVG路径中心?

解决分级统计图(Choropleth Map)文本居中定位问题

问题描述

我正在制作一幅分级统计图(Choropleth Map),需要将每个SVG path与title进行group分组以实现hover effect(悬停效果)。目前我遇到了文本定位难题:当前文本显示在路径顶部,我希望将每个Text放置在对应的Path中心位置。请查看以下代码片段,如何仅通过path而非坐标实现这一需求?
注意:我仅使用path,不使用坐标!
注意2:可通过开启/关闭console.log查看地图。

解决方案

核心问题是你当前代码错误地获取了文本元素自身的边界框(bbox)来计算位置,而不是对应分组里的path元素的边界框。要实现文本在path中心,需修改文本定位逻辑:

修改后的JavaScript代码

document.addEventListener("DOMContentLoaded", function () {
  const h = 400;
  const w = 400;

  var svg = d3
    .select(".map")
    .append("svg")
    .attr("width", w)
    .attr("height", h)
    .attr("viewBox", "0 0 400 400")
    
  var groups = svg
    .selectAll(".groups")
    .data(map)
    .enter()
    .append("g")
    .attr("class", "region");

  groups
    .append("path")
    .attr("d", (m) => m.path)
    .attr("stroke-width", "1")
    .attr("stroke", "#fff")
    .attr("fill","#333")

  groups
    .append("text")
    .text((m) => m.title)
    .style("font-weight", "bold")
    .style("font-family", "Jersey")
    .attr("fill","#6D6E71")
    .attr("text-anchor", "middle") // 水平居中对齐
    .attr("dominant-baseline", "middle") // 垂直居中对齐
    .style("text-transform", "uppercase")
    .attr("x", function() {
      // 获取当前分组对应path的边界框
      const pathBBox = d3.select(this.parentNode).select("path").node().getBBox();
      return pathBBox.x + pathBBox.width / 2; // 计算path的X轴中心
    })
    .attr("y", function() {
      const pathBBox = d3.select(this.parentNode).select("path").node().getBBox();
      return pathBBox.y + pathBBox.height / 2; // 计算path的Y轴中心
    });
});

const map=[{
    id: 0,
    title: "A",
    path: "M 45.25 34.96 l -0.97 -3.35 l -0.97 -2.39 l 2.43 -3.35 l 1.94 -4.31 l 1.94 -5.74 l 3.89 -5.74 l 0.97 6.22 l 0.49 3.35 l 3.89 3.83 l 3.89 -0.96 l 0.97 3.35 l 3.89 0.96 l 0.49 2.87 l 3.89 1.44 l 0.49 1.91 L 76.36 34 l 2.43 -2.87 l 1.46 3.83 l 1.94 2.39 l 2.92 1.91 l 0.97 1.91 l 2.43 -0.96 l 1.46 1.44 l -1.46 3.35 l 1.46 1.44 l 1.94 1.44 v 0.96 l 3.89 -0.48 v 1.91 l 0.97 1.91 v 2.39 l 2.43 1.91 l -0.49 4.31 l 1.94 3.35 l 2.43 3.35 l -0.97 3.35 v 4.79 v 4.31 l -1.46 3.83 l 0.97 4.79 l -2.43 3.35 l -3.4 4.79 l -3.89 -0.96 l -5.35 -0.48 l -1.94 -3.83 l -2.43 -1.91 l 2.92 -5.26 l -1.94 -3.83 l -2.43 -2.87 l -3.89 -1.44 l -1.94 -1.91 l -1.46 -2.87 l -2.43 0.96 c 0 0 -0.61 -0.48 -1.94 -0.96 s -3.4 -0.96 -3.4 -0.96 l -2.43 -0.96 l -2.43 1.91 l -3.89 -0.96 l -3.4 -1.44 l -2.92 1.91 l -0.49 2.39 c 0 0 -1.58 -0.24 -3.4 -0.96 c -1.82 -0.72 -3.89 -1.91 -3.89 -1.91 l -0.97 -1.44 l 3.4 -1.91 l 1.94 -2.39 l -1.46 -2.87 v -6.7 l 5.35 -0.48 l 1.94 -1.91 l 1.94 -2.87 l 4.37 -1.44 l 0.97 -3.35 l 2.43 3.83 l 2.43 -1.44 l 1.94 -3.35 l -1.94 -2.39 l -1.94 -2.87 v -3.35 l -3.4 -2.87 l -4.37 0.96 l -2.43 -3.83 l -2.43 1.91 l -2.92 -0.48 L 45.25 34.96 Z",
  },
  {
    id: 1,
    title: "B",
    path: "M 19.49 126.38 l -2.43 -4.31 l -2.92 -3.83 l 1.94 -4.31 l -4.86 -0.96 l 1.46 -7.66 l 3.4 -2.39 l -0.49 -2.87 l 1.94 -3.83 l 5.83 -3.83 l 1.94 -5.26 l -4.37 -2.39 l 2.43 -4.31 l 0.97 -4.31 l 5.35 -1.91 v -4.79 l 7.29 0.96 l 5.35 -1.44 l 7.29 4.79 l 1.46 -3.83 l 4.96 -1.03 l 4.27 1.51 l 3.89 -0.48 l 9.72 2.39 l 3.89 4.31 l 3.4 1.44 l 4.86 5.74 l -3.4 4.79 l 0.97 3.35 l -2.43 2.39 l -0.97 3.83 h -3.4 l -2.43 -3.83 l -1.46 2.87 h -2.92 l -1.46 5.27 l 1.94 1.91 l -2.43 0.48 l -2.92 4.79 l -0.97 2.87 l -1.46 2.87 l 1.94 2.39 l -2.92 0.48 h -1.94 v -2.39 l -6.32 0.48 v 3.83 h -2.43 l -0.49 4.79 l 2.92 1.44 l -1.94 1.91 v 4.31 l 2.43 1.44 l 2.92 2.39 l -0.49 3.35 l -3.4 0.48 l -2.92 -2.87 l -2.43 -1.44 v -1.92 l -3.4 -3.35 h -2.92 l -0.97 -3.35 l -5.35 -2.87 l 0.49 -2.39 l -0.49 -1.44 l -4.37 1.44 l -2.92 2.39 l -2.43 -2.87 L 19.49 126.38 Z",
  },
  {
    id: 2,
    title: "C",
    path: "M 98.24 55.54 l 2.43 -2.39 l 2.43 0.48 l 2.43 1.91 l -1.94 5.26 l 2.92 0.96 l 2.43 2.87 l 2.92 -0.48 l -0.49 -3.35 l -2.43 -1.44 l 2.92 -3.35 l 5.35 0.48 l 2.43 3.35 l 1.94 2.39 l 1.46 2.39 L 124 63.2 h 4.37 l 0.97 0.96 l 2.92 -1.44 v 3.35 l 0.49 5.74 l 3.4 2.39 l 3.4 1.91 l 0.97 2.39 l -0.49 2.87 l -0.97 7.18 l 1.46 4.79 l 0.97 5.26 l 3.89 3.35 l 4.37 2.87 l 3.4 2.87 l -1.46 3.83 l -1.46 3.35 l -1.46 1.91 l -2.92 -3.35 l -4.86 4.31 l 0.49 5.27 l -3.89 -3.83 l -2.43 4.79 l -7.78 -1.91 l -5.35 1.44 l 1.94 4.79 l -2.43 2.87 l -1.46 2.39 l -4.37 -0.48 l -3.89 -3.35 h -2.43 v -5.26 l -3.89 -0.96 v -11.49 l -1.94 -2.39 l -1.94 -3.35 l -5.35 -1.44 l 0.49 -2.39 l -1.46 -0.96 v -3.83 l -0.49 -1.44 l 1.94 -2.39 l -1.46 2.39 l 2.92 -4.31 l 2.92 -1.44 v -8.14 l 1.46 -2.87 v -1.91 l -0.49 -6.7 l 1.46 -2.87 l -5.35 -5.74 L 98.24 55.54 L 98.24 55.54 L 98.24 55.54 Z",
  }]

关键修改说明

  • 通过d3.select(this.parentNode).select("path")获取当前文本所在分组对应的path元素
  • 基于path的边界框计算中心坐标:pathBBox.x + pathBBox.width / 2(X轴中心)、pathBBox.y + pathBBox.height / 2(Y轴中心)
  • 添加dominant-baseline: middle属性,确保文本在垂直方向完全居中(也可替换为dy: "0.35em"实现类似效果)

HTML代码(无需修改)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, shrink-to-fit=no"
    />
    <title>Choropleth Map</title>
    <style>
      .region:hover path {
        cursor: pointer;
        fill: #ccc;
      }
      .region:hover text {
        fill: #fff;
      }
    </style>
  </head>

  <body>
    <div class="map m-auto"></div>
    
    <!-- Script Section -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://d3js.org/d3.v7.min.js"></script>
  </body>
</html>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 09:15:35