Last active
January 8, 2017 03:42
-
-
Save topologicallytony/97ff820f61aa12833f9503966bf0d62a to your computer and use it in GitHub Desktop.
Torus
This file contains hidden or 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> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Torus</title> | |
<!-- D3.js --> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<!-- Latest compiled and minified CSS --> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> | |
<style type="text/css"> | |
.arc { | |
fill: none; | |
stroke: steelblue; | |
stroke-width: 1px; | |
} | |
</style> | |
</head> | |
<body> | |
<div> | |
Click to Iterate | |
</div> | |
<script type="text/javascript"> | |
//Width and height of the visualization. Smaller numbers will zoom in, larger numbers zoom out | |
var w = 2400; | |
var h = 1200; | |
//padding creates a buffer of white space around the chart to make it a little easier to look at | |
var padding = 15; | |
var stripLen = (2 * Math.PI); | |
var numDataPoints = 500; | |
var strip = []; | |
var svg = d3.select("body").append("svg") | |
.attr("viewBox", "0 0 " + w + " " + h); | |
var layer1 = svg.append('g'); | |
var layer2 = svg.append('g'); | |
//Create the scales used to map datapoints | |
var xScale = d3.scaleLinear() | |
.domain([-Math.PI * (stripLen / 2 + 0.5), Math.PI * (stripLen / 2 + 0.5)]) | |
.range([padding, w - padding]); | |
var yScale = d3.scaleLinear() | |
.domain([-2, 2]) | |
.range([h - padding, padding]); | |
//Generate the base data | |
for (var i = 0; i < numDataPoints; i++) { | |
//Partition [0,stripLen] into 50 buckets | |
var t = ((i * stripLen) / (numDataPoints - 1)); | |
var theta = ((i * (Math.PI) * 2) / (numDataPoints - 1)); | |
//x,y coordinates for the center of each line segment making up the mobius strip | |
var x_m = (stripLen / 2) * Math.cos(theta + (Math.PI / 2)); | |
var y_m = Math.sin(theta + (Math.PI / 2)); | |
strip.push([(t) - (stripLen / 2), 0, 0, i, x_m, y_m]); | |
} | |
strip.pop(); | |
function update_strip() { | |
var arc = layer2.selectAll(".arc").data(strip, function(d, i) { | |
return d[3]; | |
}); | |
arc.enter().append("path") | |
.attr("class", "arc"); | |
} | |
// helper function to generate the initial sheet | |
function generateStep1(start_x, start_y, end_x, end_y) { | |
return ['M', start_x, start_y, | |
'L', end_x, end_y | |
].join(' '); | |
} | |
// custom interpolator, which returns an interpolator function | |
// which when called with a time (0-1), generates a segment sized according to time | |
// this one simply draws a straight line over time | |
function interpolateStep1(start_x, start_y, end_x, end_y) { | |
var line_len = Math.max(end_x - start_x, end_y - start_y); | |
var mid_x = (end_x + start_x) / 2; | |
var mid_y = (end_y + start_y) / 2; | |
return function(t) { | |
return generateStep1( | |
mid_x + ((start_x - mid_x) * t), | |
mid_y + ((start_y - mid_y) * t), | |
mid_x + ((end_x - mid_x) * t), | |
mid_y + ((end_y - mid_y) * t)); | |
}; | |
} | |
// helper function to generate the tube | |
function generateStep2(start_x, start_y, end_x, end_y, r) { | |
// convert angles to Radians | |
var angle = (((2 * Math.PI * r) - Math.sqrt(Math.pow(end_x - start_x, 2) + Math.pow(end_y - start_y, 2))) / (2 * r)); | |
var largeArc = angle <= Math.PI ? 0 : 1; // 1 if angle > 180 degrees | |
var sweepFlag = 0; // is arc to be drawn in +ve direction? | |
var dx = r * Math.cos(angle + ((Math.PI) * (2 / 2))); | |
var dy = r * Math.sin(angle + ((Math.PI) * (2 / 2))); | |
return ['M', start_x + r + dx, start_y + dy, | |
'A', r, r, 0, largeArc, sweepFlag, start_x, start_y, | |
'L', end_x, end_y, | |
'A', r, r, 0, largeArc, sweepFlag, end_x + r + dx, end_y - dy | |
].join(' '); | |
} | |
// custom interpolator, which returns an interpolator function | |
// which when called with a time (0-1), generates a segment sized according to time | |
// this one animates from a sheet to a tube in a curling manner | |
function interpolateStep2(start_x, start_y, end_x, end_y) { | |
var line_len = Math.max(end_x - start_x, end_y - start_y); | |
var mid_x = (end_x + start_x) / 2; | |
var mid_y = (end_y + start_y) / 2; | |
var r = line_len / (2 * Math.PI); | |
return function(t) { | |
return generateStep2( | |
start_x + ((mid_x - start_x) * t), | |
start_y + ((mid_y - start_y) * t), | |
end_x + ((mid_x - end_x) * t), | |
end_y + ((mid_y - end_y) * t), | |
r); | |
}; | |
} | |
function update(){ | |
switch (state) { | |
case 0: | |
layer2.selectAll(".arc").remove(); | |
update_strip(); | |
var arc = layer2.selectAll(".arc"); | |
arc.transition("grow").duration(2500) | |
.attrTween('d', function(d) { | |
return interpolateStep1(xScale(d[0]), yScale(1), xScale(d[0]), yScale(-1)); | |
}); | |
state++; | |
break; | |
case 1: | |
var arc = layer2.selectAll(".arc"); | |
arc.transition("state1").duration(2500) | |
.attrTween('d', function(d) { | |
return interpolateStep2(xScale(d[0]), yScale(1), xScale(d[0]), yScale(-1)); | |
}); | |
state++; | |
break; | |
case 2: | |
var arc = layer2.selectAll(".arc"); | |
arc.transition("state2").duration(2500) | |
.attr("transform", function(d) { | |
return "translate(" + (xScale(d[4]) - xScale(d[0])) + "," + (yScale(d[5]) - yScale(0)) + ")"; | |
}); | |
state = 0; | |
break; | |
} | |
} | |
// on click transition states | |
d3.select("body") | |
.on("click", update); | |
d3.select("body") | |
.on("touchstart", update); | |
// initialize | |
update_strip(); | |
var arc = layer2.selectAll(".arc"); | |
arc.transition("grow").duration(2500) | |
.attrTween('d', function(d) { | |
return interpolateStep1(xScale(d[0]), yScale(1), xScale(d[0]), yScale(-1)); | |
}); | |
var state = 1; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment