【问题标题】:D3 Bar Chart + React Hooks - exit().remove() not workingD3 条形图 + React Hooks - exit().remove() 不起作用
【发布时间】:2020-08-02 11:40:55
【问题描述】:

我正在使用 react 和 d3,试图创建一个简单的条形图,在数据刷新时更新图表。当数据更改时,图表正在更新,但它似乎在旧图表之上分层。我认为问题出在 d3 exit().remove() 函数上。

据我了解,d3 的退出方法应该返回一个要删除的项目数组,但是当我控制台记录它时,我看到一个“未定义”数组。我非常感谢任何帮助!

这里是代码框: https://codesandbox.io/s/gifted-field-n66hw?file=/src/Barchart.js

这里是sn-p的代码:

import React, { useEffect, useRef } from "react";
import * as d3 from "d3";

const BarChart = props => {
  const { randomData, width, height, padding } = props;
  const ref = useRef(null);

  function colorGradient(v) {
    return "rgb(0, " + v * 5 + ", 0";
  }

  //insert & remove elements using D3
  useEffect(() => {
    if (randomData.length > 0 && ref.current) {
      const group = d3.select(ref.current);

      // the data operator binds data items with DOM elements
      // the resulting selection contains the enter and exit subselections
      const update = group
        .append("g")
        .selectAll("rect")
        .data(randomData);

      let bars = update
        .enter() // create new dom elements for added data items
        .append("rect")
        .merge(update)
        .attr("x", (d, i) => i * (width / randomData.length))
        .attr("y", d => height - d * 5)
        .attr("width", width / randomData.length - padding)
        .attr("height", d => d * 5)
        .attr("fill", d => colorGradient(d));

      let labels = update
        .enter()
        .append("text")
        .text(d => d)
        .attr("text-anchor", "middle")
        .attr(
          "x",
          (d, i) =>
            i * (width / randomData.length) +
            (width / randomData.length - padding) / 2
        )
        .attr("y", d => height - d * 5 + 12)
        .style("font-family", "sans-serif")
        .style("font-size", 12)
        .style("fill", "#ffffff");

      update.exit().remove();
    }
  }, [randomData, height, padding, width]);

  return (
    <svg width={width} height={height}>
      <g ref={ref} />
    </svg>
  );
};

export default BarChart;

【问题讨论】:

  • 发生这种行为是因为您每次进行更新选择时都附加一个g:它将为空且没有要退出的矩形,并且输入选择将导致输入数据数组中的所有项目.如果 group 已经只是一个 g 没有别的,你可以跳过附加一个额外的。否则,您可以将 g 仅附加一次并在更新函数中使用它。
  • 你能解释一下在别处附加 g 是什么意思吗?我尝试将它移到 useEffect 之外,现在它适用于第一次渲染,但如果我更新数据,图表就会消失。此外,我认为该组只是一个 g(它有条形和文本,但没有其他内容)但我不完全确定你的意思。抱歉,我是 D3 的新手。我更新了代码框:codesandbox.io/s/gifted-field-n66hw?file=/src/Barchart.js

标签: reactjs d3.js react-hooks


【解决方案1】:

每次更新图表时都会运行:

 const update = group
    .append("g")             // create a new g
    .selectAll("rect")       // select all the rectangles in that g (which are none)
    .data(randomData);      

update 现在是一个空选择,在新创建的g 中没有可供选择的rects。因此,当我们使用update.enter() 时,会为数据数组中的每个项目创建一个 DOM 元素。使用 enter 将为数据数组中还没有对应元素的每个项目创建一个元素。

update.exit() 将为空,因为update 中没有选择任何元素,所以不会删除任何内容。之前创建的条没有被触摸,您没有选择它们。

如果我们只是为了删除 .append("g") 而更改您的代码,这会让我们更接近工作 (eg)。 这些条是白色的,所以它们不可见,我更改了填充颜色,所以更新选择可见


如果我们删除.append("g"),我们现在更新时还有一些其他问题:

  • 您没有退出文本(因为您没有选择text 和.selectAll(),只有rect 元素),并且
  • 您将文本和矩形合并到一个选择中,如果您想在更新时以不同的方式定位和着色它们,这会有点问题。

第二个问题可以再解释一下:

update.enter().append("text") // returns a selection of newly created text elements
   .merge(update)             // merges the selection of newly created text with existing rectangles
   .attr("fill", ....         // affects both text and rects.

这两个问题可以通过正确使用进入/更新/退出循环来解决。

需要注意的一点是,D3 的输入更新退出模式并非旨在使用相同的语句多次输入元素,而是使用相同的输入语句see here 输入文本和矩形。

因此,一种选择是使用两种选择,一种用于文本,一种用于矩形:

  const updateRect = group
    .selectAll("rect")
    .data(randomData);

  let bars = updateRect
    .enter() // create new dom elements for added data items
    .append("rect")
    .merge(updateRect)
    .attr("x", (d, i) => i * (width / randomData.length))
    .attr("y", d => height - d * 5)
    .attr("width", width / randomData.length - padding)
    .attr("height", d => d * 5)
    .attr("fill", d => colorGradient(d));

  const updateText = group
    .selectAll("text")
    .data(randomData);

  let labels = updateText
    .enter()
    .append("text")
    .merge(updateText)
    .text(d => d)
    .attr("text-anchor", "middle")
    .attr(
      "x",
      (d, i) =>
        i * (width / randomData.length) +
        (width / randomData.length - padding) / 2
    )
    .attr("y", d => height - d * 5 + 12)
    .style("font-family", "sans-serif")
    .style("font-size", 12)
    .style("fill", "#fff");

  updateRect.exit().remove();
  updateText.exit().remove();

这里是sandbox 表格。

另一种选择是使用父 g 来保存矩形和文本,这可以通过多种方式完成,但如果您不需要在值或条数之间进行转换,可能是最简单的喜欢:

  const update = group
    .selectAll("g")
    .data(randomData);

  // add a g for every extra datum
  const enter = update.enter().append("g")
    // give them a rect and text element:
    enter.append("rect");
    enter.append("text");

  // merge update and enter:
  const bars = update.merge(enter);

  // modify the rects
  bars.select("rect")
    .attr("x", (d, i) => i * (width / randomData.length))
    .attr("y", d => height - d * 5)
    .attr("width", width / randomData.length - padding)
    .attr("height", d => d * 5)
    .attr("fill", d => { return colorGradient(d)});

  // modify the texts:
  bars.select("text")
    .text(d => d)
    .attr("text-anchor", "middle")
    .attr(
      "x",
      (d, i) =>
        i * (width / randomData.length) +
        (width / randomData.length - padding) / 2
    )
    .attr("y", d => height - d * 5 + 12)
    .style("font-family", "sans-serif")
    .style("font-size", 12)
    .style("fill", "#ffffff");

这是sandox 形式的内容。

进一步解释:selection.select() 为选择中的 each 元素选择第一个匹配元素 - 因此我们可以选择每个父 g 中的唯一矩形(我们在输入父时添加)上面有bars.select("rect")。 D3 在上面附加时将父数据传递给子数据。 注意:如果我们有嵌套数据(每个数据数组项有多个条形或文本),我们需要有嵌套的进入/退出/更新周期。

【讨论】:

  • 谢谢谢谢谢谢!这是一个很好的解释(比我在网上的任何文章中找到的要好得多)而且现在更有意义了!!
猜你喜欢
  • 1970-01-01
  • 2016-09-22
  • 1970-01-01
  • 1970-01-01
  • 2014-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多