Built with blockbuilder.org
Last active
September 21, 2016 22:00
-
-
Save sxywu/9715b2c1a5dc5b88421f694e23d925f2 to your computer and use it in GitHub Desktop.
DS Aug, Code 1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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], [9.5,9.0,9.5,9.5,9.5]], | |
[2, 52.20, [9.0,9.5,10.0,7.0,7.5,8.0], [9.0,8.5,8.0,9.0,9.0]], | |
[3.4, 85.68, [7.5,7.0,8.5,8.5,8.0,8.5], [9.0,8.5,8.5,8.5,9.0]], | |
[3.4, 88.74, [8.0,8.0,8.0,9.0,8.5,8.5], [8.5,9.0,9.0,9.0,9.0]], | |
[3.8, 104.88, [9.0,9.0,9.0,8.5,8.5,8.5], [9.0,9.5,9.5,9.5,9.5]], | |
[3.3, 89.10, [8.5,8.5,8.5,9.0,9.5,8.5], [9.0,9.5,9.0,9.0,9.5]] | |
] | |
}]; | |
// properties | |
var padding = 25; | |
var width = canvas.width = 1200; | |
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([25, maxRadius]); | |
var yScale = d3.scaleLinear().range([0, 1]); | |
function processData(data) { | |
_.each(data, function(d) { | |
d.processed = _.map(d.breakdown, function(score) { | |
var sum = _.reduce(score[3], function(memo, num) { | |
return memo + num; | |
}, 0); | |
return _.map(score[2], function(num) {return num * sum}); | |
}); | |
}); | |
var difficulty = _.chain(data).map('breakdown').flatten().map(0).value(); | |
var minRadius = _.min(difficulty); | |
var maxRadius = _.max(difficulty); | |
var scores = _.chain(data).map('processed').flattenDeep().value(); | |
var minY = _.min(scores); | |
var maxY = _.max(scores); | |
radiusScale.domain([minRadius, 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: (height - maxRadius) / 2, | |
color: gradient, | |
param: 0, | |
phase: (team.total / 1000) * TWO_PI, // i think phase is used for rotation | |
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]) * 3; | |
}) | |
} | |
} | |
// 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 drawFlowLine(flow) { | |
// for now just draw one circle | |
var theta, rad; | |
var point1, point2; | |
var radii, length; | |
var x0, y0; | |
var cosParam, yOffset; | |
var drawCount = 0; | |
for (i = 0; i < flow.points.length - 1; i += 1) { | |
radii = flow.radii[i]; | |
length = flow.length[i]; | |
flow.param = 0; | |
ctx.strokeStyle = flow.color; | |
_.times(length, function() { | |
drawCount += 1; | |
// first set up styling | |
ctx.beginPath(); | |
flow.phase += 0.0002; | |
flow.param += 1/length; | |
point1 = flow.points[i].first; | |
point2 = flow.points[i + 1].first; | |
cosParam = 0.5 - 0.5 * Math.cos(Math.PI * flow.param); | |
theta = flow.phase; | |
rad = (point1.y + cosParam * (point2.y - point1.y)) * flow.radii[i]; | |
flow.centerX += 0.5; | |
yOffset = 40 * Math.sin(flow.globalPhase + drawCount/600*TWO_PI); | |
ctx.setTransform(1, 0, 0, 1, flow.centerY + yOffset, flow.centerX); | |
x0 = rad * Math.cos(theta); | |
y0 = rad * Math.sin(theta); | |
// draw the first point | |
ctx.lineTo(x0, y0); | |
while (point1.next && point2.next) { | |
point1 = point1.next; | |
point2 = point2.next; | |
theta = TWO_PI * (point1.x + cosParam * (point2.x - point1.x)) + flow.phase; | |
rad = (point1.y + cosParam * (point2.y - point1.y)) * flow.radii[i]; | |
x0 = rad * Math.cos(theta); | |
y0 = rad * Math.sin(theta); | |
ctx.lineTo(x0, y0); | |
// console.log(x0, y0) | |
} | |
ctx.closePath(); | |
ctx.stroke(); | |
}); | |
} | |
} | |
processData(data); | |
var flow = generateFlowData(data[0]); | |
drawFlowLine(flow); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment