Skip to content

Instantly share code, notes, and snippets.

@topologicallytony
Last active January 8, 2017 03:42
Show Gist options
  • Save topologicallytony/97ff820f61aa12833f9503966bf0d62a to your computer and use it in GitHub Desktop.
Save topologicallytony/97ff820f61aa12833f9503966bf0d62a to your computer and use it in GitHub Desktop.
Torus
<!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