All articles
12 min read

Geometric Art: How to Create Stunning Geometric Patterns With Code

geometric artgenerative artcreative codingjavascripttutorialgeometric patterns

Geometric art is one of the oldest forms of human expression — from the tessellations of the Alhambra to the stained glass windows of Gothic cathedrals to the op art of Bridget Riley. What makes it timeless is that geometry is inherently satisfying: symmetry, repetition, transformation. And when you generate geometric art with code, you unlock patterns that would take months to draw by hand but emerge in milliseconds from a few lines of JavaScript.

This guide covers the most rewarding geometric patterns for creative coding, each with working code you can paste into a browser. No frameworks, no libraries — just Canvas API and math.

Setting up

Every example uses this minimal HTML setup:

<canvas id="c" width="800" height="800"></canvas>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const cx = W / 2, cy = H / 2;
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, W, H);
</script>

1. Radial symmetry — the mandala

A mandala is geometry reflected around a center point. The code version: draw something once, then repeat it at equal angles around the center. This is the easiest geometric art pattern and produces the most dramatic results.

const slices = 12;
const angleStep = (Math.PI * 2) / slices;

ctx.strokeStyle = 'rgba(120, 200, 255, 0.6)';
ctx.lineWidth = 1.5;

for (let ring = 1; ring <= 8; ring++) {
  const radius = ring * 40;
  for (let i = 0; i < slices; i++) {
    const angle = i * angleStep;
    ctx.save();
    ctx.translate(cx, cy);
    ctx.rotate(angle);

    // Draw a petal shape
    ctx.beginPath();
    ctx.moveTo(0, radius - 30);
    ctx.quadraticCurveTo(20, radius, 0, radius + 30);
    ctx.quadraticCurveTo(-20, radius, 0, radius - 30);
    ctx.stroke();

    // Add a circle at each intersection
    ctx.beginPath();
    ctx.arc(0, radius, 5, 0, Math.PI * 2);
    ctx.stroke();

    ctx.restore();
  }
}

Change slices from 12 to 5 and you get a star. Change it to 36 and you get a sunburst. Add a second layer with a different offset and the patterns become hypnotic. This is the core insight of geometric art: simple rules, compound beauty.

2. Islamic geometric patterns

Islamic geometric art is built on a surprising principle: start with a grid of circles, then connect their intersections. The interlocking stars, hexagons, and polygons that define mosque ceilings and tile work all emerge from this simple construction.

// Hexagonal grid with interlocking stars
const size = 60;
const rows = 14, cols = 10;
ctx.strokeStyle = 'rgba(255, 200, 100, 0.7)';
ctx.lineWidth = 1.2;

for (let row = 0; row < rows; row++) {
  for (let col = 0; col < cols; col++) {
    const x = col * size * 1.5 + 50;
    const y = row * size * Math.sqrt(3) + (col % 2 ? size * Math.sqrt(3) / 2 : 0) + 50;

    // Draw a 6-pointed star (hexagram)
    for (let t = 0; t < 2; t++) {
      ctx.beginPath();
      for (let i = 0; i <= 3; i++) {
        const angle = (i * Math.PI * 2) / 3 + t * Math.PI / 6;
        const px = x + Math.cos(angle) * size * 0.5;
        const py = y + Math.sin(angle) * size * 0.5;
        if (i === 0) ctx.moveTo(px, py);
        else ctx.lineTo(px, py);
      }
      ctx.closePath();
      ctx.stroke();
    }

    // Connect to neighbors
    ctx.beginPath();
    ctx.arc(x, y, size * 0.5, 0, Math.PI * 2);
    ctx.stroke();
  }
}

The beauty of this approach is that it mirrors how Islamic artisans actually constructed these patterns — with a compass and straightedge. The code is the digital compass.

3. Voronoi diagrams — nature's geometry

Voronoi diagrams partition space into regions based on distance to seed points. They appear everywhere in nature: giraffe skin, dragonfly wings, dried mud, cell structures. Generating them from code produces organic-looking geometric art.

// Generate random seed points
const seeds = Array.from({length: 40}, () => ({
  x: Math.random() * W,
  y: Math.random() * H,
  color: `hsl(${Math.random() * 360}, 60%, 50%)`
}));

