Search notes:

D3.js: enter merge exit

This page tries to demonstrate how the D3.js trilogy enter, merge and exit can be used to create dynamic visualizations with D3.js.

goal.svg

goal.svg is an SVG document that shows the important SVG elements attributes we let D3.js create, update and delete.
As can be seen, we draw a bar chart with <rect> elements and label them with <text> elements:
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">

  <style>
     @import url("https://fonts.googleapis.com/css?family=Open+Sans:400");
  </style>

  <rect height="40" width="500" y= "10" x="10" fill="#cd4ae7"/>
  <rect height="40" width="350" y= "70" x="10" fill="#cd4ae7"/>
  <rect height="40" width="400" y="130" x="10" fill="#cd4ae7"/>

  <text font-family="Open Sans" font-size="30" y= "40" x="20"  fill="#56ffbe">Text one</text>
  <text font-family="Open Sans" font-size="30" y="100" x="20"  fill="#56ffbe">Text two</text>
  <text font-family="Open Sans" font-size="30" y="160" x="20"  fill="#56ffbe">Text three</text>

</svg>
Github repository about-d3.js, path: /enter-merge-exit/goal.svg

HTML/JavaScript source code

The JavaScript source code defines an variable named dataSets which is an array of three data sets, each of which stores its data points in an array (i. e. dataSets is an array of arrays).
Each data point is an object which has a val whose value controls the length of the individual bars when drawn, and a name whose value will be used to label the bars.
The HTML source code also defines a button which, when clicked, invokes the function showNextDataset(): <button onclick="showNextDataset()"…>.
The bar chart will be rendered in the <svg> element which is provided in the HTML document's body.
When the button is clicked and showNextDataset() is invoked, the function will display the next data in dataSets.
First
allRects.enter() then
Because after the second click on the mouse button, some rects will then already exist, these need to be updated (width attribute) rather than created.
However, the the newly created ones have a different value for the width attribute.
The «merge» step sets the width for both, the newly created and also the updated rect.
The rects that need updating are determined with newRects.merge(allRects).
Finally, when the new data set has less datapoints than the currently shown one, some rects need to be deleted.
The set of rects to be deleted is determined with allRects.exit().
The rects that need to be updated are determined with newRects.merge(allRects)

go.html

<!DOCTYPE html>
<html>
<head>
  <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
  <title>Enter merge exit</title>

 <link rel ="stylesheet"
       href="https://fonts.googleapis.com/css?family=Open+Sans:400">

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
<script>


let dataSets = [

   [ {name: 'foo', val: 173}, {name: 'bar', val: 280}, {name: 'baz'  , val: 215} ],
   [ {name: 'A'  , val: 206}, {name: 'B'  , val: 305}, {name: 'C'    , val: 288}, {name: 'D'   , val: 184}, {name: 'E', val: 125} ],
   [ {name: 'one', val: 313}, {name: 'two', val: 127}, {name: 'three', val: 256}, {name: 'four', val: 284},]

];
let nextDataset = 0;

function showNextDataset() {

   let allRects = d3.select('svg')
          .selectAll('rect')
          .data(dataSets[nextDataset])
          ;

//
// Create new rects
//
   let newRects = allRects.enter()
          .append('rect')
          .attr('x'     , '10'     )
          .attr('height', '40'     )
          .attr('fill'  , '#cd4ae7')
          .attr('y'     , (d, i) => {console.log("i = " + i); return 10 + i*60;})
          ;

//
// Determine rects that need to be updated (new rects + existing rects)
//
   let rectsToUpdate = newRects.merge(allRects);

   rectsToUpdate
          .transition()
          .attr('width', (d, i) => {console.log("d.val = " + d.val); return d.val;})
          ;

//
// If number of rects is larger than data points,
// some need to be deleted
//
   let rectsToDelete = allRects.exit();
   rectsToDelete
          .transition()
          .attr('width' , 0)
          .attr('height', 0)
          .remove()
          ;

// --- Same thing for text ---

   let allTexts = d3.select('svg')
          .selectAll('text')
          .data(dataSets[nextDataset])
          ;


   let newTexts = allTexts.enter()
          .append('text')
          .attr('font-family', 'Open Sans')
          .attr('font-size'  , '0'        )
          .attr('height'     , '40'       )
          .attr('x'          , '20'       )
          .attr('fill'       , '#56ffbe'  )
          .attr('y'          , (d, i) => {console.log("i = " + i); return 40 + i*60;})
          ;

   let textsToUpdate = newTexts.merge(allTexts);

   textsToUpdate
          .transition()
          .attr('font-size', '30')
          .text(d => d.name      )
          ;

   let textsToDelete = allTexts.exit();
  
   textsToDelete
          .transition()
          .attr('font-size', 0)
          .remove()
          ;

   nextDataset = (++nextDataset) % dataSets.length;
}

</script>

</head>
<body onload="showNextDataset()">


  <svg width=400 height=300 style="background-color:#f3f3f3">

  </svg>
  <p>
  <button onclick="showNextDataset()">Show next dataset</button>

</body>
</html>
Github repository about-d3.js, path: /enter-merge-exit/go.html

See also

D3.js

Index