Aditya Barve
Aditya Barve

Reputation: 1481

Update, enter and exit selections in a single chain

I'm using D3 to create reactive visualizations. Whenever the data changes, I find myself using this pattern a lot:

function redraw(myData) {
    // if data points decreased, remove some rectangles
    d3.selectAll('rect')
        .data(myData)
        .exit()
        .remove();
    // if data points increased, add some rectangles
    d3.selectAll('rect')
        .data(myData)
        .enter()
        .append('rect');
    // assign attribute values to all rectangles
    d3.selectAll('rect')
        .data(myData)
        .attr('width', (d) => d.width)
        .attr('height', (d) => d.height)
        .attr('fill', 'blue');
}

Is there a way to shorten this operation to a single long chain of operations?

I've seen many D3 examples like this which are fine for a one-time draw. I couldn't get them to work for redraws.

Upvotes: 2

Views: 110

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

The join() method

You asked...

Is there a way to shorten this operation to a single long chain of operations? (emphasis mine)

Yes, there is: if you're using D3 v5.8 or above you can take advantage of the new method selection.join() to make a single chain with the update, enter and exit selections. By the way the example you linked (made by Bostock, D3 creator) is already using join(), so it's not "for a one-time draw" as you said.

Your whole function would be:

function redraw(myData) {
    d3.select(foo).selectAll('rect')
        .data(myData)
        .join('rect')
        .attr('width', (d) => d.width)
        .attr('height', (d) => d.height)
        .attr('fill', 'blue');
};

Here is a basic demo:

const svg = d3.select("svg");

d3.interval(function() {
  redraw(getData());
}, 1500);

redraw(getData());

function redraw(myData) {
  svg.selectAll('rect')
    .data(myData)
    .join('rect')
    .attr('width', (d) => d.width)
    .attr('height', (d) => d.height)
    .attr('x', (d) => d.x)
    .attr('y', (d) => d.y)
    .style('fill', 'lavender')
    .style('stroke', '#444');
};

function getData() {
  return d3.range(~~(Math.random() * 20)).map(function(d) {
    return {
      x: Math.random() * 250,
      y: Math.random() * 100,
      width: 10 + Math.random() * 40,
      height: 10 + Math.random() * 40,
    }
  });
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>


PS: your current function has some problems. First, d3.selectAll is not the same of selection.selectAll (pay attention to d3.select(foo) in my function). Second, you don't have the correct update, enter and exit selections in that function. Have a look here for the idiomatic pattern.

Upvotes: 3

Related Questions