block by HarryStevens 8e11384213b3dd54061fc4fc5add48aa

Wartplot Update Pattern

Full Screen

What do you call a joyplot that represents data as half circles rather than as an area path? How about a wartplot?!

This wartplot uses D3’s general update pattern to animate transitions with regularly changing data.

index.html

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
      font-family: "Helvetica Neue", sans-serif;
    }
    .circle {
      stroke: #000;
      opacity: .8;
    }
    .circle:hover {
      opacity: 1;
      stroke-width: 1.5px;
    }
    .baseline {
      stroke: #000;
      shape-rendering: crispEdges;
    }
    .label {
      text-anchor: end;
      font-size: .8em;
    }
    .axis .domain {
      display: none;
    }
  </style>
</head>
<body>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://unpkg.com/jeezy@1.11.0/lib/jeezy.min.js"></script>
  <script>
  
    var alpha = "Annie,Bob,Christina,Dylan,Elizabeth,Fred,Gertrude,Isaiah,Jennifer".split(",");

    var w = window.innerWidth, h = window.innerHeight;

    var margin = {top: h / 6, left: 75, bottom: 10, right: w / 20}, width = w - margin.right - margin.left, height = h - margin.top - margin.bottom;

    var svg = d3.select("body").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
      .append("g")
        .attr("transform", "translate(" + margin.left + ", " + margin.top + ")");

    var y_scale = d3.scaleBand()
      .rangeRound([0, height])
      .domain(alpha)

    var circle_scale = d3.scaleLinear()
      .range([0, y_scale.bandwidth()])
      .domain([0, 100])

    var x_scale = d3.scaleBand()
      .rangeRound([0, width])
      .domain(Array.apply(null, {length: 21}).map(Number.call, Number));

    // lines and labels
    var line = svg.selectAll(".baseline")
        .data(alpha)
      .enter().append("line")
        .attr("class", "baseline")
        .attr("x1", 0)
        .attr("x2", width)
        .attr("y1", function(d){ return y_scale(d); })
        .attr("y2", function(d){ return y_scale(d); })

    var label = svg.selectAll(".label")
        .data(alpha)
      .enter().append("text")
        .attr("class", "label")
        .attr("x", -10)
        .attr("y", function(d){ return y_scale(d); })
        .text(function(d){ return d; });

    var axes = svg.selectAll(".x")
        .data(alpha)
      .enter().append("g")
        .attr("class", "x axis")
        .attr("transform", function(d) { return "translate(0, " + y_scale(d) + ")"; })
        .call(d3.axisBottom(x_scale))

    drawChart(makeData());

    d3.interval(function(){ drawChart(makeData()); }, 750);

    function drawChart(data){
      
      // JOIN
      var circle = svg.selectAll(".circle")
        .data(data, function(d){ return d.id + d.day; });

      // EXIT
      circle.exit()
          .style("fill", "tomato")
        .transition().delay(325)
          .attr("d", function(d){ return half_circle(x_scale(d.day) + (x_scale.bandwidth() / 2), y_scale(d.id), 0)})
          .remove();

      // UPDATE
      circle
        .transition()
          .style("fill", "steelblue")
          .attr("d", function(d){ return half_circle(x_scale(d.day) + (x_scale.bandwidth() / 2), y_scale(d.id), circle_scale(d.value)); });

      // ENTER
      circle.enter().append("path")
          .attr("class", "circle")
          .attr("d", function(d){ return half_circle(x_scale(d.day) + (x_scale.bandwidth() / 2), y_scale(d.id), 0)})
          .style("fill", "#47ff63")
        .transition()
          .attr("d", function(d){ return half_circle(x_scale(d.day) + (x_scale.bandwidth() / 2), y_scale(d.id), circle_scale(d.value)); });
    }

    function makeData(){
      var arr = [];
      alpha.map(function(letter){
        for (var i = 0; i <= 20; i++){
          arr.push({id: letter, day: i, value: jz.num.randBetween(10, 90)})
        }
      });

      // don't return the whole thing, so we have exiting and entering
      return jz.arr.shuffle(arr).map(function(d, i){ if (i < jz.num.randBetween(70, 90)) return d; }).filter(function(d){ return d != undefined; });
    }

    function half_circle(cx, cy, r){
      return "M" + (cx + r) + "," + cy + " a" + r + "," + r + " 1 1,0 -" + (r * 2) + ",0";
    }

  </script>

</body>
</html>