block by nitaku 6173659

Hex coordinates

Full Screen

Forked from Mike Bostock’s example.

For each hex, its hexagonal coordinates are drawn.

This kind of coordinate system makes it possible to interpret the position vectors of the six hexes around the origin as direction vectors. Therefore, it is possible to sum one of them to the position vector of an hex to “move” to an adjacent one. These are the values for the six directions:

E   (+1,-1,0)
SE  (+1,0,-1)
SW  (0,+1,-1)
W   (-1,+1,0)
NW  (-1,0,+1)
NE  (0,-1,+1)

Possible use cases are: computing the shortest path on a connectivity graph based on the hexagonal tiling (Stojmenovic 2007), walking on the hexagonal cells by following a space-filling curve.

Two example hexagons are also highlighted, to show the possibility of addressing an hexagon by using its coordinates.

index.js

(function() {
  var global, hexProjection, hexTopology, register;

  global = {
    registry: {}
  };

  register = function(hexes) {
    return hexes.each(function(d) {
      if (!(d.x in global.registry)) global.registry[d.x] = {};
      if (!(d.y in global.registry[d.x])) global.registry[d.x][d.y] = {};
      if (!(d.z in global.registry[d.x][d.y])) {
        return global.registry[d.x][d.y][d.z] = this;
      }
    });
  };

  window.main = function() {
    var coord_format, height, new_label, radius, svg, width;
    width = 960;
    height = 500;
    radius = 40;
    coord_format = d3.format(' 03d');
    global.hex_topology = hexTopology(radius, width, height);
    global.path_generator = d3.geo.path().projection(hexProjection(radius));
    svg = d3.select('body').append('svg').attr('width', width).attr('height', height);
    /* draw the axes
    */
    svg.append('line').attr('class', 'x axis').attr('x1', 450).attr('y1', 300).attr('x2', 550).attr('y2', 357);
    svg.append('line').attr('class', 'y axis').attr('x1', 450).attr('y1', 300).attr('x2', 350).attr('y2', 357);
    svg.append('line').attr('class', 'z axis').attr('x1', 450).attr('y1', 300).attr('x2', 450).attr('y2', 177);
    /* draw the hexagons
    */
    svg.append('g').attr('class', 'hexagon').selectAll('path').data(global.hex_topology.objects.hexagons.geometries).enter().append('path').attr('d', function(d) {
      return global.path_generator(topojson.feature(global.hex_topology, d));
    }).style('stroke', function(d) {
      if (d.x === 0 && d.y === 0 && d.z === 0) {
        return 'black';
      } else {
        return 'none';
      }
    }).call(register);
    /* draw the coordinates
    */
    new_label = svg.append('g').attr('class', 'label').selectAll('text').data(global.hex_topology.objects.hexagons.geometries).enter().append('text').attr('transform', function(d) {
      return "translate(" + (global.path_generator.centroid(topojson.feature(global.hex_topology, d))) + ")";
    });
    new_label.append('tspan').text(function(d) {
      return coord_format(d.x);
    }).attr('x', '0.6em').attr('y', '-1em');
    new_label.append('tspan').text(function(d) {
      return coord_format(d.y);
    }).attr('x', '0.6em').attr('y', '.35em');
    new_label.append('tspan').text(function(d) {
      return coord_format(d.z);
    }).attr('x', '0.6em').attr('y', '1.65em');
    svg.append('path').datum(topojson.mesh(global.hex_topology, global.hex_topology.objects.hexagons)).attr('class', 'mesh').attr('d', global.path_generator);
    /* select a few example hexagons by using their coordinates
    */
    d3.select(global.registry[-4][2][2]).attr('fill', 'yellow');
    return d3.select(global.registry[1][-2][1]).attr('fill', 'yellow');
  };

  /* create the hex mesh TopoJSON
  */

  hexTopology = function(radius, width, height) {
    var arcs, dx, dy, geometries, i, j, m, n, q, x, y;
    dx = radius * 2 * Math.sin(Math.PI / 3);
    dy = radius * 1.5;
    m = Math.ceil((height + radius) / dy) + 1;
    n = Math.ceil(width / dx) + 1;
    geometries = [];
    arcs = [];
    for (j = -1; -1 <= m ? j <= m : j >= m; -1 <= m ? j++ : j--) {
      for (i = -1; -1 <= n ? i <= n : i >= n; -1 <= n ? i++ : i--) {
        y = j * 2;
        x = (i + (j & 1) / 2) * 2;
        arcs.push([[x, y - 1], [1, 1]], [[x + 1, y], [0, 1]], [[x + 1, y + 1], [-1, 1]]);
      }
    }
    q = 3;
    for (j = 0; 0 <= m ? j < m : j > m; 0 <= m ? j++ : j--) {
      for (i = 0; 0 <= n ? i < n : i > n; 0 <= n ? i++ : i--) {
        geometries.push({
          type: 'Polygon',
          arcs: [[q, q + 1, q + 2, ~(q + (n + 2 - (j & 1)) * 3), ~(q - 2), ~(q - (n + 2 + (j & 1)) * 3 + 2)]],
          x: i - 6 + Math.floor((j - 6) / 2),
          y: -(i - 6) + Math.ceil((j - 6) / 2),
          z: Math.floor(-j + 6)
        });
        q += 3;
      }
      q += 6;
    }
    return {
      transform: {
        translate: [0, 0],
        scale: [1, 1]
      },
      objects: {
        hexagons: {
          type: 'GeometryCollection',
          geometries: geometries
        }
      },
      arcs: arcs
    };
  };

  /* define a custom projection to make hexagons appear regular
  */

  hexProjection = function(radius) {
    var dx, dy;
    dx = radius * 2 * Math.sin(Math.PI / 3);
    dy = radius * 1.5;
    return {
      stream: function(stream) {
        return {
          point: (function(x, y) {
            return stream.point(x * dx / 2, (y - (2 - (y & 1)) / 3) * dy / 2);
          }),
          lineStart: (function() {
            return stream.lineStart();
          }),
          lineEnd: (function() {
            return stream.lineEnd();
          }),
          polygonStart: (function() {
            return stream.polygonStart();
          }),
          polygonEnd: (function() {
            return stream.polygonEnd();
          })
        };
      }
    };
  };

}).call(this);

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Hex coordinates</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 = {
    registry: {}
}

register = (hexes) ->
    hexes.each (d) ->
        if d.x not of global.registry
            global.registry[d.x] = {}
            
        if d.y not of global.registry[d.x]
            global.registry[d.x][d.y] = {}
            
        if d.z not of global.registry[d.x][d.y]
            global.registry[d.x][d.y][d.z] = this
            
window.main = () ->
    width = 960
    height = 500
    radius = 40
    
    coord_format = d3.format(' 03d')
    
    global.hex_topology = hexTopology(radius, width, height)
    global.path_generator = d3.geo.path()
        .projection(hexProjection(radius))
        
    svg = d3.select('body').append('svg')
        .attr('width', width)
        .attr('height', height)
        
    ### draw the axes ###
    svg.append('line')
        .attr('class', 'x axis')
        .attr('x1', 450)
        .attr('y1', 300)
        .attr('x2', 550)
        .attr('y2', 357)
        
    svg.append('line')
        .attr('class', 'y axis')
        .attr('x1', 450)
        .attr('y1', 300)
        .attr('x2', 350)
        .attr('y2', 357)
        
    svg.append('line')
        .attr('class', 'z axis')
        .attr('x1', 450)
        .attr('y1', 300)
        .attr('x2', 450)
        .attr('y2', 177)
        
    ### draw the hexagons ###
    svg.append('g')
        .attr('class', 'hexagon')
      .selectAll('path')
        .data(global.hex_topology.objects.hexagons.geometries)
      .enter().append('path')
        .attr('d', (d) -> global.path_generator(topojson.feature(global.hex_topology, d)) )
        .style('stroke', (d) -> if d.x is 0 and d.y is 0 and d.z is 0 then 'black' else 'none')
        .call(register)
        
    ### draw the coordinates ###
    new_label = svg.append('g')
        .attr('class', 'label')
      .selectAll('text')
        .data(global.hex_topology.objects.hexagons.geometries)
      .enter().append('text')
        .attr('transform', (d) -> "translate(#{global.path_generator.centroid(topojson.feature(global.hex_topology, d))})")
    
    new_label.append('tspan')
        .text((d) -> coord_format(d.x))
        .attr('x', '0.6em')
        .attr('y', '-1em')
        
    new_label.append('tspan')
        .text((d) -> coord_format(d.y))
        .attr('x', '0.6em')
        .attr('y', '.35em')
        
    new_label.append('tspan')
        .text((d) -> coord_format(d.z))
        .attr('x', '0.6em')
        .attr('y', '1.65em')
        
    svg.append('path')
        .datum(topojson.mesh(global.hex_topology, global.hex_topology.objects.hexagons))
        .attr('class', 'mesh')
        .attr('d', global.path_generator)
        
    ### select a few example hexagons by using their coordinates ###
    d3.select(global.registry[-4][2][2]).attr('fill', 'yellow')
    d3.select(global.registry[1][-2][1]).attr('fill', 'yellow')
    
