block by sxywu 0909fba07ffd7ec9a67a619d6cb5772d

DS Aug, Code 4

Full Screen

Built with blockbuilder.org

forked from sxywu‘s block: DS Aug, Code 1

forked from sxywu‘s block: DS Aug, Code 1

forked from sxywu‘s block: DS Aug, Code 3

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>
  <style>
  </style>
</head>

<body>
  <canvas id='canvas'></canvas>
  <script>
		var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    
    // data from olympics diving
    var data = [{
      "country": "China",
      "athletes": ["A. Chen", "Y. Lin"],
      "total": 496.98,
      "breakdown": [
        [2, 56.4, [9.5,9.5,9.5,9,9.5,9]],
        [2, 52.20, [9.0,9.5,10.0,7.0,7.5,8.0]],
        [3.4, 85.68, [7.5,7.0,8.5,8.5,8.0,8.5]],
        [3.4, 88.74, [8.0,8.0,8.0,9.0,8.5,8.5]],
        [3.8, 104.88, [9.0,9.0,9.0,8.5,8.5,8.5]],
        [3.3, 89.10, [8.5,8.5,8.5,9.0,9.5,8.5]]
      ]
    }];
    
    // properties
    var padding = 25;
    var width = canvas.width = window.innerWidth;
    var height = canvas.height = 1200;
    var colors = {'China': [[255,0,0], [255,255,0]]};
    var TWO_PI = 2 * Math.PI;
    
    var maxRadius = 50;
    var radiusScale = d3.scaleLinear().range([5, maxRadius]);
    var yScale = d3.scaleLinear().range([0, 1]);
    function processData(data) {
    	_.each(data, function(d) {
        // create an artifical first score
        d.breakdown.unshift([1, 1, [10, 10, 10, 10, 10, 10]]);
        d.processed = _.map(d.breakdown, function(score) {
          return _.map(score[2], function(num) {return num * score[0]});
        });
      });
      
      var difficulty = _.chain(data).map('breakdown').flatten().map(0).value();
      var maxRadius = _.max(difficulty);
      var scores = _.chain(data).map('processed').flattenDeep().value();
      var minY = _.min(scores);
      var maxY = _.max(scores);
      
      radiusScale.domain([1, maxRadius]);
      yScale.domain([minY, maxY]);
    }
    
    // generate the flow line, given one event for one team
    function generateFlowData(team) {
      var gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, maxRadius);
      gradient.addColorStop(1, 'rgba(' + colors[team.country][0] + ',0.2)');
      gradient.addColorStop(0, 'rgba(' + colors[team.country][1] + ',0.2)');
      
      return {
        centerX: padding,
        centerY: (width / 2) + padding + maxRadius,
        color: gradient,
//         globalPhase: (team.total / 1000) * TWO_PI, // globalPhase is for yOffset
        radii: _.map(team.breakdown, function(scores) {
          return radiusScale(scores[0]);
        }),
        points: _.map(team.processed, function(scores) {
        	return generateCircleData(scores);
      	}),
        length: _.map(team.breakdown, function(scores) {
          return Math.round(scores[1]) * 4;
        }),
        rotations: _.map(team.breakdown, function(scores) {
          return scores[1] / team.total;
        }),
        totalLength: team.total * 4,
        elapsed: 0
      }
    }
    
    // generate the data for just one of the circles in the flow line
    // majority of this function is taken from
    // Dan Gries's tutorial //rectangleworld.com/blog/archives/462
    // in particular the function setLinePoints
    function generateCircleData(scores) {
      var circle = {
				first: {x: 0, y: 1}
      };
      var last = {x: 1, y: 1};
      var minY = maxY = 1;
      var point, nextPoint;
      var dx, newX, newY;
      
      // connect first point with the last
      circle.first.next = last;
      _.each(scores, function(score) {
        point = circle.first;
        while (point.next) {
          nextPoint = point.next;
          dx = nextPoint.x - point.x;
          newX = 0.5 * (point.x + nextPoint.x);
          newY = 0.5 * (point.y + nextPoint.y);
          // vary the y-pos by the score, but subtract it
          // by what is around the mid-point so that
          // some are positive and others are negative
          newY += dx * (yScale(score) * 2 - 1);
          
          var newPoint = {x: newX, y: newY};
          
          //min, max
          if (newY < minY) {
            minY = newY;
          }
          else if (newY > maxY) {
            maxY = newY;
          }
          
          // insert mid-point
          newPoint.next = nextPoint;
          point.next = newPoint;
          
          point = nextPoint;
        }
      })
      
//       normalize to values between 0 and 1
      if (maxY != minY) {
        var normalizeRate = 1/(maxY - minY);
        point = circle.first;
        while (point != null) {
          point.y = normalizeRate*(point.y - minY);
          point = point.next;
        }
      }
      
      return circle;
    }
    
    function tweenPoints(circle1, circle2) {
      // interpolate all the points of the circles
      var interpolators = _.map(circle1, function(point1, i) {
        return {
          x: d3.interpolate(point1.x, circle2[i].x),
          y: d3.interpolate(point1.y, circle2[i].y)
        };
      });
      return function(t) {
        return _.map(interpolators, function(interpolate) {
          return {x: interpolate.x(t), y: interpolate.y(t)};
        });
      };
    }
    
    // given set of points making up a squiggly line
    // turn it into a squiggly imperfect circle
    // also calculate the interpolators for them
    function calculateCircles(flow) {
      flow.circles = [];
      flow.interpolators = [];
      var prevCircle = null;
			_.each(flow.points, function(points, i) {
        // calculate circles
        var point = points.first;
        var rotation = flow.rotations[i];
        var radii = flow.radii[i];
        var circle = [];
        
        var theta = TWO_PI * (point.x + rotation);
        var radius = radii * point.y;
        var x = radius * Math.cos(theta);
        var y = radius * Math.sin(theta);
        circle.push({x: x, y: y});
        
        while (point.next) {
          point = point.next;
          
          // given its x and y, calculate its theta and radius
          var theta = TWO_PI * (point.x + rotation);
          var radius = radii * point.y;

          var x = radius * Math.cos(theta);
          var y = radius * Math.sin(theta);

          circle.push({x: x, y: y});
        }
        flow.circles.push(circle);
        
        // now calculate the interpolators
        if (prevCircle) {
          var interpolators = tweenPoints(prevCircle, circle);
          flow.interpolators.push(interpolators);
        }

        prevCircle = circle;
      });
    }
    
    function drawCircle(elapsed, flow) {
      var drawCount;
      elapsed = parseInt(elapsed);
      elapsed = d3.easeQuad(elapsed / flow.totalLength);
      elapsed = parseInt(flow.totalLength * elapsed);
      if (elapsed < flow.elapsed) {
        // if it's going backwards, clear the canvas
        // and set everything back to 0
        console.log(elapsed, flow.elapsed)
        ctx.clearRect(0, 0, width, height);
        flow.elapsed = 0;
      }
      
      _.times(elapsed - flow.elapsed, function(t) {
        t += flow.elapsed;
        drawCount = t;
        _.some(flow.interpolators, function(interpolator, i) {
          var length = flow.length[i + 1];
          
          if (t > length) {
            // if elapsed is more than length of section
            // subtract length and move to next interpolator
            t -= length;
            return false;
          }
          
          // else this is the interpolator to use
          ctx.strokeStyle = flow.color;
          ctx.beginPath();

          flow.centerX += 0.5;
          var yOffset = 40 * Math.sin(drawCount/600*TWO_PI);
          ctx.setTransform(1, 0, 0, 1, flow.centerY + yOffset, flow.centerX);

          var points = interpolator(t / length);
          _.each(points, function(pos) {
            ctx.lineTo(pos.x, pos.y);
          });

          ctx.closePath();
          ctx.stroke();
          
          return true;
        });
      });

      flow.elapsed = elapsed;
    }
    
    processData(data);
    var flow = generateFlowData(data[0]);
    calculateCircles(flow);
    
    var t = d3.timer(function(elapsed) {
      drawCircle(elapsed, flow);
      if (elapsed > flow.totalLength) t.stop();
    });
      
  </script>
</body>