block by curran 65bb295d224a09f83fad6f9942ee4639

Basic General Update Pattern

Full Screen

This example shows the General Update Pattern, which is required to create things with D3.js that update with dynamic data.

Part of the video course: D3.js in Motion.

See also:

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Shopping App Tester</title>
    <script src="https://d3js.org/d3.v4.min.js"></script>
  </head>
  <body>
    <svg width="960" height="500"></svg>
    <script src="visualization.js"></script>
    <script>

      const props = {
        width: 300,
        height: 500,
        data: [
          { name: 'banana', price: 1 },
          { name: 'eggs', price: 2 },
          { name: 'peanut butter', price: 4 }
        ]
      };

      const svg = d3.select('svg');
      const selection = svg.append('g')
        .attr('transform', `translate(${+svg.attr('width') / 2 - props.width / 2})`);

      visualization(selection, props);

      // Test the problem: bars don't update when price changes.
      setTimeout(() => {
        props.data[1].price = 3;
        visualization(selection, props);
      }, 1000);

      // Test the problem: bars don't update correctly after an item is removed.
      setTimeout(() => {
        props.data = [
          { name: 'banana', price: 1 },
          { name: 'eggs', price: 2 },
        ];
        visualization(selection, props);
      }, 2000);

    </script>
  </body>
</html>

visualization.js

function visualization(selection, props){
  const { width, height, data } = props;

  const yValue = d => d.name;
  const xValue = d => d.price;

  const yScale = d3.scaleBand()
    .domain(data.map(yValue))
    .range([height, 0]);

  const xScale = d3.scaleLinear()
    .domain([0, d3.max(data, xValue)])
    .range([0, width]);

  // Create the data join.
  const bars = selection.selectAll('rect').data(data);

  // Handle the ENTER case - when elements first get added.
  const barsEnter = bars
    .enter().append('rect')
       .attr('x', 0); // The x values won't change, so we can set it on ENTER.

  // Set the bar position and dimensions on ENTER and UPDATE.
  barsEnter
    .merge(bars)
      .attr('y', d => yScale(yValue(d)))
      .attr('width', d => xScale(xValue(d)))
      .attr('height', yScale.bandwidth());

  // Remove bars for which there are no longer any corresponding data elements.
  bars.exit().remove();
}