简体   繁体   中英

Understanding D3 enter, update and exit selections with nested content

When using D3.js, I find I often want to create elements bound to my data but within divs and wrappers for style/layout reasons. What I'm struggling to understand is how nested content and wrappers works with D3's enter, update, and exit pattern.

Basically, I typically use arrays of JSON with a few attributes for each object I want to display.

Say I have an array of people, with each person's name and age, that looks something like this:

var array = [
    {
        name: "John",
        age: 31
    },
    {
        name: "Mark",
        age: 29
    },
]

I want to list out this data with D3 in a wrapper div:

<div id="wrapper"></div>

and I would like to create a for each person, and then append a paragraph containing that person's name and a paragraph containing that person's age within that containing . So, the desired end result would be:

<div id="wrapper">
    <div class="person">
        <p>John</p>
        <p>31</p>
    </div>
    <div class="person">
        <p>Mark</p>
        <p>29</p>
    </div>
</div>

What I've been trying to do is select all divs within the wrapper div, enter/update based on the data array, and then append the paragraphs with the desired info:

var selection = d3.select("#wrapper")
    .selectAll("div")
    .data(array);

selection.enter()
    .append("div")
    .classed("person", true)
    .merge(selection);

selection.append("p")
    .html(d.name);

selection.append("p")
    .html(d.age);

selection.exit().remove();

I run into a few issues with this:

  1. No paragraphs are appended the first time this runs

  2. If I have an event that adds another person to the array and then runs this again, I see only the two people that were in the original array - John and Mark.

  3. If I add another person to the array, not only does issue #2 persist, now it appends paragraphs to those person's wrapper divs so there is duplicate content within the divs for each person.

Frankly, I'm a little unsure of how the enter/update/exit selection is supposed to work when I want to place D3 elements within wrappers and nest content and add multiple children at the same level and then update the whole group when the array updates. Any clarity on this would be greatly appreciated - thank you.

You have to reassign the selection:

selection = selection.enter()
  .append("div")
  .classed("person", true)
  .merge(selection);

Also, you cannot use the first argument like that, you have to pass an function to the methods:

selection.append("p")
  .html(d=>d.name);

selection.append("p")
  .html(d=>d.age);

Here is your code with those changes:

 var array = [{ name: "John", age: 31 }, { name: "Mark", age: 29 }, ] var selection = d3.select("#wrapper") .selectAll("div") .data(array); selection = selection.enter() .append("div") .classed("person", true) .merge(selection); selection.append("p") .html(d=>d.name); selection.append("p") .html(d=>d.age); selection.exit().remove(); 
 <script src="https://d3js.org/d3.v5.min.js"></script> <div id="wrapper"></div> 

Finally, given the information you shared in your comment , the first choice to fix your code seems to be creating a nested enter/update/exit selection.

However, on a second thought, because your objects don't have nested arrays, a way simpler solution is appending the <p> elements in the enter selection and updating them in the update selection.

Here is a snippet demonstrating this, with 3 different data arrays:

 var array = [{ name: "John", age: 31 }, { name: "Mark", age: 29 }, ] function display() { var selection = d3.select("#wrapper") .selectAll("div") .data(array); var selectionEnter = selection.enter() .append("div") .classed("person", true); selectionEnter.append("p") .classed("name", true) .html(d => d.name); selectionEnter.append("p") .classed("age", true) .html(d => d.age); selection.exit().remove(); selection.select(".name") .html(d => d.name); selection.select(".age") .html(d => d.age); } display(); setTimeout(function() { array = [{ name: "John", age: 31 }, { name: "Mark", age: 29 }, { name: "Andrew", age: 24, } ]; display(); }, 2000) setTimeout(function() { array = [{ name: "John", age: 21 }, { name: "Bob", age: 49 } ]; display(); }, 4000) 
 <script src="https://d3js.org/d3.v5.min.js"></script> <div id="wrapper"></div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM