D3 + React hooks + useEffect() - 防止在 mouseout 时重复调用 d3.append()

Posted

技术标签:

【中文标题】D3 + React hooks + useEffect() - 防止在 mouseout 时重复调用 d3.append()【英文标题】:D3 + React hooks + useEffect() - prevent duplicate calls to d3.append() on mouseout 【发布时间】:2021-08-17 18:14:07 【问题描述】:

大家好,这里有一个有趣的 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()
      );

【问题讨论】:

注意:在工具提示 JSX 中添加空标题并按此处所示设置它们的内容是一种方法...换句话说,将 JSX 元素“硬编码”到目标 JSX . codesandbox.io/s/objective-elgamal-lno7k?file=/src/MapChart.js 【参考方案1】:

添加:

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

之前:

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

这是fixed sandbox。

【讨论】:

以上是关于D3 + React hooks + useEffect() - 防止在 mouseout 时重复调用 d3.append()的主要内容,如果未能解决你的问题,请参考以下文章

自定义 Hook

自定义 Hook

无法获取 API URL 以重新路由到正确的地址:React Hooks

react hook 新特性汇总

React Hook - Hook规则

react hook介绍