block by harrystevens de1069536e00256d7aa82299fac0f3d5

Bar Update Pattern

Full Screen

Use D3’s general update pattern to animate bar chart transitions. Axis styling from this block.

This data takes the format:

name value
String Number

index.html

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
      font-family: "Helvetica Neue", sans-serif;
    }
    .amount {
      text-anchor: middle;
    }
    .x.axis .domain {
      display: none;
    }
  </style>
</head>
<body>
  <script src="https://d3js.org/d3.v6.min.js"></script>
  <script>
    const xVar = "name";
    const yVar = "value";
    const alpha = "abcdefg".split("");

    const margin = { left: 10, right: 10, top: 20, bottom: 20 };
    const width = innerWidth - margin.left - margin.right;
    const height = innerHeight - margin.top - margin.bottom;
    
    const svg = d3.select("body").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom);

    const g = svg.append("g")
        .attr("transform", `translate(${[ margin.left, margin.top ]})`);

    const x = d3.scaleBand()
        .range([0, width])
        .domain(alpha)
        .padding(.2);

    const y = d3.scaleLinear()
        .range([height, 0])
        .domain([0, 10]);

    const xAxis = d3.axisBottom(x);

    const yAxis = g => {
      g.call(
        d3.axisRight(y)
            .tickFormat((d, i, e) => `${d}${i == e.length - 1 ? ` ${yVar}s` : ""}`)
            .tickSize(width)
      );
      g.select(".domain").remove();
      g.selectAll(".tick:not(:first-of-type) line")
          .attr("stroke", "#777")
          .attr("stroke-dasharray", "2,2");
      g.selectAll(".tick text")
          .attr("x", 4)
          .attr("dy", -4);
    }

    g.append("g")
        .attr("class", "x axis")
        .attr("transform", `translate(0, ${height})`)
        .call(xAxis);

    g.append("g")
        .attr("class", "y axis")
        .call(yAxis);

    const color = d3.scaleOrdinal(d3.schemeSet2);

    redraw(randomData());

    d3.interval(() => {
      redraw(randomData());
    }, 2000);

    function redraw(data){
      // join
      const bar = g.selectAll(".bar")
          .data(data, d => d[xVar]);

      const amount = g.selectAll(".amount")
          .data(data, d => d[xVar]);

      // update
      bar
        .transition()
          .attr("y", d => y(d[yVar]))
          .attr("height", d => height - y(d[yVar]));

      amount
        .transition()
          .attr("y", d => y(d[yVar]))
          .text(d => d[yVar]);

      // enter
      bar.enter().append("rect")
          .attr("class", "bar")
          .attr("x", d => x(d[xVar]))
          .attr("y", d => y(d[yVar]))
          .attr("width", x.bandwidth())
          .attr("height", d => height - y(d[yVar]))
          .attr("fill", d => color(d[xVar]));

      amount.enter().append("text")
          .attr("class", "amount")
          .attr("x", d => x(d[xVar]) + x.bandwidth() / 2)
          .attr("y", d => y(d[yVar]))
          .attr("dy", 16)
          .attr("fill", d => d3.color(color(d[xVar])).darker(3))
          .text(d => d[yVar]);
    }

    function randomData(){
      return alpha.map(name => {
        return {
          name,
          value: Math.round(d3.randomUniform(1, 10)())
        }
      });
    }

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