Skip to content

Instantly share code, notes, and snippets.

@MrHen
Last active April 13, 2016 22:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MrHen/1c2907a5f06e10d6595eeadaf51f4eef to your computer and use it in GitHub Desktop.
Save MrHen/1c2907a5f06e10d6595eeadaf51f4eef to your computer and use it in GitHub Desktop.
Sierpinski carpet
height: 729
scrolling: true
license: MIT

Sierpinski carpet using a recursive D3 selection pattern. The cursor will automatically split areas further.

<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="sierpinski-carpet-container"></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.6.1/lodash.min.js"></script>
<script src="script.js"></script>
</body>
</html>
var startingDepth = 3;
var batch = 10;
var tick = 300;
var root = {
x: 0,
y: 0,
resized: false,
depth: 0,
size: 729
};
// http://colorbrewer2.org/?type=sequential&scheme=BuGn&n=9
var colors = d3.scale.quantize().domain([8, 0]).range(['#fff7fb', '#ece2f0', '#d0d1e6', '#a6bddb', '#67a9cf', '#3690c0', '#02818a', '#016c59', '#014636']);
var background = "#211";
var svg = d3.select("#sierpinski-carpet-container")
.append("svg")
.attr("id", "sierpinski-carpet-chart")
.attr("height", root.size)
.attr("width", root.size)
.style("background-color", background);
svg.call(drawLayer, [root]);
function drawLayer(selection, squares) {
var layer = selection.selectAll("g")
.data(function(d) {
// Use specific data if it was provided; otherwise use children
return squares || d.children;
});
layer.enter()
.append("g")
.on("mouseover", function(d) {
// Only split groups that haven't been split yet
if (!d.children) {
d3.select(this).call(split);
}
})
.append("rect")
.call(layout);
// Resize any groups that were just split
selection.select("rect")
.filter(function(d) {
return !d.resized && d.children;
})
.each(resize)
.on("mouseover", null)
.transition().duration(tick)
.call(layout)
.call(endAll, function() {
// Check to see if we should automatically generate the next depth
layer
.filter(function(d) {
return !d.children && d.depth < startingDepth;
})
.call(split);
});
}
function layout(selection) {
selection.attr("height", function(d) {
return d.size;
})
.attr("width", function(d) {
return d.size;
})
.attr("transform", function(d) {
return `translate(${d.x}, ${d.y})`;
})
.attr("fill", function(d) {
return d.children ? colors(d.depth) : background;
});
}
// These are the "holes" in the Sierpinski carpet
function resize(square) {
square.resized = true;
square.size /= 3;
square.x = square.x + square.size;
square.y = square.y + square.size;
}
function split(selection) {
selection.each(splitSquare)
.call(function(s) {
if (!s.empty()) {
setTimeout(drawLayer.bind(null, s));
}
});
}
function splitSquare(square) {
if (square.children) {
return square.children;
}
square.children = [];
var size = square.size / 3;
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
continue;
}
square.children.push({
x: square.x + size * i,
y: square.y + size * j,
size: size,
depth: square.depth + 1
});
}
}
return square.children;
}
// Wait until all transitions are done then use callback
function endAll(transition, callback) {
var n;
if (transition.empty()) {
callback();
} else {
n = transition.size();
transition.each("end", function() {
n--;
if (n === 0) {
callback();
}
});
}
}
body {
margin: 0;
padding: 0;
background-color: #333;
}
#sierpinski-carpet-chart {
cursor: crosshair;
display: block;
margin: auto;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment