D3 Choropleth地图悬停Tooltip不显示及位置异常求助
解决D3 Choropleth地图悬停Tooltip不显示及位置偏移问题
问题回顾
你尝试给D3世界地图添加悬停Tooltip时遇到了两个核心问题:
- 最初将Tooltip附加在SVG内部,导致Tooltip始终无法显示
- 移到SVG外部后,Tooltip位置要么固定在地图底部,要么偏移到页面顶部,无法跟随鼠标位置移动
问题根源分析
- SVG无法直接容纳HTML元素:SVG属于XML命名空间,
<div>这类HTML元素不能直接嵌入SVG中,所以你最初把tooltip附加到svg内的写法,浏览器根本不会渲染这个div。 - 定位逻辑存在漏洞:
- 未给Tooltip设置正确的
position属性,导致它默认以流式布局显示,只能出现在SVG下方 - 即使添加了
position:absolute,d3.mouse(this)获取的是相对于SVG元素的局部坐标,而绝对定位默认参考整个页面,所以需要结合SVG在页面中的偏移量计算,才能让Tooltip精准跟随鼠标
- 未给Tooltip设置正确的
分步解决办法
1. 正确创建Tooltip容器
将Tooltip创建在SVG所在的父容器中,而非SVG内部:
// 选择包含SVG的父容器,而非SVG本身 var tooltip = d3.select("#map-container") .append("div") .style("opacity", 0) .attr("class", "tooltip") .style("pointer-events", "none") // 防止Tooltip干扰地图的鼠标事件
2. 修复定位的CSS样式
给包含SVG的父容器设置相对定位,让Tooltip的绝对定位参考这个父容器,避免相对于整个页面偏移:
#map-container { position: relative; /* 关键:让子元素的绝对定位基于自身 */ } .tooltip { position: absolute; background-color: white; border: solid 2px; border-radius: 5px; padding: 5px; pointer-events: none; }
3. 修正鼠标位置计算
在mouseOver函数中,先获取SVG元素在页面中的偏移量,再结合鼠标相对于SVG的局部坐标,计算出Tooltip的正确位置:
let mouseOver = function(d) { // 保留原有高亮逻辑 d3.selectAll(".Country") .transition() .duration(200) .style("opacity", .5) d3.select(this) .transition() .duration(200) .style("opacity", 1) .style("stroke", "black") // 计算SVG在页面中的偏移量 const svgRect = svg.node().getBoundingClientRect(); tooltip.style("opacity", 1) .html("Population: <br/>" + d.total.toLocaleString()) // 用绑定的d.total而非d.pop,同时格式化数字 .style("left", (d3.mouse(this)[0] + svgRect.left + 10) + "px") .style("top", (d3.mouse(this)[1] + svgRect.top + 10) + "px") }
注意:你的代码中
d.pop是错误的,应该使用d.total——因为你在设置填充色时已经给每个国家数据绑定了d.total = data.get(d.id) || 0
完整修改后的代码
<head> <!-- Load d3.js and the geo projection plugin --> <script src="https://d3js.org/d3.v4.js"></script> <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> <script src="https://d3js.org/d3-geo-projection.v2.min.js"></script> <style> #map-container { position: relative; } .tooltip { position: absolute; background-color: white; border: solid 2px; border-radius: 5px; padding: 5px; pointer-events: none; } </style> <script> jQuery(document).ready(function() { // The svg var svg = d3.select("#my_dataviz"), width = +svg.attr("width"), height = +svg.attr("height"); // Map and projection var path = d3.geoPath(); var projection = d3.geoMercator() .scale(70) .center([0,20]) .translate([width / 2, height / 2]); // Data and color scale var data = d3.map(); var colorScale = d3.scaleThreshold() .domain([100000, 1000000, 10000000, 30000000, 100000000, 500000000]) .range(d3.schemeBlues[7]); // 创建Tooltip:放在SVG的父容器中 var tooltip = d3.select("#map-container") .append("div") .style("opacity", 0) .attr("class", "tooltip") // Load external data and boot d3.queue() .defer(d3.json, "https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson") .defer(d3.csv, "https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world_population.csv", function(d) { data.set(d.code, +d.pop); }) .await(ready); function ready(error, topo) { let mouseOver = function(d) { d3.selectAll(".Country") .transition() .duration(200) .style("opacity", .5) d3.select(this) .transition() .duration(200) .style("opacity", 1) .style("stroke", "black") // 计算SVG的页面偏移量,修正Tooltip位置 const svgRect = svg.node().getBoundingClientRect(); tooltip.style("opacity", 1) .html("Population: <br/>" + d.total.toLocaleString()) .style("left", (d3.mouse(this)[0] + svgRect.left + 10) + "px") .style("top", (d3.mouse(this)[1] + svgRect.top + 10) + "px") } let mouseLeave = function(d) { d3.selectAll(".Country") .transition() .duration(200) .style("opacity", .8) d3.select(this) .transition() .duration(200) .style("stroke", "transparent") tooltip.style("opacity", 0) } // Draw the map svg.append("g") .selectAll("path") .data(topo.features) .enter() .append("path") .attr("d", d3.geoPath().projection(projection)) .attr("fill", function (d) { d.total = data.get(d.id) || 0; return colorScale(d.total); }) .style("stroke", "transparent") .attr("class", "Country" ) .style("opacity", .8) .on("mouseover", mouseOver ) .on("mouseleave", mouseLeave ) } }) </script> </head> <body> <div> <h1>Graphs</h1> <!-- 给SVG添加父容器,用于放置Tooltip --> <div id="map-container"> <svg id="my_dataviz" width="400" height="300"></svg> </div> </div> </body>
额外优化点
- 将Tooltip样式移到CSS中,便于后续维护
- 使用
toLocaleString()格式化人口数字,提升可读性 - 添加
pointer-events: none避免Tooltip干扰地图的鼠标事件
内容的提问来源于stack exchange,提问作者Juan Ignacio Durante
相关产品推荐
相关产品推荐