// For each pixel, find nearest seed (brute force — good enough for art)
const imageData = ctx.createImageData(W, H);
for (let py = 0; py < H; py++) {
  for (let px = 0; px < W; px++) {
    let minDist = Infinity, closest = 0;
    let secondDist = Infinity;
    for (let i = 0; i < seeds.length; i++) {
      const d = Math.hypot(px - seeds[i].x, py - seeds[i].y);
      if (d < minDist) {
        secondDist = minDist;
        minDist = d;
        closest = i;
      } else if (d < secondDist) {
        secondDist = d;
      }
    }
    const idx = (py * W + px) * 4;
    // Edge detection: darken pixels near cell boundaries
    const edge = secondDist - minDist < 3 ? 0.1 : 1;
    const hue = (closest * 37) % 360;
    // Simple HSL to RGB conversion for the fill
    const c = edge * 0.5;
    imageData.data[idx] = (Math.sin(hue * 0.017) * 0.5 + 0.5) * 200 * edge;
    imageData.data[idx + 1] = (Math.sin((hue + 120) * 0.017) * 0.5 + 0.5) * 200 * edge;
    imageData.data[idx + 2] = (Math.sin((hue + 240) * 0.017) * 0.5 + 0.5) * 200 * edge;
    imageData.data[idx + 3] = 255;
  }
}
ctx.putImageData(imageData, 0, 0);

For a more mathematical approach, you can use Fortune's algorithm for precise Voronoi edges, but the brute-force pixel method above has its own charm — you can manipulate the distance function to create warped, organic cells.

4. Spirograph / hypotrochoid patterns

A spirograph draws curves by rolling one circle inside another. The mathematical name is hypotrochoid, and the formula is elegantly simple — yet produces endlessly varied geometric art:

ctx.strokeStyle = 'rgba(255, 100, 180, 0.5)';
ctx.lineWidth = 0.8;

const R = 200;  // Outer radius
const r = 73;   // Inner radius
const d = 120;  // Drawing point distance from inner center
const steps = 10000;

ctx.beginPath();
for (let i = 0; i <= steps; i++) {
  const t = (i / steps) * Math.PI * 2 * (r / gcd(R, r));
  const x = cx + (R - r) * Math.cos(t) + d * Math.cos(((R - r) / r) * t);
  const y = cy + (R - r) * Math.sin(t) - d * Math.sin(((R - r) / r) * t);
  if (i === 0) ctx.moveTo(x, y);
  else ctx.lineTo(x, y);
}
ctx.stroke();

// Helper: greatest common divisor
function gcd(a, b) { return b ? gcd(b, a % b) : a; }

The magic is in the ratio R/r. When it's a simple fraction (like 3/1), you get a simple shape with 3 petals. When it's complex (like 200/73), you get dense, intricate patterns that take thousands of steps to close. Try changing R, r, and d — each combination produces a unique geometric signature.

5. Penrose tiling — aperiodic geometry

Penrose tilings are the mind-bender of geometric art. They use just two shapes (kite and dart, or thick and thin rhombus) but never repeat. The pattern has five-fold symmetry — which shouldn't be possible in a tiling, yet there it is. Here's a simplified deflation approach:

// Penrose tiling via subdivision (P3 rhombus method)
const phi = (1 + Math.sqrt(5)) / 2; // Golden ratio

function drawRhombus(ax, ay, bx, by, cx, cy, dx, dy, type, depth) {
  if (depth === 0) {
    ctx.beginPath();
    ctx.moveTo(ax, ay);
    ctx.lineTo(bx, by);
    ctx.lineTo(cx, cy);
    ctx.lineTo(dx, dy);
    ctx.closePath();
    ctx.fillStyle = type === 'thick'
      ? 'rgba(80, 160, 220, 0.6)'
      : 'rgba(220, 120, 80, 0.6)';
    ctx.fill();
    ctx.strokeStyle = 'rgba(255,255,255,0.3)';
    ctx.lineWidth = 0.5;
    ctx.stroke();
    return;
  }
  // Subdivision would go here (omitted for brevity)
  // Each thick rhombus subdivides into 2 thick + 1 thin
  // Each thin rhombus subdivides into 1 thick + 1 thin
}

