Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active December 25, 2015 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nitaku/7010320 to your computer and use it in GitHub Desktop.
Save nitaku/7010320 to your computer and use it in GitHub Desktop.
Hex coordinates (rectangular)

For each hexagon in the tiling, draw its rectangular coordinates. The Y coordinate identifies the row, while the X identifies a sort of zig-zag column.

This type of coordinate system is widely used to address hexagons in a tiling, but has a drawback: It is not possible to interpret the coordinates of the hexes around the origin as unit vectors, therefore it is not possible in general to sum them to an hex position vector to obtain another one.

Compare with another take on hexagonal coordinates, which solves the problem.

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

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] = 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', 570)
.attr('y2', 300)
svg.append('line')
.attr('class', 'y axis')
.attr('x1', 450)
.attr('y1', 300)
.attr('x2', 450)
.attr('y2', 180)
### 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 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', '-0.5em')
new_label.append('tspan')
.text((d) -> coord_format(d.y))
.attr('x', '0.6em')
.attr('y', '1em')
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]).attr('fill', 'yellow')
d3.select(global.registry[1][-2]).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,
y: -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() )
}
}
.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;
}
.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;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hex coordinates (rectangular)</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="index.js"></script>
</head>
<body onload="main()"></body>
</html>
(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])) return global.registry[d.x][d.y] = 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', 570).attr('y2', 300);
svg.append('line').attr('class', 'y axis').attr('x1', 450).attr('y1', 300).attr('x2', 450).attr('y2', 180);
/* 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) {
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', '-0.5em');
new_label.append('tspan').text(function(d) {
return coord_format(d.y);
}).attr('x', '0.6em').attr('y', '1em');
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]).attr('fill', 'yellow');
return d3.select(global.registry[1][-2]).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,
y: -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);
.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
.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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment