block by nitaku 7039746

Simplified Gosper regions

Full Screen

A test for Visvalingam simplification applied to Gosper regions. Mouseover to control the simplification.

The code uses the TopoJSON API as in this Mike Bostock’s example to obtain a topology-preserving simplification (regions are simplified together). If regions were simplified one by one, artifacts (holes and overlaps) would have appeared along the common boundaries.

index.js

(function() {
  var global, mousemoved;

  global = {
    area: 1
  };

  /* whenever the mouse moves, compute a new area parameter for the simplification, then draw again the regions
  */

  mousemoved = function() {
    global.area = global.mouse_scale.invert(d3.mouse(this)[0]);
    return global.svg.selectAll('.region, .boundary').attr('d', global.path_generator);
  };

  window.main = function() {
    var colorify, dx, dy, height, radius, vis, width;
    width = 960;
    height = 500;
    /* prepare a scale for the mouse input
    */
    global.mouse_scale = d3.scale.sqrt().domain([0, 1e4]).range([0, width]);
    global.svg = d3.select('body').append('svg').attr('width', width).attr('height', height).on('mousemove', mousemoved);
    vis = global.svg.append('g').attr('transform', 'translate(640, 120)');
    /* custom projection to make hexagons appear regular (y axis is also flipped)
    */
    /* it also applies the Visvalingam simplification
    */
    radius = 0.6;
    dx = radius * 2 * Math.sin(Math.PI / 3);
    dy = radius * 1.5;
    global.path_generator = d3.geo.path().projection(d3.geo.transform({
      point: function(x, y, z) {
        /* draw only nodes that are "important" enough
        */        if (z >= global.area) {
          return this.stream.point(x * dx / 2, -(y - (2 - (y & 1)) / 3) * dy / 2);
        }
      }
    }));
    /* define a color scale (Colorbrewer's Set3)
    */
    colorify = d3.scale.ordinal().domain(['A', 'B', 'C', 'D']).range(["#8dd3c7", "#ffffb3", "#bebada", "#fb8072"]);
    /* load topoJSON data
    */
    return d3.json('readme.regions.topo.json', function(error, data) {
      /* presimplify the topology (compute the effective area (z) of each point)
      */      topojson.presimplify(data);
      /* draw the cells
      */
      vis.selectAll('.region').data(topojson.feature(data, data.objects.regions).features).enter().append('path').attr('class', 'region').attr('d', global.path_generator).attr('fill', function(d) {
        return colorify(d.properties['class']);
      });
      /* draw the boundaries
      */
      vis.append('path').datum(topojson.mesh(data, data.objects.regions, function(a, b) {
        return a === b;
      })).attr('d', global.path_generator).attr('class', 'outer boundary');
      return vis.append('path').datum(topojson.mesh(data, data.objects.regions, function(a, b) {
        return a !== b;
      })).attr('d', global.path_generator).attr('class', 'boundary');
    });
  };

}).call(this);

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Simplified Gosper Regions</title>
        <link type="text/css" href="index.css" rel="stylesheet"/>
        <script src="//d3js.org/d3.v3.min.js"></script>
        <script src="//d3js.org/topojson.v1.min.js"></script>
        <script src="index.js"></script>
    </head>
    <body onload="main()"></body>
</html>

index.coffee

global = {
    area: 1
}

### whenever the mouse moves, compute a new area parameter for the simplification, then draw again the regions ###
mousemoved = () ->
    global.area = global.mouse_scale.invert(d3.mouse(this)[0])
    global.svg.selectAll('.region, .boundary').attr('d', global.path_generator)
    
window.main = () ->
    width = 960
    height = 500
    
    ### prepare a scale for the mouse input ###
    global.mouse_scale = d3.scale.sqrt()
        .domain([0, 1e4])
        .range([0, width])
        
    global.svg = d3.select('body').append('svg')
        .attr('width', width)
        .attr('height', height)
        .on('mousemove', mousemoved)
        
    vis = global.svg.append('g')
        .attr('transform','translate(640, 120)')
        
    ### custom projection to make hexagons appear regular (y axis is also flipped) ###
    ### it also applies the Visvalingam simplification ###
    radius = 0.6
    dx = radius * 2 * Math.sin(Math.PI / 3)
    dy = radius * 1.5
    
    global.path_generator = d3.geo.path()
        .projection d3.geo.transform({
            point: (x,y,z) ->
                ### draw only nodes that are "important" enough ###
                if z >= global.area
                    this.stream.point(x * dx / 2, -(y - (2 - (y & 1)) / 3) * dy / 2)
        })
        
    ### define a color scale (Colorbrewer's Set3) ###
    colorify = d3.scale.ordinal()
        .domain(['A','B','C','D'])
        .range(["#8dd3c7","#ffffb3","#bebada","#fb8072"])
        
    ### load topoJSON data ###
    d3.json 'readme.regions.topo.json', (error, data) ->
        ### presimplify the topology (compute the effective area (z) of each point) ###
        topojson.presimplify(data)
        
        ### draw the cells ###
        vis.selectAll('.region')
            .data(topojson.feature(data, data.objects.regions).features)
          .enter().append('path')
            .attr('class', 'region')
            .attr('d', global.path_generator)
            .attr('fill', (d) -> colorify(d.properties['class']))
            
        ### draw the boundaries ###
        vis.append('path')
            .datum(topojson.mesh(data, data.objects.regions, (a, b) -> a is b))
            .attr('d', global.path_generator)
            .attr('class', 'outer boundary')
            
        vis.append('path')
            .datum(topojson.mesh(data, data.objects.regions, (a, b) -> a isnt b))
            .attr('d', global.path_generator)
            .attr('class', 'boundary')
            

index.css

.boundary {
  stroke: #333333;
  stroke-width: 1px;
  fill: none;
}
.boundary.outer {
  stroke-width: 1.5px;
}

index.sass

.boundary
    stroke: #333
    stroke-width: 1px
    fill: none
    
    &.outer
        stroke-width: 1.5px