Skip to content

Instantly share code, notes, and snippets.

@topologicallytony
Last active January 6, 2017 03:28
Show Gist options
  • Save topologicallytony/a210308874b745d9c1b6bbaeb2e6a49e to your computer and use it in GitHub Desktop.
Save topologicallytony/a210308874b745d9c1b6bbaeb2e6a49e to your computer and use it in GitHub Desktop.
Inverted Koch Snowflake
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Inverted Koch Snowflake</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">
</style>
</head>
<body>
<div>
Click to iterate
</div>
<div id="container">
<script type="text/javascript">
//Width and height of the visualization
var w = 500;
var h = 500;
//padding creates a buffer of white space around the chart to make it a little easier to look at
var padding = 15;
//Create the scales used to map the set
var xScale = d3.scaleLinear()
.domain([-.5,1.5])
.range([padding, w - padding]);
var yScale = d3.scaleLinear()
.domain([-1,1])
.range([h - padding, padding]);
//Create a canvas to display the chart on
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", 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');
//This function will iterate the koch snowflake and is called on mouse click
function step() {
//temp_dataset holds the most recently displayed iteration
temp_dataset = dataset;
//dataset will get loaded with the next iteration
dataset = [];
//how much the length of a given line segment will change with an iteration
dist = dist * (1 / 3);
//How many line segments we need to iterate on
var len = temp_dataset.length;
//For each line segment, we remove the middle third and load the 2 new line segments into dataset
for (var i = 0; i < len; i++) {
//get the endpoints of the current line segment
var x1 = temp_dataset[i][0];
var y1 = temp_dataset[i][1];
var x4 = temp_dataset[i][2];
var y4 = temp_dataset[i][3];
//remove the middle third
var x2 = x1 + ((1 / 3) * (x4 - x1));
var y2 = y1 + ((1 / 3) * (y4 - y1));
var x3 = x1 + ((2 / 3) * (x4 - x1));
var y3 = y1 + ((2 / 3) * (y4 - y1));
//get the endpoints of the new segments
var x_new = x2 + (dist * Math.cos((Math.PI / 3) + temp_dataset[i][4]));
var y_new = y2 + (dist * Math.sin((Math.PI / 3) + temp_dataset[i][4]));
//store the new endpoints
//The angle of the line will change only on the middle two segments, the first is an increase of 60 degrees, the second a decrease
dataset.push([x1, y1, x2, y2, temp_dataset[i][4]]);
dataset.push([x2, y2, x_new, y_new, (temp_dataset[i][4] + (Math.PI / 3))]);
dataset.push([x_new, y_new, x3, y3, (temp_dataset[i][4] - (Math.PI / 3))]);
dataset.push([x3, y3, x4, y4, temp_dataset[i][4]]);
}
//For each line segment, draw the lines
for (var i = 0; i < dataset.length; i++) {
var line = layer2.append("line")
.attr("x1", xScale(dataset[i][0]))
.attr("y1",yScale(dataset[i][1]))
.attr("x2",xScale(dataset[i][2]))
.attr("y2",yScale(dataset[i][3]))
.attr("stroke", "steelblue")
.attr("stroke-width", "1.5px")
.attr("fill", "none");
}
//Display the iteration number based on the length of the side
var iter = Math.round(Math.log(1 / dist) / Math.log(3));
var numIterations = layer1.selectAll(".iter")
.data([0]);
numIterations
.text(iter);
numIterations.enter().append("text")
.attr("x", xScale(0))
.attr("y", yScale(1))
.text(iter)
.attr("font-size", "12px")
.attr("class", "iter");
numIterations.exit().remove();
}
//initialize the datasets
var temp_dataset = [];
//dataset will hold all the line segments
//initialize with an equilateral triangle.
//Careful with angles on the bottom side which is technically a line drawn with angle pi
//For inverted koch, all lines are a reflection of the original angle of pi radians
var dataset = [
[0,0,1,0,0],
[Math.cos(Math.PI / 3),Math.sin(Math.PI / 3),0,0,4*(Math.PI / 3)],
[1,0,Math.cos(Math.PI / 3),Math.sin(Math.PI / 3),(2 * (Math.PI / 3))]];
var dist = 1;
//Draw the initial line from (0,0) to (1,0)
var line = layer2.append("line")
.attr("x1",xScale(dataset[0][0]))
.attr("y1",yScale(dataset[0][1]))
.attr("x2",xScale(dataset[0][2]))
.attr("y2",yScale(dataset[0][3]))
.attr("stroke", "steelblue")
.attr("stroke-width", "1.5px")
.attr("fill", "none");
//Draw a line from the top of the triangle to (0,0)
var line = layer2.append("line")
.attr("x1",xScale(dataset[1][0]))
.attr("y1",yScale(dataset[1][1]))
.attr("x2",xScale(dataset[1][2]))
.attr("y2",yScale(dataset[1][3]))
.attr("stroke", "steelblue")
.attr("stroke-width", "1.5px")
.attr("fill", "none");
//Draw a line from (1,0) to the top of the triangle
var line = layer2.append("line")
.attr("x1",xScale(dataset[2][0]))
.attr("y1",yScale(dataset[2][1]))
.attr("x2",xScale(dataset[2][2]))
.attr("y2",yScale(dataset[2][3]))
.attr("stroke", "steelblue")
.attr("stroke-width", "1.5px")
.attr("fill", "none");
//Handle click event by clearing all the lines and running step()
d3.select("body")
.on("click", function(){
svg.selectAll("line").remove();
step();
});
d3.select("body")
.on("touchstart", function() {
svg.selectAll("line").remove();
step();
});
</script>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment