Honeycomb: hexagon grids in JavaScript
There are plenty of JavaScript libraries offering the tools needed for rendering hexagon grids. But none (that I know of) where you can choose how the grid is rendered, or not render at all (on the server). Most libraries I could find came with canvas rendering built in or had mediocre APIs.
So, yearning for a new hobby project and some time to spare I started Honeycomb.
Basic usage
After installation (npm i -S honeycomb-grid
or yarn add honeycomb-grid
) you can have a grid in 2 steps:
// 1. create a Grid factory that uses the default Hex factory:
const Grid = Honeycomb.defineGrid()// 2a. create a rectangular grid:
Grid.rectangle({ width: 4, height: 4 })
// [
// { x: 0, y: 0 },
// { x: 0, y: 1 },
// { x: 0, y: 2 },
// …
// ]// 2b. or create a grid from individual hexes:
const Hex = Grid.Hex
Grid(Hex(1, 2), Hex(3, 4))
// [
// { x: 1, y: 2 },
// { x: 3, y: 4 }
// ]
There’s an optional (but very likely) step 0: creating a custom Hex factory:
// 0. create a Hex factory by extending the default:
const Hex = Honeycomb.extendHex({
size: 30, // default: 1
orientation: 'flat' // default: 'pointy'
})// 1. create a Grid factory that uses the Hex factory:
const Grid = Honeycomb.defineGrid(Hex)
Rendering
Honeycomb doesn’t care how a grid is rendered. In fact, it’s so indifferent that it’s completely void of functionality to show hexes on a screen. Fortunately, it isn’t very hard. Especially if you use a dedicated rendering library.
Here’s an example of rendering 10,000 hexes with WebGL using PixiJS:
If you prefer SVGs (like I do), here’s how you can achieve the same with SVG.js:
Grids extend Array.prototype
Most properties/methods of grids are the same as their Array
counterpart:
const grid = Grid.rectangle({ width: 4, height: 4 })grid.length // 16
grid.pop() // { x: 3, y: 3 }
grid.length // 15
grid[4] // { x: 1, y: 0 }
Some Grid methods are augmented. For example: Array#includes
always returns false
when passed an object literal because it uses strict equality internally. Grid#includes
only accepts object literals (in the form of points):
const array = [{ x: 1, y: 0 }]
array.includes({ x: 1, y: 0 }) // falseconst grid = Grid(Hex(1, 0))
grid.includes({ x: 1, y: 0 }) // true
Grid methods that mutate
Methods that mutate the grid in-place (Grid#push
, Grid#splice
and Grid#unshift
) only accept valid hexes to prevent “grid corruption” 👮.
const grid = Grid() // []// this silently fails:
grid.push('invalid hex') // 0 <- the grid's length, which remains 0
grid.includes('invalid hex') // false
Keep in mind that methods that return a new grid (e.g. Grid#map
) can create grids with invalid hexes:
const grid = Grid.rectangle({ width: 4, height: 4 })const newGrid = grid.map(hex => 'invalid hex')
// [
// 'invalid hex',
// 'invalid hex',
// 'invalid hex',
// …
// ]
Be careful with bracket notation
It’s possible to add an invalid hex to a grid by using bracket notation:
const grid = Grid(Hex())grid[0] // { x: 0, y: 0 }
grid[0] = 'invalid hex'
grid[0] // 'invalid hex' ⚠️
Use Grid#get
and Grid#set
instead:
const grid = Grid(Hex())grid.get(0) // { x: 0, y: 0 }
grid.set(0, 'invalid hex')
grid.get(0) // { x: 0, y: 0 } <- invalid hex is ignored// Grid#set() also accepts a point:
grid.set({ x: 0, y: 0 }, Hex(-1, 3))
// …as does Grid#get():
grid.get([-1, 3]) // { x: -1, y: 3 }
Point → Hex
Translating a point (pixel) in the grid to the corresponding hex is possible with Grid.pointToHex
:
Grid shapes
Honeycomb offers 4 shape methods: rectangle, triangle, hexagon and parallelogram:
Coordinate systems
The standard coordinate system is a cartesian one. It’s intuitive and easy to reason about. A lot of methods internally use a “cube” coordinate system. See this redblobgames.com blog post for an explanation between the two (he calls the cartesian system “offset coordinates”).
Hexes have getters for each of the cube coordinates q
, r
and s
:
const Hex = Honeycomb.extendHex()
const hex = Hex(3, 4)hex.q // 1
hex.r // 4
hex.s // -5hex.cartesian() // { x: 3, y: 4 }
hex.cube() // { q: 1, r: 4, s: -5 }
There are methods for converting between cartesian and cube:
const Hex = Honeycomb.extendHex()
const hex = Hex()hex.toCube({ x: 3, y: 4 }) // { q: 1, r: 4, s: -5 }// Hex#toCartesian doesn't require the s coordinate:
hex.toCartesian({ q: 1, r: 4 }) // { x: 3, y: 4 }
These methods always require coordinates to be passed and don’t work on a hex instance, even though they’re instance methods. This will be fixed in a future release 🙃
The full API documentation is available on Github.
While adding examples to the docs I’ve gained a lot of insights that I hope to apply in the coming months (a full-time job and a 1-year-old aren’t beneficial to a hobby project).
Let me know what you think about Honeycomb by leaving a comment or opening a Github issue if you have any questions or ideas.