uzay95
uzay95

Reputation: 16632

What is enter and exit in d3.js?

I tried to understand with this example but couldn't understand exit function. Actually before exit I got what enter is but when I implemented the exit function right after enter function not appended li items created even if I bound different data to the exit function.

Please help me to understand the missions of enter a

enter image description here

<ul id="#example">
  <li></li>
  <li></li>
  <li></li>
</ul>
<script>

var updateSelection;
var veri;

window.onload = function() {
  // Bağlanacak veri sayısı 5
  veri = [5, 10, 15, 20, 25];


  updateSelection = d3
    .select("ul") 
    .selectAll("li") 
    .data(veri)
    .enter() 
    .append("li")
    .text(d => d)
    .style("color","blue");

  console.log(updateSelection);

  updateSelection = d3
    .select("ul") 
    .selectAll("li")
    .data([1,2]) 
    .exit() 
    .append("li")
    .text(d => d)
    .style("color","red");

  console.log(updateSelection);

}
</script>

Upvotes: 3

Views: 2159

Answers (2)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

There are good tutorials talking about D3 selections, this quite old one being a good read: https://bost.ocks.org/mike/join/

Regarding your question, it's not clear why you're appending elements to an exit selection. However, it's worth mentioning that one can do whatever they want with the exit selection, including appending elements.

That being said, in short, this is what's happening in your code:

  1. You are using this data...

    [5, 10, 15, 20, 25]
    

    ... to the enter selection. As you can see, there are 5 elements in the data array, so the enter selection has five elements. Have a look at the console:

var veri = [5, 10, 15, 20, 25];

var updateSelection = d3
  .select("ul")
  .selectAll("li")
  .data(veri)
  .enter()
  .append("li")
  .text(d => d)
  .style("color", "blue");

console.log("size of enter selection: " + updateSelection.size());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<ul></ul>

  1. Then, you select everything again and bind the selection to this data array:

    [1, 2]
    

As you can see, you had 5 elements in the previous data array and 2 elements in the new data array (because you don't have a key function here, you're binding the elements by index). So, since you have 3 data elements without a corresponding DOM element, your exit selection size is 3. Compare the two arrays:

[5, 10, 15, 20, 25]
[1, 2]   |   |   |
         |   |   |
         V   V   V
//these elements belong to the exit selection

Have a look at the console again:

var veri = [5, 10, 15, 20, 25];

var updateSelection = d3
  .select("ul")
  .selectAll("li")
  .data(veri)
  .enter()
  .append("li")
  .text(d => d)
  .style("color", "blue");

updateSelection = d3
  .select("ul")
  .selectAll("li")
  .data([1, 2])
  .exit()
  .append("li")
  .text(d => d)
  .style("color", "red");

console.log("size of exit selection: " + updateSelection.size());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<ul></ul>

Finally, the visual we have is exactly the expected one: we have 5 blue elements, corresponding to the enter selection, and 3 red elements, corresponding to the exit selection (which, for whatever reason, you appended instead or removed).

Upvotes: 0

Ruben Helsloot
Ruben Helsloot

Reputation: 13129

In d3JS v4 and 5, if you create a selection and apply the .data() function, that changes the nature of the selection object you have. If you store that in a variable, you can then subsequently call the .enter() and .exit() functions on that object to select the items that should be added and the items that should be deleted, respectively. You can then merge the new items with the pre-existing ones using the .merge() function, which, as the name applies, merges those into a single selection, kind of like the result of an entirely new d3.select() call would be.

With regards to your code, you should not have to make the selection multiple times, and I think calling .data() multiple times can do more harm than good. I'll add some code in which I draw a legend to a chart. I hope it helps illustrate my point.

// I select all legend values and apply a new set of data to it
let items = this.legend
    .selectAll(".item")
    .data(values);
// I remove the values that can be removed
items.exit().remove();
// I specifically select the new items and draw a new <g> element for each
const enterItems = items.enter()
    .append("g")
    .classed("item", true);

// Each of those <g> elements gets a <circle> and a <text> node
enterItems.datum(d => d)
    .append("circle")
    .attr("r", 3);
enterItems.datum(d => d)
    .append("text")
    .attr("dx", -7)
    .attr("dy", 3);

// Finally I merge the new <g> elements with the ones that I left over
// from before I called `.data()` - so not the ones I deleted
// It's good practice not to apply any code to this merged selection
// that involves drawing new items, but to only respond to changes in data,
// i.e. calculating and setting dynamic variables.
items = enterItems
    .merge(items)
    // Apply to all because this redraw
    // might have been caused by a resize event
    .attr("transform", (_, i) =>
      `translate(${this.$element[0].clientWidth - this.margin.right},
                      ${this.margin.top + i * 10 + 5})`);

// This also applies to all child elements. I may change the color and the text,
// but I do not recalculate the fixed size of the circles or the positioning of the
// text - because I know those would never change
items.select("circle")
    .style("fill", d => this.colors(d))
    .attr("class", d => `legend item-${d}`);
items.select("text")
    .text(d => d);

Upvotes: 2

Related Questions