block by nitaku 8947565

Unstable Hilbert curve

Full Screen

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.

index.js

/* 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);

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Unstable Hilbert curve</title>
        <link type="text/css" href="index.css" rel="stylesheet"/>
        <script src="//d3js.org/d3.v3.min.js"></script>
        
    </head>
    <body></body>
    <script src="index.js"></script>
</html>

index.coffee

### 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)
            

index.css

.curve {
  fill: none;
  stroke: black;
  stroke-width: 1.5px;
}

svg {
  cursor: pointer;
}

index.sass

.curve
    fill: none
    stroke: black
    stroke-width: 1.5px
    
svg
    cursor: pointer