Skip to content

Instantly share code, notes, and snippets.

@sxywu
Last active October 3, 2016 07:22
Show Gist options
  • Save sxywu/e5edf25729cb6573783d927d677a4a4d to your computer and use it in GitHub Desktop.
Save sxywu/e5edf25729cb6573783d927d677a4a4d to your computer and use it in GitHub Desktop.
DS Aug, Code 5
license: mit
[
{
"event": "Women's synchronized 3m springboard",
"country": "China",
"athletes": ["M.X. Wu", "T.M. Shi"],
"total": 345.60,
"gender": "F",
"breakdown": [
[2, 55.8, [9, 9.5, 9, 8.5, 9, 9], [9.5, 9.5, 9.5, 9.5, 9.5]],
[2, 52.2, [9, 9, 9, 8.5, 8.5, 9], [8.5, 8.5, 9, 8.5, 9]],
[3, 76.5, [8.5, 8.5, 8, 8.5, 7.5, 8], [9, 8.5, 9, 8.5, 8.5]],
[3, 80.1, [9, 9, 9, 8.5, 9, 8.5], [8.5, 9, 9, 9, 9]],
[3, 81, [9.5, 8.5, 9, 8.5, 9, 9], [9, 9, 9, 9, 9]]
]
},
{
"event": "Women's synchronized 3m springboard",
"country": "Italy",
"athletes": ["T. Cagnotto", "F. Dallapé"],
"total": 313.83,
"gender": "F",
"breakdown": [
[2, 51.6, [8.5, 8.5, 8, 8.5, 8.5, 8.5], [9, 8.5, 9, 5.5, 8.5]],
[2, 50.4, [8, 8, 8.5, 8.5, 8.5, 9], [8.5, 8.5, 8.5, 8, 8.5]],
[3, 71.1, [7.5, 7.5, 8, 8, 8, 8], [8, 7.5, 8, 8.5, 8]],
[3.1, 66.03, [8, 7.5, 7.5, 6.5, 7, 7], [7, 7, 7, 7, 7.5]],
[3, 74.7, [8, 8, 8, 7.5, 8, 8], [8.5, 8.5, 8.5, 8.5, 8.5]]
]
},
{
"event": "Women's synchronized 3m springboard",
"country": "Australia",
"athletes": ["M. Keeney", "A. Smith"],
"total": 299.19,
"gender": "F",
"breakdown": [
[2, 48, [8, 8, 8, 7.5, 7.5, 8], [8, 8.5, 8, 8.5, 8]],
[2, 42, [7, 7.5, 7.5, 7, 6, 6.5], [8, 7, 7, 7, 7]],
[3, 70.2, [8.5, 8, 8, 7, 7, 7], [8, 8.5, 8, 8, 8]],
[3.1, 67.89, [7.5, 8, 7.5, 6.5, 6.5, 5.5], [7.5, 7.5, 7.5, 8, 7.5]],
[3, 71.1, [7.5, 8, 7.5, 7.5, 7.5, 7.5], [8, 7.5, 8, 8.5, 8.5]]
]
},
{
"event": "Men's synchronized 10m platform",
"country": "China",
"athletes": ["A. Chen", "Y. Lin"],
"total": 496.98,
"gender": "M",
"breakdown": [
[2, 57, [9.5, 9, 9.5, 9.5, 9, 9.5], [9.5, 9.5, 9.5, 9.5, 9.5]],
[2, 57, [9.5, 9.5, 9.5, 9.5, 9.5, 9.5], [9.5, 10, 9.5, 9.5, 9.5]],
[3.4, 92.82, [8.5, 8.5, 9, 9.5, 10, 9], [9.5, 9, 9, 9, 9.5]],
[3.6, 85.32, [7, 7, 6.5, 8, 8.5, 8.5], [8, 8, 8, 8, 8.5]],
[3.7, 106.56, [9.5, 9.5, 9, 9.5, 10, 9.5], [9.5, 9.5, 9.5, 10, 10]],
[3.6, 98.28, [9, 9, 9, 9, 9, 9], [8.5, 9.5, 9, 9, 9.5]]
]
},
{
"event": "Men's synchronized 10m platform",
"country": "United States",
"athletes": ["S. Johnson", "D. Boudia"],
"total": 457.11,
"gender": "M",
"breakdown": [
[2, 54, [9, 9, 9, 9, 9.5, 9], [9, 9, 9, 9, 9.5]],
[2, 53.4, [9, 8.5, 9, 8.5, 9, 9], [9, 9, 8.5, 8.5, 9]],
[3.2, 83.52, [8.5, 8.5, 8.5, 8.5, 9, 8.5], 8.5, 8.5, 9, 9, 9],
[3.4, 85.68, [7.5, 7, 7.5, 9, 9.5, 9], [8, 8.5, 8.5, 8.5, 8.5]],
[3.7, 85.47, [7, 7, 7.5, 8, 7.5, 7.5], [8.5, 8, 8, 8, 7.5]],
[3.6, 95.04, [8, 9, 8.5, 8.5, 8.5, 8.5], [9, 9, 9, 9, 9]]
]
},
{
"event": "Men's synchronized 10m platform",
"country": "Great Britain",
"athletes": ["T. Daley", "D. Goodfellow"],
"total": 444.45,
"gender": "M",
"breakdown": [
[2, 51.6, [8.5, 9.0, 9.5, 8.5, 8.5, 8.5], [9, 8.5, 8.5, 8.5, 8.5]],
[2, 49.8, [8.5, 8.5, 8.5, 8.5, 8, 8.5], [8, 8, 8, 8.5, 8.5]],
[3.2, 79.68, [9, 8.5, 8.5, 7.5, 7.5, 8], [9, 8, 8.5, 8.5, 8.5]],
[3.4, 81.6, [8.5, 9, 8.5, 7.5, 7.5, 7.5], [8, 8.5, 8, 7.5, 8]],
[3.7, 92.13, [8, 8, 8, 8, 8.5, 8], [9, 8.5, 8, 8.5, 8.5]],
[3.6, 89.64, [8.5, 8.5, 8.5, 8, 7.5, 7.5], [8.5, 8.5, 8, 8.5, 8.5]]
]
},
{
"event": "Women's synchronized 10m platform",
"country": "China",
"athletes": ["R. L. Chen", "H. X. Liu"],
"total": 354,
"gender": "W",
"breakdown": [
[2, 55.8, [9, 9, 9, 9, 9, 9], [9.5, 9.5, 9.5, 9.5, 9.5]],
[2, 55.8, [9, 8.5, 9, 9.5, 9, 9], [9.5, 9.5, 9.5, 9.5, 9.5]],
[3, 79.2, [8.5, 8.5, 9, 8.5, 8.5, 8.5], [9, 8.5, 9, 9, 9]],
[3.2, 75.84, [8, 7.5, 7.5, 7, 8, 7.5], [9, 8, 8, 8.5, 8]],
[3.2, 87.36, [9.5, 8.5, 8.5, 8.5, 8.5, 8.5], [9.5, 9.5, 9.5, 9.5, 9]]
]
},
{
"event": "Women's synchronized 10m platform",
"country": "Malaysia",
"athletes": ["P. Rinong", "J.H. Cheong"],
"total": 344.34,
"gender": "W",
"breakdown": [
[2, 52.8, [8.5, 8.5, 9, 8.5, 9.5, 8.5], [9, 9, 9, 9.5, 9]],
[2, 52.8, [8.5, 8.5, 8.5, 9, 8.5, 8.5], [8.5, 9, 9, 9, 9]],
[3, 76.5, [6.5, 8, 8, 9, 8.5, 8.5], [8.5, 8, 8.5, 9, 9]],
[3.2, 79.68, [8, 7.5, 8.5, 8, 8.5, 7.5], [8.5, 8.5, 8.5, 9, 8.5]],
[3.2, 82.56, [8.5, 8.5, 8.5, 8.5, 8, 8.5], [8.5, 8.5, 9, 8.5, 9]]
]
},
{
"event": "Women's synchronized 10m platform",
"country": "Canada",
"athletes": ["M. Benfeito", "R. Filion"],
"total": 336.18,
"gender": "W",
"breakdown": [
[2, 52.8, [9, 8.5, 8.5, 8.5, 9.5, 8.5], [9, 9, 9, 9, 9]],
[2, 52.8, [9, 8.5, 8, 8.5, 8.5, 8.5], [9, 9, 9, 9, 9]],
[3, 74.7, [8, 8, 8, 8, 8, 8], [9, 8.5, 8.5, 8.5, 8.5]],
[3.3, 75.24, [7.5, 7, 7, 7, 7, 7.5], [8, 7.5, 8, 8, 8]],
[3.2, 80.64, [8.5, 8, 8, 8.5, 8.5, 8], [8.5, 8.5, 8.5, 8.5, 9]]
]
}
]
<!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');
ctx.globalCompositeOperation = 'overlay';
// properties
var padding = 25;
var width = canvas.width = 800;
var height = canvas.height = 1200;
var maxRadius = 30;
var TWO_PI = 2 * Math.PI;
var colors = {
'China': [[255,0,0], [255,255,0]],
'United States': [[187, 19, 62], [0, 44, 119]],
'Great Britain': [[0, 39, 118], [198, 12, 48]],
'Italy': [[0, 146, 70], [206, 43, 55]],
'Australia': [[0, 0, 139], [255, 0, 0]],
'Malaysia': [[1, 0, 102], [255, 204, 0]],
'Canada': [ [255, 255, 255], [255, 0, 0]],
'F': [250,176,189],
'M': [187,209,222]
};
// scale
var radiusScale = d3.scaleLinear().range([5, maxRadius]);
var xScale = d3.scaleLinear()
.range([padding, width - padding]);
var yScale = d3.scaleLinear().range([0, 1]);
function processData(data) {
_.each(data, function(d) {
// create an artifical first score
d.breakdown.unshift([1.5, 1, [5, 5, 5, 5, 5, 5]]);
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);
var minX = _.minBy(data, 'total').total - padding;
var maxX = _.maxBy(data, 'total').total + padding;
radiusScale.domain([1, maxRadius]);
xScale.domain([minX, maxX]);
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.1)');
gradient.addColorStop(0, 'rgba(' + colors[team.country][1] + ',0.1)');
return {
centerX: padding,
centerY: xScale(team.total),
stroke: gradient,
fill: 'rgba(' + colors[team.gender] + ', 0.01)',
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]) * 3.5;
}),
rotations: _.map(team.breakdown, function(scores) {
return scores[1] / team.total;
}),
totalLength: team.total * 3.5,
elapsed: 0,
data: team
}
}
// generate the data for just one of the circles in the flow line
// majority of this function is taken from
// Dan Gries's tutorial http://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) {
if (elapsed > flow.totalLength) return;
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
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.stroke;
ctx.fillStyle = flow.fill;
ctx.beginPath();
flow.centerX += 0.5;
var yOffset = (_.last(flow.radii) / 2) * Math.sin(drawCount/flow.data.total *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();
// ctx.fill();
return true;
});
});
flow.elapsed = elapsed;
}
// load the data
d3.json('data.json', function(data) {
processData(data);
var flows = _.map(data, function(d) {
var flow = generateFlowData(d);
calculateCircles(flow);
return flow;
})
var t = d3.timer(function(elapsed) {
_.each(flows, function(flow) {
drawCircle(elapsed, flow);
});
if (elapsed > 2000) t.stop();
});
});
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment