Skip to content

Instantly share code, notes, and snippets.

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

Hilbert curves of even order cannot be overlapped with Hilbert curves of odd order (click the canvas to see it). This makes them somewhat problematic for Hilbert treemaps like the ones we showed in this and this examples.

According to our data cartography methodology, if a dataset is slightly changed, only a slight change should be reflected by the map. But, if we use Hilbert layouts, we can observe a disastrous change on an already full map if a single cell is added to it: the map will be flipped!

Peano curves are not affected by this problem (see here). A solution for fixing Hilbert curves is presented in this example.

### 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},440)")
.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,440)')
.attr('opacity', 0.2)
else
svg.selectAll('.curve').transition().duration(1000)
.attr('transform', (d,i)->"translate(#{70 + (Math.pow(2,i+1)+i)*side},440)")
.attr('opacity', 1)
.curve {
fill: none;
stroke: black;
stroke-width: 1.5px;
}
svg {
cursor: pointer;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Unstable 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) + ",440)";
}).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,440)';
}).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) + ",440)";
}).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