-
-
Save stoikerty/31d032c0f35efc73a15b3f2b416fa08d to your computer and use it in GitHub Desktop.
Mobius Strip
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> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Mobius Strip</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"> | |
.line { | |
fill: none; | |
stroke: steelblue; | |
stroke-width: 1px; | |
} | |
.curve { | |
fill: none; | |
stroke: steelblue; | |
stroke-width: 0.0625px; | |
} | |
.arrow{ | |
stroke: black; | |
stroke-width: 2; | |
} | |
</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 = 1200; | |
var h = 600; | |
//padding creates a buffer of white space around the chart to make it a little easier to look at | |
var padding = 15; | |
var numDataPoints = 500; | |
var stripLen = (Math.PI); | |
var strip = []; | |
var twist = []; | |
var mobius = []; | |
var top = []; | |
var bottom = []; | |
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); | |
var y_m = 0.5 * Math.sin(theta); | |
strip.push([(t) - (stripLen / 2), 0, 0, i]); | |
twist.push([(t) - (stripLen / 2), 0, (theta / 2), i]); | |
mobius.push([x_m, y_m, (theta / 2), i]); | |
// mobius.push([x_m, y_m, (Math.PI / 2) * (Math.atan((t / 2) - (Math.PI / 2)) + 1)]); | |
} | |
//Create the scales used to map datapoints | |
var xScale = d3.scaleLinear() | |
.domain([-(stripLen / 2 + 0.5), (stripLen / 2 + 0.5)]) | |
.range([padding, w - padding]); | |
var yScale = d3.scaleLinear() | |
.domain([-1.5, 1.5]) | |
.range([h - padding, padding]); | |
//Create a canvas to display the chart on | |
var svg = d3.select("body").append("svg") | |
.attr("viewBox", "0 0 " + w + " " + h); | |
//Create group elements to layer the svg | |
//This is an easy way to make sure things you want on top are on top, and things you want behind stay behind | |
var layer1 = svg.append('g'); | |
var layer2 = svg.append('g'); | |
svg.append("svg:defs") | |
.append("svg:marker") | |
.attr("id", "arrow") | |
.attr("viewBox", "0 0 10 10") | |
.attr("refX", 28) | |
.attr("refY", 5) | |
.attr("markerUnits", "strokeWidth") | |
.attr("markerWidth", 12) | |
.attr("markerHeight", 9) | |
.attr("orient", "auto") | |
.append("svg:path") | |
.attr("d", "M 0 0 L 10 5 L 0 10 z") | |
var topLine = d3.line() | |
.curve(d3.curveBasis) | |
.x(function(d) { return xScale(d[0] + pct * Math.sin(d[2])); }) | |
.y(function(d) { return yScale(d[1] + pct * Math.cos(d[2])); }); | |
var bottomLine = d3.line() | |
.curve(d3.curveBasis) | |
.x(function(d) { return xScale(d[0] - pct * Math.sin(d[2])); }) | |
.y(function(d) { return yScale(d[1] - pct * Math.cos(d[2])); }); | |
function update_strip() { | |
//Draw all the lines | |
var line = layer2.selectAll(".line").data(dataset, function(d,i) { return d[3];}); | |
line | |
.transition() | |
.duration(2500) | |
.attr("x1", function(d){return xScale(d[0] + 0.5 * Math.sin(d[2]));}) | |
.attr("y1", function(d){return yScale(d[1] + 0.5 * Math.cos(d[2]));}) | |
.attr("x2", function(d){return xScale(d[0] - 0.5 * Math.sin(d[2]));}) | |
.attr("y2", function(d){return yScale(d[1] - 0.5 * Math.cos(d[2]));}); | |
line.enter().append("line") | |
.attr("x1", function(d){return xScale(d[0] + 0.5 * Math.sin(d[2]));}) | |
.attr("y1", function(d){return yScale(d[1] + 0.5 * Math.cos(d[2]));}) | |
.attr("x2", function(d){return xScale(d[0] - 0.5 * Math.sin(d[2]));}) | |
.attr("y2", function(d){return yScale(d[1] - 0.5 * Math.cos(d[2]));}) | |
.attr("class", "line"); | |
} | |
function updateArrow() { | |
var arrows = layer2.selectAll(".arrow").data(arrow, function(d,i) { return d[0];}); | |
arrows | |
.attr("x1", function(d){ return xScale(d[0]);}) | |
.attr("x2", function(d){ return xScale(d[1]);}) | |
.attr("y1", function(d){ return yScale(d[2]);}) | |
.attr("y2", function(d){ return yScale(d[3]);}); | |
arrows.enter().append("line") | |
.attr("x1", function(d){ return xScale(d[0]);}) | |
.attr("x2", function(d){ return xScale(d[1]);}) | |
.attr("y1", function(d){ return yScale(d[2]);}) | |
.attr("y2", function(d){ return yScale(d[3]);}) | |
.attr("marker-end", "url(#arrow)") | |
.attr("class", "arrow"); | |
} | |
function updateHorizontalLines() { | |
var dataset_subset = dataset.filter(function(d, i) { | |
return i % 10 == 0; | |
}); | |
dataset_subset.push(dataset[numDataPoints - 1]); | |
var top = layer2.selectAll(".top") | |
.data(dataset_subset); | |
top.transition().delay(2500).duration(2500).attr("d", function(d) { return topLine(dataset_subset); }).transition().attr("opacity",0); | |
top.enter().append("path") | |
.attr("class", "curve top").transition().delay(2500) | |
.attr("d", function(d) { return topLine(dataset_subset); }); | |
var bottom = layer2.selectAll(".bottom") | |
.data(dataset_subset); | |
bottom.transition().delay(2500).duration(2500).attr("d", function(d) { return bottomLine(dataset_subset);}).transition().attr("opacity",0); | |
bottom.enter().append("path") | |
.attr("class", "curve bottom").transition().delay(2500) | |
.attr("d", function(d) { return bottomLine(dataset_subset); }); | |
} | |
var dataset = []; | |
dataset = strip; | |
var state = 0; | |
update_strip(); | |
var pct = -0.5; | |
for(pct = -0.5; pct <= 0.5; pct = pct + 0.1) { | |
updateHorizontalLines(); | |
} | |
var arrow = [ | |
[(stripLen / 2 + 0.5), (stripLen / 2 + 0.5), 1.5, 1], | |
[(stripLen / 2), (stripLen / 2), 1, 1.5], | |
[(stripLen / 2), (stripLen / 2 + 0.5), 1.5, 1.5], | |
[(stripLen / 2), (stripLen / 2 + 0.5), 1, 1]]; | |
//updateArrow(); | |
function update() { | |
state++; | |
switch(state) { | |
case 1: | |
dataset = twist; | |
break; | |
case 2: | |
dataset = mobius; | |
break; | |
case 3: | |
dataset = strip; | |
state = 0; | |
} | |
update_strip(); | |
layer2.selectAll(".curve").remove(); | |
for(pct = -0.5; pct <= 0.5; pct = pct + 0.1) { | |
console.log(pct); | |
updateHorizontalLines(); | |
} | |
} | |
d3.select("body") | |
.on("click", update); | |
d3.select("body") | |
.on("touchstart", update); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment