Procedural Grids: Square, Triangle, and Hexagon Patterns in Pathogen

Grid patterns show up everywhere — engineering overlays, graph paper, game boards, architectural plans, generative art backgrounds. Building one from scratch means nested loops, coordinate math, and careful edge deduplication for triangles and hexagons. Pathogen's three new grid functions collapse all of that into a single call.

squareGrid, triangleGrid, and hexagonGrid each generate complete path geometry within a bounding rectangle. A GridPatternType enum selects the visual style — Shape, Dot, Intersection, or Partial — and all three return PathSegment values, so they compose with layers, transforms, and clip paths just like circle() or polygon().

Square Grids

squareGrid(type, x, y, width, height, cellSize)
Parameter Description
type GridPatternType.Shape, .Dot, .Intersection, or .Partial
x, y Top-left origin
width, height Bounding dimensions
cellSize Side length of each square cell

The grid fills as many complete cells as fit: floor(width / cellSize) columns and floor(height / cellSize) rows. Extra space is ignored.

// viewBox="0 0 400 400" // Square grid — all four pattern types let gridColor = Color(CSSVar('--grid-color', #4488ff)); let bgColor = Color(CSSVar('--bg-color', #0f172a)); let cellSize = 14; let gridW = 160; let gridH = 150; let pad = 20; // Background let bg = PathLayer('bg') ${ fill: bgColor; stroke: none; }; bg.apply { rect(0, 0, 400, 400) } // Grid layers — each pattern in a different hue let shapeColor = gridColor; let dotColor = gridColor.hueShift(90); let intColor = gridColor.hueShift(180); let partColor = gridColor.hueShift(270); let shapeLayer = PathLayer('shape') ${ stroke: shapeColor; stroke-width: 0.3; fill: none; }; let dotLayer = PathLayer('dot') ${ stroke: dotColor; stroke-width: 0.3; fill: dotColor; }; let intLayer = PathLayer('int') ${ stroke: intColor; stroke-width: 0.4; fill: none; stroke-linecap: round; }; let partLayer = PathLayer('part') ${ stroke: partColor; stroke-width: 0.4; fill: none; stroke-linecap: round; }; // Top-left: Shape shapeLayer.apply { squareGrid(GridPatternType.Shape, pad, pad + 15, gridW, gridH, cellSize); } // Top-right: Dot dotLayer.apply { squareGrid(GridPatternType.Dot, 220, pad + 15, gridW, gridH, cellSize); } // Bottom-left: Intersection intLayer.apply { squareGrid(GridPatternType.Intersection, pad, 215 + 15, gridW, gridH, cellSize); } // Bottom-right: Partial partLayer.apply { squareGrid(GridPatternType.Partial, 220, 220 + 15, gridW, gridH, cellSize); } // Labels let labels = TextLayer('labels') ${ font-family: system-ui, sans-serif; font-size: 11; fill: #94a3b8; text-anchor: start; }; labels.apply { text(pad, pad + 8)`Shape` text(220, pad + 8)`Dot` text(pad, 220 + 8)`Intersection` text(220, 220 + 8)`Partial` } squareGrid — four pattern types with reactive colors

Triangle Grids

triangleGrid(type, x, y, width, height, cellSize)

Same parameters as squareGrid, but cellSize is the triangle height (altitude of the equilateral triangle). The side length is derived: side = 2 × cellSize / √3. Triangles alternate between point-up and point-down orientations, with odd rows offset by half a side length to form a seamless tessellation.

The intersection marks on triangle grids are edge-aligned — six arms at 60° intervals (three bidirectional lines), matching the grid's natural symmetry where six edges meet at each interior vertex.

// viewBox="0 0 400 400" // Triangle grid — all four pattern types let gridColor = Color(CSSVar('--grid-color', #44cc88)); let bgColor = Color(CSSVar('--bg-color', #0f172a)); let cellSize = 14; let gridW = 160; let gridH = 150; let pad = 20; // Background let bg = PathLayer('bg') ${ fill: bgColor; stroke: none; }; bg.apply { rect(0, 0, 400, 400) } // Grid layers let shapeColor = gridColor; let dotColor = gridColor.hueShift(90); let intColor = gridColor.hueShift(180); let partColor = gridColor.hueShift(270); let shapeLayer = PathLayer('shape') ${ stroke: shapeColor; stroke-width: 0.3; fill: none; }; let dotLayer = PathLayer('dot') ${ stroke: dotColor; stroke-width: 0.3; fill: dotColor; }; let intLayer = PathLayer('int') ${ stroke: intColor; stroke-width: 0.4; fill: none; stroke-linecap: round; }; let partLayer = PathLayer('part') ${ stroke: partColor; stroke-width: 0.4; fill: none; stroke-linecap: round; }; // Top-left: Shape shapeLayer.apply { triangleGrid(GridPatternType.Shape, pad, pad + 15, gridW, gridH, cellSize); } // Top-right: Dot dotLayer.apply { triangleGrid(GridPatternType.Dot, 220, pad + 15, gridW, gridH, cellSize); } // Bottom-left: Intersection intLayer.apply { triangleGrid(GridPatternType.Intersection, pad, 215 + 15, gridW, gridH, cellSize); } // Bottom-right: Partial partLayer.apply { triangleGrid(GridPatternType.Partial, 220, 220 + 15, gridW, gridH, cellSize); } // Labels let labels = TextLayer('labels') ${ font-family: system-ui, sans-serif; font-size: 11; fill: #94a3b8; text-anchor: start; }; labels.apply { text(pad, pad + 8)`Shape` text(220, pad + 8)`Dot` text(pad, 220 + 8)`Intersection` text(220, 220 + 8)`Partial` } triangleGrid — equilateral triangle tessellation in four pattern types

Hexagon Grids

hexagonGrid(type, x, y, width, height, cellSize, orientation?)

The hexagon function adds a seventh parameter: orientation. It accepts the HexagonOrientation enum — .Edge for flat-top hexagons (an edge faces up) or .Vertex for pointy-top (a vertex faces up). When omitted, it defaults to .Edge.

Orientation Top cellSize meaning
HexagonOrientation.Edge Flat edge Flat-to-flat height
HexagonOrientation.Vertex Pointed vertex Point-to-point height

Intersection marks on hex grids are 3-arm radial marks — matching the three edges that meet at each vertex.

// viewBox="0 0 400 400" // Hexagon grid — all four pattern types (flat-top) let gridColor = Color(CSSVar('--grid-color', #ff6688)); let bgColor = Color(CSSVar('--bg-color', #0f172a)); let cellSize = 18; let gridW = 160; let gridH = 150; let pad = 20; // Background let bg = PathLayer('bg') ${ fill: bgColor; stroke: none; }; bg.apply { rect(0, 0, 400, 400) } // Grid layers let shapeColor = gridColor; let dotColor = gridColor.hueShift(90); let intColor = gridColor.hueShift(180); let partColor = gridColor.hueShift(270); let shapeLayer = PathLayer('shape') ${ stroke: shapeColor; stroke-width: 0.3; fill: none; }; let dotLayer = PathLayer('dot') ${ stroke: dotColor; stroke-width: 0.3; fill: dotColor; }; let intLayer = PathLayer('int') ${ stroke: intColor; stroke-width: 0.4; fill: none; stroke-linecap: round; }; let partLayer = PathLayer('part') ${ stroke: partColor; stroke-width: 0.4; fill: none; stroke-linecap: round; }; // Top-left: Shape shapeLayer.apply { hexagonGrid(GridPatternType.Shape, pad, pad + 15, gridW, gridH, cellSize); } // Top-right: Dot dotLayer.apply { hexagonGrid(GridPatternType.Dot, 220, pad + 15, gridW, gridH, cellSize); } // Bottom-left: Intersection intLayer.apply { hexagonGrid(GridPatternType.Intersection, pad, 215 + 15, gridW, gridH, cellSize); } // Bottom-right: Partial partLayer.apply { hexagonGrid(GridPatternType.Partial, 220, 220 + 15, gridW, gridH, cellSize); } // Labels let labels = TextLayer('labels') ${ font-family: system-ui, sans-serif; font-size: 11; fill: #94a3b8; text-anchor: start; }; labels.apply { text(pad, pad + 8)`Shape` text(220, pad + 8)`Dot` text(pad, 220 + 8)`Intersection` text(220, 220 + 8)`Partial` } hexagonGrid — flat-top hexagons in four pattern types

Orientation Comparison

The two orientations produce visually distinct tessellations from the same cell size:

// viewBox="0 0 400 200" // Hexagon orientations — flat-top (Edge) vs pointy-top (Vertex) let gridColor = Color(CSSVar('--grid-color', #cc88ff)); let bgColor = Color(CSSVar('--bg-color', #0f172a)); let cellSize = 18; let gridW = 150; let gridH = 140; let pad = 20; // Background let bg = PathLayer('bg') ${ fill: bgColor; stroke: none; }; bg.apply { rect(0, 0, 400, 200) } // Grid layers let flatColor = gridColor; let pointyColor = gridColor.hueShift(120); let flatLayer = PathLayer('flat') ${ stroke: flatColor; stroke-width: 0.4; fill: none; }; let pointyLayer = PathLayer('pointy') ${ stroke: pointyColor; stroke-width: 0.4; fill: none; }; // Left: Flat-top (Edge) flatLayer.apply { hexagonGrid(GridPatternType.Shape, pad, pad + 15, gridW, gridH, cellSize, HexagonOrientation.Edge); } // Right: Pointy-top (Vertex) pointyLayer.apply { hexagonGrid(GridPatternType.Shape, 220, pad + 15, gridW, gridH, cellSize, HexagonOrientation.Vertex); } // Labels let labels = TextLayer('labels') ${ font-family: system-ui, sans-serif; font-size: 11; fill: #94a3b8; text-anchor: start; }; labels.apply { text(pad, pad + 8)`Flat-top (Edge)` text(220, pad + 8)`Pointy-top (Vertex)` } HexagonOrientation.Edge (flat-top) vs HexagonOrientation.Vertex (pointy-top)

Four Pattern Types

All three grid functions share the same GridPatternType enum:

Pattern Visual Description
Shape Cell outlines Complete grid lines — every edge drawn once
Dot Small circles Dots at every grid vertex
Intersection Edge-aligned marks Marks where edges meet — axis-aligned + for squares, 6-arm stars for triangles (3 bidirectional lines at 60°), 3-arm Y for hexagons
Partial Centered segments 40% of each edge length, centered on the edge midpoint

Pattern proportions are relative to cellSize: dot radius is 2.5%, intersection arm length is 7.5%.

Putting It Together

Grid functions are most useful as background textures layered behind other geometry. Combine them with layer transforms for rotation, and use Color methods to derive palette variations from a single reactive base color:

// viewBox="0 0 400 400" // Practical composition — rotated grid background with foreground geometry let gridColor = Color(CSSVar('--grid-color', #5577aa)); let bgColor = Color(CSSVar('--bg-color', #0f172a)); // Background let bg = PathLayer('bg') ${ fill: bgColor; stroke: none; }; bg.apply { rect(0, 0, 400, 400) } // Layer 1: Rotated partial grid as background texture let gridLayer = PathLayer('grid') ${ stroke: gridColor; stroke-width: 0.3; fill: none; stroke-linecap: round; }; gridLayer.ctx.transform.rotate.set(0.08pi); gridLayer.apply { squareGrid(GridPatternType.Partial, -40, -40, 480, 480, 16); } // Layer 2: Hex grid overlay with shape pattern let accentColor = gridColor.lighten(30%); let hexLayer = PathLayer('hex') ${ stroke: accentColor; stroke-width: 0.5; fill: none; }; hexLayer.apply { hexagonGrid(GridPatternType.Shape, 60, 60, 280, 280, 30); } // Layer 3: Triangle intersection marks at center let triColor = gridColor.hueShift(180).lighten(20%); let triLayer = PathLayer('tri') ${ stroke: triColor; stroke-width: 0.6; fill: none; stroke-linecap: round; }; triLayer.apply { triangleGrid(GridPatternType.Intersection, 120, 120, 160, 160, 25); } // Labels let labels = TextLayer('labels') ${ font-family: system-ui, sans-serif; font-size: 9; fill: #64748b; text-anchor: start; }; labels.apply { text(22, 388)`Rotated partial grid + hex outlines + triangle intersections` } Layered composition — rotated partial grid, hex outline, and triangle intersections

The composition uses three techniques worth noting:

  • Rotation via transformsgridLayer.ctx.transform.rotate.set(0.08pi) tilts the background grid. The grid area is oversized (-40, -40, 480, 480) to prevent gaps at the rotated corners.
  • Layered grid types — a partial square grid as subtle texture, hex outlines as mid-ground structure, and triangle intersection marks as a focal accent. Each gets its own PathLayer with distinct styling.
  • Color derivation — all three grid colors derive from a single --grid-color variable via .lighten() and .hueShift(), so changing the base color recolors the entire composition.

The core pattern is always the same: create a styled PathLayer, optionally set a transform, then call the grid function inside layer.apply {}.

Three functions, four pattern types, two hexagon orientations — enough to cover graph paper, game boards, engineering overlays, and generative art backgrounds without writing a single loop. Try changing the --grid-color and --bg-color variables in any of the examples above to see how the reactive colors work.

For the full function signatures and parameter details, see the stdlib reference. For layer management and transforms, see the layers documentation. For combining grids with reusable path blocks, see the PathBlock introduction.