RAIN


var datastorm = datastorm || {};

datastorm.rain = (function(){
  var my = {};

  var wrapper = d3.select('#wrapper');
  var width = wrapper.node().clientWidth;
  var height = wrapper.node().clientHeight;

  var raindrops = [];
  var minMonthlyRainfall, maxMonthlyRainfall;
  var startTs; // start time of animation. Used to calculate elapsed time
  var previousTs;
  var tsStep; // time since the previous frame
  var durationTs = 400000; // full length of animation
  var animationActive = false;
  var doLabels = false;

  var nextActiveRaindropIndex = 0;

  var makeRaindropActiveInterval;

  // map radius to opacity
  var opacityScale = d3.scale.linear().domain([200, 0]).range([1, 0]).clamp(true);
  // var startTime = Date.now();

  var colour = d3.scale.linear().domain([-1, 5, 16, 22]).range(['lightblue', 'white', 'orange', 'red']).clamp(true);

  // map the rainfall amount to how quickly the opacity reduces
  var circleOpacityDampingScale = d3.scale.linear();

  // map the rainfall amount to how quickly the radius reduces
  var circleStartOpacityScale = d3.scale.linear();

  var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

  var ctx = datastorm.canvas.ctx;


  function initialiseData(rainJson, tempJson) {
    // Create array of raindrops
    _.each(rainJson, function(year) {
      // console.log(year);
      _.each(year.rainfall, function(rainfall, i) {
        if(rainfall === -1)
          return;

        raindrops.push({
          rainfall: rainfall,
          radius: 1,
          active: false,
          labelDone: false,
          // opacity: 1,
          x: Math.random() * width,
          y: Math.random() * height,
          date: months[i] + ' ' + year.year
        });

      });
    });


    // Create array of temperature
    var temps = [];
    _.each(tempJson, function(year) {
      _.each(year.temp, function(t) {
        temps.push(t);
      });
    });

    // Merge in temperature
    _.each(raindrops, function(raindrop, i) {
      raindrop.temp = temps[i];
      raindrop.colour = colour(raindrop.temp);
    });

    minMonthlyRainfall = _.min(raindrops, function(d) {return d.rainfall;}).rainfall;
    maxMonthlyRainfall = _.max(raindrops, function(d) {return d.rainfall;}).rainfall;

    circleOpacityDampingScale.domain([minMonthlyRainfall, maxMonthlyRainfall]).range([1000, 8000]);
    circleStartOpacityScale.domain([minMonthlyRainfall, maxMonthlyRainfall]).range([0.3, 1]);

    _.each(raindrops, function(raindrop) {
      raindrop.opacityDamping = 6000; //circleOpacityDampingScale(raindrop.rainfall);

      raindrop.opacity = circleStartOpacityScale(raindrop.rainfall);
    });

    // console.log(raindrops);
  }


  function makeRaindropActive() {
    if(nextActiveRaindropIndex >= raindrops.length)
      return;

    raindrops[nextActiveRaindropIndex].active = true;
    nextActiveRaindropIndex += 1;
  }

  function doFrame() {
    tsStep = Date.now() - previousTs;
    previousTs = Date.now();

    updateRaindropAttributes();

    ctx.globalAlpha = 1;
    ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
    ctx.fillRect(0, 0, width, height);

    render();

    var elapsedTs = Date.now() - startTs;
    if(elapsedTs > durationTs)
      animationActive = false;

    if(animationActive)
      window.requestAnimationFrame(doFrame);
  }

  function updateRaindropAttributes() {
    _.each(raindrops, function(rd) {
      if(!rd.active)
        return;

      // Update radius
      rd.radius += tsStep / 20;  

      // Reduce opacity
      rd.opacity -= tsStep / rd.opacityDamping;
      if(rd.opacity < 0) {
        rd.opacity = 0;
        rd.active = false;
      }
    });
  }

  function render() {
    // console.log('render');
    ctx.fillStyle = 'none';
    ctx.lineWidth = 4;


    _.each(raindrops, function(rd) {
      if(!rd.active)
        return;

      opacityScale.range([rd.opacity, 0]);

      for(var r = rd.radius; r > 5; r -= 25) {
        ctx.strokeStyle = rd.colour;
        ctx.globalAlpha = opacityScale(r);
        datastorm.canvas.drawCircleOutline(rd.x, rd.y, r);
      }

      if(doLabels && rd.labelDone === false) {
        // console.log('label', rd.x, rd.y)
        ctx.fillStyle = rd.colour;
        ctx.font = "14px sans-serif";
        ctx.globalAlpha = rd.opacity - 0.2;
        ctx.fillText(rd.date, rd.x - 30, rd.y + 5);
        // rd.labelDone = true;
        ctx.fillStyle = 'none';
      }
    });


  }

  my.init = function() {
    d3.json('https://d28qoto45d39ov.cloudfront.net/datastorm/visualisations/rain/data/rainfall.json', function(err, rainJson) {
      d3.json('https://d28qoto45d39ov.cloudfront.net/datastorm/visualisations/rain/data/maxtemp.json', function(err, tempJson) {
        initialiseData(rainJson, tempJson);
      });
    });
  };

  my.start = function() {
    animationActive = true;
    startTs = Date.now();

    makeRaindropActiveInterval = setInterval(makeRaindropActive, 500);
    window.requestAnimationFrame(doFrame);
  };

  my.stop = function() {
    animationActive = false;
    clearInterval(makeRaindropActiveInterval);
  }

  return my;
}());

datastorm.rain.init();
datastorm.rain.start();

                

See the Pen Datastorm - Rain by Genevieve Smith-Nunes (@readysaltedcode) on CodePen.