【问题标题】:D3 + React hooks + useEffect() - prevent duplicate calls to d3.append() on mouseoutD3 + React hooks + useEffect() - 防止在 mouseout 时重复调用 d3.append()
【发布时间】:2025-07-08 18:30:02
【问题描述】:

大家好,这里有一个有趣的 D3、topojson 和 React 钩子。

我很想删除在鼠标悬停时创建并在鼠标悬停时持续存在的工具提示实例——使用 D3 的“React hooks”方式是使用useEffect(),svg 使用@987654326 呈现@,这很好用,但是每个连续的鼠标悬停事件都会触发一个新的 .append,它会添加另一个 div,因此工具提示内容只会不断堆积。

我认为使用 D3 的 selection.join() 可能是处理此问题的方法,但还没有工作。我确实考虑过在 React 中实现一个完全独立的工具提示组件,它与图表组件共享上下文,但首先我希望有人可能知道如何使用 D3 来做到这一点,因为它已经很接近了。

如何使用 D3 在鼠标移出时删除使用 .append 添加的“额外”div 内容?

Working example on Codesandbox here.

相关代码:

    svg
      .append("g")
      .selectAll("path")
      .data(features)
      .join(
        (enter) =>
          enter
            .append("path")
            .attr("fill", (d) =>
              colorScale(turnoutPerc.get(d.properties.GEOID))
            )
            .attr("d", path)
            .on("mouseover", (event, d) => {
              d3.select(event.currentTarget)
                .raise()
                .style("stroke", "#000")
                .style("stroke-width", 2)
                .style("cursor", "pointer");
              event.preventDefault();
              let [x, y] = d3.pointer(event);
              let ttPos = positionTooltip(x, y);
              let countyData = {
                showTT: true,
                FIPS: d.properties.GEOID,
                mouse: { x: ttPos.x, y: ttPos.y }
              };
              updateCurrentCounty(countyData);
              let countyProps = getCountyProps(countyData, data);
              container
                .style("visibility", "visible")
                .style("top", () => {
                  let ttHeight = 400;
                  if (y < ttHeight / 2) {
                    return event.pageY - 50 + "px";
                  } else if (y >= ttHeight / 2 && y < height - ttHeight / 2) {
                    return event.pageY - ttHeight / 2 + "px";
                  } else {
                    return event.pageY - ttHeight + "px";
                  }
                })
                .style("left", event.pageX + 60 + "px");

                // Header
                const header = container
                  .append("div")
                  .attr("class", "tt-header");
                const location = header
                  .append("div")
                  .attr("class", "tt-location");
                location
                  .append("h1")
                  .attr("class", "tt-county")
                  .html(countyProps.countyName);
                location
                  .append("h2")
                  .attr("class", "tt-state")
                  .html(countyProps.state);
            })
            .on("mouseout", (event, d) => {
              updateCurrentCounty({
                showTT: false,
                FIPS: null
              });
              d3.select(event.currentTarget)
                .style("stroke", "none")
                .style("cursor", "default");
            }),
        (update) => update.call((update) => update.transition(t)),
        (exit) => exit.remove()
      );

【问题讨论】:

标签: javascript reactjs d3.js react-hooks topojson


【解决方案1】:

添加:

container.selectAll(".tt-header").remove();

之前:

const header = container.append("div").attr("class", "tt-header");

这是fixed sandbox

【讨论】: