Search code examples
javascripthtmlmathhtml5-canvasgrid

Draw grid with hexagons using canvas Html & JavaScript


I try to create a canvas with X * X of size, using hexagon shapes, currently I base on this reference, with some adaptations, my code until now look like this:

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

const shapeType = 6;
const angle = 2 * Math.PI / shapeType;
const radius = 20;

function init() {
    drawGrid(5);
}

init();

function drawGrid(size) {
    for (let y = radius, i = 0; i < size; i++) {
        y += radius * Math.sin(angle);
        for (let x = radius, j = 0;
             j < size;
             x += radius * (1 + Math.cos(angle)), y += (-1) ** j++ * radius * Math.sin(angle)) {
            drawHexagon(x, y);
        }
    }
}

function drawHexagon(x, y) {
    context.beginPath();
    for (let i = 0; i < shapeType; i++) {
        let xx = x + radius * Math.cos(angle * i);
        let yy = y + radius * Math.sin(angle * i);
        context.lineTo(xx, yy);
    }
    context.closePath();
    context.stroke();
}
<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HexGrid</title>
    <style>
        .test {
            display: flex;
            justify-content: space-between;
        }

        .canvas {
            border: 1px solid #000000;
        }
    </style>
</head>
<body>
<canvas id="canvas" width="500" height="500" class="canvas"></canvas>
<script src="main.js"></script>
</body>
</html>

Until now, every thing is good, but just with odd size, but if the size is even, I get this:

even grid

As a beginner in JavaScript, I'm blocked in this, any one have any idea how can I solve this please?


Solution

  • Regular Hexagon Grid

    A grid of hexagons has a regular spacing just like a grid of squares however every 2nd column if offset (down in this case) by half the hexagons height.

    To get the x and y grid spacing, and the offset is half the y grid spacing.

        RADIUS = 20;
        EDGE_LEN = Math.sin(Math.PI / 6) * RADIUS * 2;
        GRID_Y_SPACE = Math.cos(Math.PI / 6) * RADIUS * 2;
        GRID_X_SPACE = RADIUS * 2 - EDGE_LEN * 0.5;
        GRID_Y_OFFSET = GRID_Y_SPACE * 0.5;
    

    The hexagon's position can be calculated using (gx, gy are column, row positions)

        function gridToPixel(gx, gy, p = {}) {
            p.x = gx * GRID_X_SPACE;
            p.y = gy * GRID_Y_SPACE + (gx % 2 ? GRID_Y_OFFSET : 0);        
            return p;
        }
    

    Example

    Example fills canvas with hexagon grid.

    const ctx = canvas.getContext('2d');
    
    const P2 = (x, y) => ({x,y});
    const EDGES = 6;
    const RADIUS = 20;
    const TAU = 2 * Math.PI;
    const EDGE_LEN = Math.sin(Math.PI / EDGES) * RADIUS * 2;
    const GRID_Y_SPACE = Math.cos(Math.PI / EDGES) * RADIUS * 2;
    const GRID_X_SPACE = RADIUS * 2 - EDGE_LEN * 0.5;
    const GRID_Y_OFFSET = GRID_Y_SPACE * 0.5;
    const COLS = "=#3c2f18,#01335f,#3f0e77,#204a73,#511d94,#fe1f00,#0060fd,#fe7603,#f0ca1d,#b085e8,#e9cafa".split(",");
    const rndItem = arr => arr[Math.random() * arr.length | 0];
    
    drawGrid(1, 1, 15, 13, createPoly(EDGES));
    function drawGrid(x, y, w, h, points) {
      const p = P2();
      var gy, gx;
      for (gy = y; gy < y + h; gy++) {
          for (gx = x; gx < x + w; gx++) {
              ctx.fillStyle = rndItem(COLS);
              drawPoly(gridToPixel(gx, gy, p), points);
          }
      }
    }
    function gridToPixel(gx, gy, p = {}) {
        p.x = gx * GRID_X_SPACE;
        p.y = gy * GRID_Y_SPACE + (gx % 2 ? GRID_Y_OFFSET : 0);       
        return p;
    }
    function drawPoly(p, points) { // p.x, p.y is center
        ctx.setTransform(1, 0, 0, 1, p.x, p.y);
        var i = 0;
        ctx.beginPath();
        while (i < points.length) {
            const p2 = points[i++];
            ctx.lineTo(p2.x, p2.y);
        }
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
    }
    function createPoly(sides, points = []) {
        const step = TAU / sides;
        var ang = 0, i = sides;
        while (i--) {
            points.push(P2(RADIUS * Math.cos(ang), RADIUS * Math.sin(ang)));
            ang += step;
        }
        return points;
    }
    canvas { border: 1px solid #000000; }
    <canvas id="canvas" width="500" height="500"></canvas>