D3条形图+ React挂钩-exit().remove()无法正常工作 [英] D3 Bar Chart + React Hooks - exit().remove() not working

查看:49
本文介绍了D3条形图+ React挂钩-exit().remove()无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using react and d3, trying to create a simple bar chart that updates the chart when data is refreshed. The chart is updating when the data changes, but it seems to be layering on top of the old chart. I think the issue is with the d3 exit().remove() function.

据我所知,d3的exit方法应该返回要删除的项目数组,但是当我在控制台日志中看到一个未定义"数组.我非常感谢您的帮助!

As I understand it d3's exit method should return an array of items to be removed, however when I console log it I see an array of "undefined"s. I'm super grateful for any help!

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

这是代码段:

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;

推荐答案

每次更新图表时,您都运行以下命令:

Everytime you update the chart you run this:

 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 中没有可供选择的 rect .因此,当我们使用 update.enter()时,将为数据数组中的每个项目创建一个DOM元素.使用enter将为数据数组中尚未具有对应元素的每个项目创建一个元素.

update is now an empty selection, there are no rects to select in the newly created g. So when we use update.enter(), a DOM element is created for every item in the data array. Using enter will create an element for every item in the data array that doesn't have a corresponding element already.

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

update.exit() will be empty, because there are no elements selected in update, so nothing will be removed. Previously created bars are not touched, you aren't selecting them.

如果我们只是为了删除 .append("g")而更改您的代码,它将使我们更接近工作(

If we change your code just to remove the .append("g"), it gets us closer to working (eg). The bars were colored white, so they were not visible, I've changed the fill color so the update selection is visible

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

If we remove .append("g") we have some other problems on update now:

  • 您不会退出文本(因为您没有通过 .selectAll()仅选择 rect 元素选择 text )和
  • 您要将文本和矩形合并为一个选择,如果您想在更新时对它们进行不同的定位和着色,则会遇到一些问题.

第二个问题可以进一步解释:

The second problem could be explained a bit more:

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.

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

These two issues can be resolved by using the enter/update/exit cycle correctly.

要注意的一件事是,D3的enter update退出模式并非旨在使用同一条语句多次输入元素,您要使用相同的enter语句

One thing to note is that D3's enter update exit pattern isn't designed to enter elements more than once with the same statement, you're entering text and rects with the same enter statement, see here.

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

Therefore, one option is to use two selections, one for text and one for rects:

  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();

此处为沙盒形式.

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

The other option is to use a parent g to hold both rect and text, this could be done many ways, but if you don't need a transition between values or the number of bars, would probably be most simple like:

  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将父基准传递给子.注意:如果我们嵌套了数据(每个数据数组项有多个条或文本),则需要嵌套进入/退出/更新周期.

A bit further explanation: selection.select() selects the first matching element for each element in the selection - so we can select the sole rectangle in each parent g (that we add when entering the parent) with bars.select("rect") above. D3 passes the parent datum to the child when appending in the above. Note: If we had nested data (multiple bars or texts per data array item) we'd need to have nested enter/exit/update cycles.

这篇关于D3条形图+ React挂钩-exit().remove()无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