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

D3.js choropleth地图tooltip鼠标悬停显示重复数据问题求助

问题描述

我正在完成freeCodeCamp的人口 choropleth 地图项目,这是一个展示美国郡级教育数据的地图。目前除自定义tooltip外所有功能均正常:鼠标悬停时,tooltip本应显示对应郡的名称、教育百分比等信息,但实际无论悬停哪个郡,都只显示同一个郡的信息。不过使用d3原生title tooltip时数据显示正常。我怀疑问题出在.data(dataset).enter()调用引用的数据集不匹配上,以下是代码片段,问题代码已在底部标注:

// Fetch the population/education dataset and feed it into a function that will merge it with the map dataset

fetch("https://cdn.freecodecamp.org/testable-projects-fcc/data/choropleth_map/for_user_education.json")
  .then(resp => resp.json())
  .then(popData => dataMerge(popData))

// Merge datasets -- this fetch is for the map geometry data

function dataMerge(popData) {
  fetch("https://cdn.freecodecamp.org/testable-projects-fcc/data/choropleth_map/counties.json")
    .then(resp => resp.json())
    .then(geoData => {

      const counties = geoData.objects.counties.geometries;
      popData.forEach(elem => {
        let fips = elem.fips;

        for(let i=0; i<counties.length; i++) {
          let id = counties[i].id;
          if(id === fips) {
            counties[i] = Object.assign(counties[i], elem);
            break;
          }
        }
      })
      return geoData;
    })
    .then(geoData => svgGenerate(geoData))
}

// Generate the svg
function svgGenerate(data) {

  let geojson = topojson.feature(data, data.objects.counties);
  let statesjson = topojson.feature(data, data.objects.states)
  // let projection = d3.geoAlbers();
  const path = d3.geoPath();

  const canvas = svg.append('g')
    // .attr('padding', padding)
  canvas.selectAll('path')
    .data(geojson.features)
    .enter()
    .append('path')
    .attr('d', path)
    .attr('class', 'county')

// since I'm using the geojson.features data, I must pull the county data explicitly.

    .attr('data-fips', (d,i)=>data.objects.counties.geometries[i].fips)
    .attr('data-education', (d,i)=>data.objects.counties.geometries[i].bachelorsOrHigher)
    .attr('fill', (d,i) => {
      const percentage = data.objects.counties.geometries[i].bachelorsOrHigher
      if(percentage<10) return 'rgb(255, 255, 255)'
      else if(percentage<20) return 'rgb(223, 254, 240)'
      else if(percentage<30) return 'rgb(187, 255, 226)'
      else if(percentage<40) return 'rgb(131, 255, 202)'
      else if(percentage<50) return 'rgb(0, 255, 146)'
      else if(percentage<60) return 'rgb(5, 215, 126)'
      else if(percentage<70) return 'rgb(1, 99, 57)'
      else return 'black'
    })
    .attr('transform', 'translate(160,30)')

// ****** HERE IS WHAT FAILS ******* 

    .on('mouseenter', (e,d) => {
      tooltip.style('visibility','visible')
        .style('left', event.pageX+'px')
        .style('bottom', h-event.pageY+300+'px')
        .html((d,i) => `${data.objects.counties.geometries[i].area_name}<br/>${data.objects.counties.geometries[i].bachelorsOrHigher}%`)
        .attr('data-education',(d,i)=>data.objects.counties.geometries[i].bachelorsOrHigher)
    })
    .on('mouseout', (e,d) => {
      tooltip.style('visibility','hidden')
    })
    .append('title')
    .text((d,i)=>data.objects.counties.geometries[i].fips)
问题原因及修复方案

问题核心是你在mouseenter事件回调中,错误地通过索引i访问原数据集data.objects.counties.geometries,但此处的i并非当前元素绑定数据的索引——D3事件回调的第二个参数d已经是当前元素绑定的完整数据对象,无需再通过原数据集索引查找。同时你混用了事件参数e和全局event,可能引发兼容性问题。

修复后的鼠标事件代码:

// ****** 修复后的鼠标事件代码 ******* 
.on('mouseenter', (e, d) => {
  tooltip.style('visibility','visible')
    .style('left', `${e.pageX}px`)
    .style('bottom', `${h - e.pageY + 300}px`)
    // 直接使用绑定的数据对象d,已包含合并后的教育数据
    .html(`${d.properties.area_name}<br/>${d.properties.bachelorsOrHigher}%`)
    .attr('data-education', d.properties.bachelorsOrHigher)
})
.on('mouseout', (e, d) => {
  tooltip.style('visibility','hidden')
})
.append('title')
// 直接使用绑定的数据
.text(d => d.properties.fips)

额外优化建议:
你之前设置data-fips、data-education和填充色时,也无需通过data.objects.counties.geometries[i]获取数据,直接使用绑定的d.properties即可,示例:

.attr('data-fips', d => d.properties.fips)
.attr('data-education', d => d.properties.bachelorsOrHigher)
.attr('fill', d => {
  const percentage = d.properties.bachelorsOrHigher
  // 原有颜色判断逻辑不变
})

这样能简化代码,彻底避免索引不匹配的问题。

内容的提问来源于stack exchange,提问作者u-lx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 14:42:44