Built with blockbuilder.org
forked from sxywu's block: DS Aug, Code 1
license: mit |
Built with blockbuilder.org
forked from sxywu's block: DS Aug, Code 1
<!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' width="1024px" height="576px"></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, | |
"difficulty": [2, 2, 3.4, 3.4, 3.8, 3.3], | |
"scores": [56.4, 52.20, 85.68, 88.74, 104.88, 89.10] | |
}]; | |
// properties | |
var padding = 25; | |
var width = 800; | |
var height = 600; | |
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]); | |
function processData(data) { | |
var difficulty = _.chain(data).map('difficulty').flatten().value(); | |
debugger | |
var minR = _.min(difficulty); | |
var maxR = _.max(difficulty); | |
radiusScale.domain([minR, maxR]); | |
} | |
// 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.difficulty, function(d) { | |
return radiusScale(d); | |
}), | |
points: _.times(team.scores.length, function() { | |
return generateCircleData(); | |
}), | |
length: _.map(team.scores, function(score) { | |
return Math.round(score) * 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() { | |
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; | |
_.times(7, function() { | |
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); | |
newY += dx * (Math.random() * 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) { | |
console.log(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; | |
var xSqueeze = 0.75; | |
// go through each of the points | |
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() { | |
point1 = flow.points[i].first; | |
point2 = flow.points[i + 1].first; | |
drawCount += 1; | |
// first set up styling | |
ctx.beginPath(); | |
flow.phase += 0.0002; | |
flow.param += 1/length; | |
cosParam = 0.5 - 0.5 * Math.cos(Math.PI * flow.param); | |
theta = flow.phase; | |
rad = (point1.y + cosParam * (point2.y - point1.y)) * radii; | |
//move center | |
flow.centerX += 0.5; | |
yOffset = 40 * Math.sin(flow.globalPhase + drawCount/600*TWO_PI); | |
ctx.setTransform(xSqueeze, 0, 0, 1, flow.centerY + yOffset, flow.centerX); | |
x0 = xSqueeze * 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)) * radii; | |
x0 = xSqueeze * 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> |