score:2

Accepted answer

The main problem in your non-working function (loadData) is that you're using selectAll. Unlike select, selectAll doesn't propagate the data.

Have a look at this table I made summarising the differences between them:

Method select() selectAll()
Selection selects the first element that matches the selector string selects all elements that match the selector string
Grouping Does not affect grouping Affects grouping
Data propagation Propagates data Doesn't propagate data

As you can see, the main information for you is this:

  • select: propagates the data.
  • selectAll: doesn't propagate the data.

Given that your snippet has just one child (circle and text) per group, the simplest solution is just changing selectAll to select. However, if you have more than one child per parent, the best (and idiomatic) solution is creating a nested enter-update-exit selection, which is a bit more laborious.

Besides that, you're missing the key function:

const container = svg.selectAll(".container")
    .data(data, function(d) {
        return d.name;
    });

Here is your code with those changes:

const DATA = [{
  name: "data1",
  left: 70,
  top: 70,
  radius: 20
}, {
  name: "data2",
  left: 200,
  top: 100,
  radius: 50
}];
const svg = d3.select("#svg");

// Not working as expected
function loadData(data) {
  console.log(data);
  const container = svg.selectAll(".container")
    .data(data, function(d) {
      return d.name;
    });
  container.exit().remove();
  const enter = container.enter().append("g")
    .attr("class", "container");
  enter.append("circle");
  enter.append("text");

  container.merge(enter)
    .attr("transform", entry => `translate(0,${entry.top})`)
  svg.select(".container circle")
    .attr("r", entry => entry.radius)
    .attr("cx", entry => entry.left);
  svg.select(".container text")
    .text(entry => entry.name);
}

// Working, but not desired
function expected(data) {
  console.log(data);
  const container = svg.selectAll(".container")
    .data(data);
  container.exit().remove();
  const enter = container.enter().append("g")
    .attr("class", "container");
  enter.append("circle");
  enter.append("text");

  container.merge(enter)
    .attr("transform", entry => `translate(0,${entry.top})`)
  svg.selectAll(".container circle")
    .data(data)
    .attr("r", entry => entry.radius)
    .attr("cx", entry => entry.left);
  svg.selectAll(".container text")
    .data(data)
    .text(entry => entry.name);
}
let i = 0;
loadData(DATA);

function updateData() {
  i = (i + 1) % 2;
  loadData(DATA.slice(i, i + 1));
}

function expectedUpdate() {
  i = (i + 1) % 2;
  expected(DATA.slice(i, i + 1));
}
button {
  display: inline-block;
}

svg {
  width: 300px;
  height: 300px;
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button onclick="updateData();">Update</button>
<button onclick="expectedUpdate();">Expected</button>
<svg id="svg"></svg>


Related Query