// Start with a star of 5 thick rhombuses
const startR = 350;
for (let i = 0; i < 5; i++) {
  const a1 = (i * 2) * Math.PI / 5 - Math.PI / 2;
  const a2 = (i * 2 + 2) * Math.PI / 5 - Math.PI / 2;
  const ax = cx, ay = cy;
  const bx = cx + Math.cos(a1) * startR;
  const by = cy + Math.sin(a1) * startR;
  const dx = cx + Math.cos(a2) * startR;
  const dy = cy + Math.sin(a2) * startR;
  const mx = cx + Math.cos((a1 + a2) / 2) * startR * 0.6;
  const my = cy + Math.sin((a1 + a2) / 2) * startR * 0.6;
  drawRhombus(ax, ay, bx, by, mx, my, dx, dy, 'thick', 0);
}

Penrose tilings connect to deep mathematics — they're related to quasicrystals, the golden ratio, and Fibonacci sequences. In 2023, a hobbyist discovered the first true "einstein tile" (a single shape that tiles aperiodically), adding a new chapter to this ancient pursuit.

6. Optical illusion patterns — op art with code

Op art (optical art) uses precise geometric patterns to create the illusion of movement, depth, or vibration on a flat surface. Bridget Riley and Victor Vasarely pioneered this in the 1960s. With code, you can generate these hypnotic effects in seconds:

// Warped checkerboard — creates illusion of a bulging surface
for (let y = 0; y < H; y += 2) {
  for (let x = 0; x < W; x += 2) {
    // Distance from center, normalized
    const dx = (x - cx) / cx;
    const dy = (y - cy) / cy;
    const dist = Math.sqrt(dx * dx + dy * dy);

    // Warp the grid coordinates using a spherical bulge
    const warpStrength = 0.4;
    const warp = 1 + warpStrength * (1 - dist);
    const wx = dx * warp * 20;
    const wy = dy * warp * 20;

    // Checkerboard pattern on warped coordinates
    const check = (Math.floor(wx) + Math.floor(wy)) % 2 === 0;
    ctx.fillStyle = check ? '#ffffff' : '#000000';
    ctx.fillRect(x, y, 2, 2);
  }
}

The illusion of a 3D bulge emerges from nothing but warped coordinates in a flat checkerboard. This is the power of geometric art — pure math creating visceral perception.

7. Kaleidoscope effects

A kaleidoscope works by reflecting an image across multiple axes. In code, we draw in one "slice" of the circle and reflect it:

const segments = 8;
const segAngle = (Math.PI * 2) / segments;

// Draw random elements in one segment
ctx.save();
ctx.translate(cx, cy);

for (let seg = 0; seg < segments; seg++) {
  ctx.save();
  ctx.rotate(seg * segAngle);
  if (seg % 2 === 1) ctx.scale(1, -1); // Mirror alternate segments

  // Draw geometric elements in the slice
  for (let i = 0; i < 15; i++) {
    const r = 50 + Math.random() * 300;
    const a = Math.random() * segAngle * 0.9;
    const x = Math.cos(a) * r;
    const y = Math.sin(a) * r;
    const size = 5 + Math.random() * 20;

    ctx.fillStyle = `hsla(${r * 0.8}, 70%, 60%, 0.6)`;
    ctx.beginPath();

    // Randomly choose shape
    if (Math.random() < 0.33) {
      ctx.arc(x, y, size, 0, Math.PI * 2);
    } else if (Math.random() < 0.5) {
      ctx.rect(x - size/2, y - size/2, size, size);
    } else {
      ctx.moveTo(x, y - size);
      ctx.lineTo(x + size, y + size);
      ctx.lineTo(x - size, y + size);
      ctx.closePath();
    }
    ctx.fill();
  }

  ctx.restore();
}
ctx.restore();

The key insight: you only need to design one slice. The symmetry does the rest. This is why geometric art is so rewarding — small inputs produce grand outputs.

8. Line moiré — interference geometry

When two sets of lines overlap at slightly different angles or spacings, moiré patterns emerge — shimmering, flowing shapes that seem to move even in a static image:

ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 1;

// First set: straight vertical lines
for (let x = 0; x < W; x += 6) {
  ctx.beginPath();
  ctx.moveTo(x, 0);
  ctx.lineTo(x, H);
  ctx.stroke();
}