### create the hex mesh TopoJSON ###
hexTopology = (radius, width, height) ->
    dx = radius * 2 * Math.sin(Math.PI / 3)
    dy = radius * 1.5
    m = Math.ceil((height + radius) / dy) + 1
    n = Math.ceil(width / dx) + 1
    geometries = []
    arcs = []
    
    for j in [-1..m]
        for i in [-1..n]
            y = j * 2
            x = (i + (j & 1) / 2) * 2
            arcs.push([[x, y - 1], [1, 1]], [[x + 1, y], [0, 1]], [[x + 1, y + 1], [-1, 1]])
            
    q = 3
    for j in [0...m]
        for i in [0...n]
            geometries.push({
                type: 'Polygon',
                arcs: [[q, q + 1, q + 2, ~(q + (n + 2 - (j & 1)) * 3), ~(q - 2), ~(q - (n + 2 + (j & 1)) * 3 + 2)]],
                x: i-6+Math.floor((j-6)/2),
                y: -(i-6)+Math.ceil((j-6)/2),
                z: Math.floor(-j+6)
            })
            q += 3
        q += 6
        
    return {
        transform: {translate: [0, 0], scale: [1, 1]},
        objects: {hexagons: {type: 'GeometryCollection', geometries: geometries}},
        arcs: arcs
    }
    
### define a custom projection to make hexagons appear regular ###
hexProjection = (radius) ->
    dx = radius * 2 * Math.sin(Math.PI / 3)
    dy = radius * 1.5
    
    return {
        stream: (stream) -> {
            point: ((x, y) -> stream.point(x * dx / 2, (y - (2 - (y & 1)) / 3) * dy / 2) ),
            lineStart: (() -> stream.lineStart() ),
            lineEnd: (() -> stream.lineEnd() ),
            polygonStart: (() -> stream.polygonStart() ),
            polygonEnd: (() -> stream.polygonEnd() )
        }
    }
    

index.css

.hexagon {
  fill: none;
  pointer-events: all;
  stroke-width: 2;
}

.label text {
  font-family: sans-serif;
  font-size: 9pt;
  font-weight: bold;
  text-anchor: end;
}
.label text *:nth-child(1) {
  fill: red;
}
.label text *:nth-child(2) {
  fill: green;
}
.label text *:nth-child(3) {
  fill: blue;
}

.mesh {
  fill: none;
  stroke: black;
  stroke-width: 2;
  stroke-opacity: 0.2;
  pointer-events: none;
}

.axis {
  stroke-width: 4;
  opacity: 0.5;
}

.x {
  stroke: red;
}

.y {
  stroke: green;
}

.z {
  stroke: blue;
}

index.sass

.hexagon
    fill: none
    pointer-events: all
    stroke-width: 2
    
.label text
    font-family: sans-serif
    font-size: 9pt
    font-weight: bold
    text-anchor: end
    
    *:nth-child(1)
        fill: red
    *:nth-child(2)
        fill: green
    *:nth-child(3)
        fill: blue
        
.mesh
    fill: none
    stroke: black
    stroke-width: 2
    stroke-opacity: .2
    pointer-events: none
    
.axis
    stroke-width: 4
    opacity: 0.5
    
.x
    stroke: red
.y
    stroke: green
.z
    stroke: blue