Last active
January 6, 2017 03:29
-
-
Save topologicallytony/8a80ba89ae8189336efeef502710752e to your computer and use it in GitHub Desktop.
Koch Snowflake
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>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 | |
var dataset = [ | |
[1,0,0,0,Math.PI], | |
[0,0,Math.cos(Math.PI / 3),Math.sin(Math.PI / 3),(Math.PI / 3)], | |
[Math.cos(Math.PI / 3),Math.sin(Math.PI / 3),1,0,(-1 * (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