// Second set: concentric circles (creates flowing moiré)
for (let r = 3; r < 600; r += 6) {
  ctx.beginPath();
  ctx.arc(cx, cy, r, 0, Math.PI * 2);
  ctx.stroke();
}

This is perhaps the simplest code in this guide — just lines and circles — yet the visual result is strikingly complex. Moiré patterns were explored in our math art guide too, and they remain one of the most accessible entry points to geometric art.

Animating geometric art

Static geometric patterns are beautiful. Animated ones are mesmerizing. The general pattern: wrap your drawing code in a requestAnimationFrame loop and make one or more parameters time-dependent:

function animate(time) {
  ctx.fillStyle = 'rgba(10, 10, 10, 0.05)'; // Trail effect
  ctx.fillRect(0, 0, W, H);

  const t = time * 0.001;
  const slices = 8;

  ctx.strokeStyle = `hsl(${t * 30 % 360}, 70%, 60%)`;
  ctx.lineWidth = 1;

  for (let i = 0; i < slices; i++) {
    const angle = (i / slices) * Math.PI * 2 + t * 0.5;
    const r1 = 100 + Math.sin(t + i) * 50;
    const r2 = 250 + Math.cos(t * 0.7 + i) * 80;

    ctx.beginPath();
    ctx.moveTo(
      cx + Math.cos(angle) * r1,
      cy + Math.sin(angle) * r1
    );
    ctx.lineTo(
      cx + Math.cos(angle + 0.2) * r2,
      cy + Math.sin(angle + 0.2) * r2
    );
    ctx.stroke();
  }

  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

The rgba(10, 10, 10, 0.05) fill creates a fade trail — previous frames slowly darken instead of being erased. This single trick transforms any geometric animation into something that feels alive.

Design principles for geometric art

After building dozens of geometric pieces, some patterns emerge in what makes them work:

  • Constraint breeds creativity — Limiting yourself to one shape (only circles, only triangles) forces more inventive compositions. This is why Lumitree's 50KB constraint produces such varied results.
  • Layer transparency — Overlapping semi-transparent shapes creates visual depth that solid fills can't match. Use rgba() or hsla() with alpha values between 0.2 and 0.6.
  • Break the pattern — Perfect symmetry is satisfying but can feel sterile. Introduce one element of randomness or irregularity. A mandala with one slightly off-center ring is more interesting than a perfect one.
  • Color harmony matters — Use HSL color space. Analogous colors (hues within 30° of each other) create calm pieces. Complementary colors (180° apart) create vibrant tension.
  • Scale variation — Patterns that repeat at different scales (large shapes made of smaller shapes) tap into the same appeal as fractal art.

Geometric art in history and culture

Geometric art isn't just a coding exercise — it connects to deep human traditions:

  • Islamic art — Developed the most sophisticated geometric pattern systems over 1000+ years, based on circle packings and polygon subdivisions
  • Celtic knotwork — Continuous interlacing lines that form complex geometric patterns with no beginning or end
  • Op art movement (1960s) — Bridget Riley, Victor Vasarely, and others used precise geometry to create optical illusions on canvas
  • Sacred geometry — The Flower of Life, Metatron's Cube, Sri Yantra — geometric patterns used across spiritual traditions
  • Bauhaus and De Stijl — Mondrian, Kandinsky, and others reduced art to geometric essentials
  • Generative art (1960s–now) — From Vera Molnár's early computer art to today's creative coding community, geometry remains the foundation

When you write code that generates geometric patterns, you're participating in a tradition that spans millennia and cultures. The medium changes (compass → code), but the fascination with geometry's beauty is constant.

Tools and resources

  • Creative coding tools — p5.js, Processing, Shadertoy and more for geometric art
  • Shadertoy — GPU-accelerated geometric patterns with GLSL
  • p5.js — Beginner-friendly creative coding library, great for geometric experiments
  • Craig Kaplan's research — Academic work on Islamic geometric patterns and computational geometry
  • Drawing with code — Our beginner's guide to making art in the browser

Ready to see geometric art generated by code? Explore the Lumitree — each micro-world is a unique generative artwork, many built on the geometric principles described here. Or plant your own seed and watch it grow into something unexpected. The geometry is in the code; the art is in the exploration.

Related articles