Saturday 6 March 2021

Understanding D3 data binding key accessor

I'm new to D3 and I'm doing a simple example trying to understand how the data binding works.

Basically I've an array of colors, a function for adda color and a function to remove a color from index. What is not working is the remove action. If I set 0 as index to remove, I see that D3 set the last element as element to remove. If I use the key accessor d => d, it works. I've a lot of question.

Here my code:

const data = {
  colors: ["Black", "White", "Brown"],
  addColor(color) {
    this.colors.push(color);
  },
  removeColorByIndex(index) {
    this.colors.splice(index, 1);
  }
};

const root = d3.select("#root");
const barsContainer = d3.select("#bars-container");
const addButton = d3.select("#add-button");
const removeButton = d3.select("#remove-button");

addButton.on("click", () => {
  const newColor = d3.select("#color-input").node().value;
  data.addColor(newColor);
  update();
});
removeButton.on("click", () => {
  const index = d3.select("#index-input").node().value;
  data.removeColorByIndex(index);
  update();
});

function update() {
  barsContainer
    .selectAll("div")
    .data(data.countries, (d, i) => {
      console.log({ i, d });
      return i;
    })
    .join(
      (enter) => {
        console.log("enter:", enter);
        return enter
          .append("div")
          .text((d) => d)
          .classed("bar", true)
          .classed("added", true);
      },

      (update) => {
        console.log("update:", update);
        return update.classed("update", true);
      },

      (exit) => {
        console.log("exit:", exit);
        return exit.classed("remove", true);
      }
    );

  console.log("divs", barsContainer.selectAll("div")["_groups"][0]);
}

update();
.bar {
  margin: 5px 0px 5px 0px;
  max-width: 200px;
  padding: 10px;
}
.added { background-color: lightgreen; }
.update { background-color: cornflowerblue; }
.remove { background-color: tomato; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="root">
  <div>
    <div>
      <input type="text" id="color-input" />
      <button id="add-button">Add color</button>
    </div>
    <div>
      <input type="text" id="index-input" />
      <button id="remove-button">Remove color by index</button>
    </div>
  </div>
  <div id="bars-container"></div>
</div>
  1. Initially all the bars are green. That's because they are all in the enter selection.
  2. If I add a color using the UI button, then new bars appears, it's green and the old ones became blue.
  3. Then, If I try to remove the bar with index 0, the last bars became red, not the first one, why?

If I imagine the behind logic, it would be:

Step # Action Enter selection Update selection Exit selection Join return selection (enter + update)
0 / [black, white, brown] [] [] black, white, brown
1 Add 'Yellow' color [yellow] black, white, brown] [] black, white, brown, yellow] ?
2 Remove element with index 0 [] [yellow] [black] [yellow]

But it seems is not right, what I'm wrong?

I knwow that doing data(myData, (d, i) => i) is the same of data(myData) and that means that D3 is matching data/DOM nodes by index. So why if I look at the __data__ property of the binded elements, they have __data__ = black/white/brown and not __data__ = 0/1/2?

I'm very confused and I didn't find anything that can help me..

I read D3 documentation and also this question.



from Understanding D3 data binding key accessor

No comments:

Post a Comment