Skip to content

Instantly share code, notes, and snippets.

@topologicallytony
Last active February 28, 2021 05:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save topologicallytony/8afe6a1747c37f099c46a233c00bf1e6 to your computer and use it in GitHub Desktop.
Save topologicallytony/8afe6a1747c37f099c46a233c00bf1e6 to your computer and use it in GitHub Desktop.
Mobius Strip
<!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