Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nitaku/8947871 to your computer and use it in GitHub Desktop.
Save nitaku/8947871 to your computer and use it in GitHub Desktop.
Stable Hilbert curve

A stabilized version of a Hilbert curve, more suitable for Hilbert treemaps. Odd orders are rotated by 90 degrees and flipped along Y, making each curve overlappable to each other (click the canvas to see it).

Classical Hilbert curves of even order cannot be overlapped with Hilbert curves of odd order (see this example showing it). Peano curves are not affected by this problem (see here).

### compute a Lindenmayer system given an axiom, a number of steps and rules ###
fractalize = (config) ->
input = config.axiom
for i in [0...config.steps]
output = ''
for char in input
if char of config.rules
output += config.rules[char]
else
output += char
input = output
return output
### convert a Lindenmayer string into an SVG path string ###
svg_path = (config) ->
angle = 0.0
path = 'M0 0'
for char in config.fractal
if char == '+'
angle += config.angle
else if char == '-'
angle -= config.angle
else if char == 'F'
path += "l#{config.side * Math.cos(angle)} #{config.side * Math.sin(angle)}"
return path
side = 6
curves = []
for steps in [1..6]
fractal = fractalize
axiom: 'A'
steps: steps
rules:
A: '-BF+AFA+FB-'
B: '+AF-BFB-FA+'
curves.push svg_path
fractal: fractal
side: side
angle: Math.PI/2
width = 960
height = 500
svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
svg.selectAll('.curve')
.data(curves)
.enter().append('path')
.attr('class', 'curve')
.attr('d', (d)->d)
.attr('transform', (d,i)->"translate(#{70 + (Math.pow(2,i+1)+i)*side},430) #{if i%2 then 'rotate(90)scale(-1,1)' else ''}")
.attr('opacity', 1)
collapse = false
svg.on 'click', () ->
collapse = not collapse
if collapse
svg.selectAll('.curve').transition().duration(1000)
.attr('transform', (d,i)->"translate(300,430) #{if i%2 then 'rotate(90)scale(-1,1)' else ''}")
.attr('opacity', 0.2)
else
svg.selectAll('.curve').transition().duration(1000)
.attr('transform', (d,i)->"translate(#{70 + (Math.pow(2,i+1)+i)*side},430) #{if i%2 then 'rotate(90)scale(-1,1)' else ''}")
.attr('opacity', 1)
.curve {
fill: none;
stroke: black;
stroke-width: 1.5px;
}
svg {
cursor: pointer;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Stable Hilbert curve</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body></body>
<script src="index.js"></script>
</html>
/* compute a Lindenmayer system given an axiom, a number of steps and rules
*/
(function() {
var collapse, curves, fractal, fractalize, height, side, steps, svg, svg_path, width;
fractalize = function(config) {
var char, i, input, output, _i, _len, _ref;
input = config.axiom;
for (i = 0, _ref = config.steps; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) {
output = '';
for (_i = 0, _len = input.length; _i < _len; _i++) {
char = input[_i];
if (char in config.rules) {
output += config.rules[char];
} else {
output += char;
}
}
input = output;
}
return output;
};
/* convert a Lindenmayer string into an SVG path string
*/
svg_path = function(config) {
var angle, char, path, _i, _len, _ref;
angle = 0.0;
path = 'M0 0';
_ref = config.fractal;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
char = _ref[_i];
if (char === '+') {
angle += config.angle;
} else if (char === '-') {
angle -= config.angle;
} else if (char === 'F') {
path += "l" + (config.side * Math.cos(angle)) + " " + (config.side * Math.sin(angle));
}
}
return path;
};
side = 6;
curves = [];
for (steps = 1; steps <= 6; steps++) {
fractal = fractalize({
axiom: 'A',
steps: steps,
rules: {
A: '-BF+AFA+FB-',
B: '+AF-BFB-FA+'
}
});
curves.push(svg_path({
fractal: fractal,
side: side,
angle: Math.PI / 2
}));
}
width = 960;
height = 500;
svg = d3.select('body').append('svg').attr('width', width).attr('height', height);
svg.selectAll('.curve').data(curves).enter().append('path').attr('class', 'curve').attr('d', function(d) {
return d;
}).attr('transform', function(d, i) {
return "translate(" + (70 + (Math.pow(2, i + 1) + i) * side) + ",430) " + (i % 2 ? 'rotate(90)scale(-1,1)' : '');
}).attr('opacity', 1);
collapse = false;
svg.on('click', function() {
collapse = !collapse;
if (collapse) {
return svg.selectAll('.curve').transition().duration(1000).attr('transform', function(d, i) {
return "translate(300,430) " + (i % 2 ? 'rotate(90)scale(-1,1)' : '');
}).attr('opacity', 0.2);
} else {
return svg.selectAll('.curve').transition().duration(1000).attr('transform', function(d, i) {
return "translate(" + (70 + (Math.pow(2, i + 1) + i) * side) + ",430) " + (i % 2 ? 'rotate(90)scale(-1,1)' : '');
}).attr('opacity', 1);
}
});
}).call(this);
.curve
fill: none
stroke: black
stroke-width: 1.5px
svg
cursor: pointer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment