DEV Community

Cover image for 3D Zig Zag Matrix
Dan
Dan

Posted on

3D Zig Zag Matrix

GitHub “Finish-Up-A-Thon” Challenge Submission

Project Overview
3D Zig-Zag Matrix — a small interactive visual that renders a zig‑zag numbered matrix as a rotating 3D wave of colored points on an HTML canvas. It started as a compact demo combining a classic zig‑zag matrix generator with a simple perspective projection and animation. I finished it by polishing the visuals, fixing projection/rotation math, adding responsive canvas sizing, and improving the demo copy for the GitHub/DEV submission.

Polished DEV Submission (ready to paste)
What I Built

I built a small interactive demo called 3D Zig‑Zag Matrix that visualizes a zig‑zag traversal of an
𝑛
×
𝑛
matrix as a rotating 3D wave of colored points. The project combines a compact algorithmic generator with a lightweight 3D projection and animation loop so you can see the zig‑zag order come alive.

Demo

Live demo: (paste your hosted URL here)

Screenshots / GIF: Add a short GIF or screenshot showing the rotating wave.

How to run locally: Clone the repo, open index.html in a browser, or serve with a static server.

The Comeback Story

The project began as a small codepen-style prototype with working logic but rough visuals and a few bugs in the projection and resizing behavior. To finish it up I:

Fixed the 3D projection and rotation math so the grid rotates smoothly.

Made the canvas responsive to window resizes and device pixel ratio.

Added a wave effect tied to the zig‑zag index for a more organic motion.

Cleaned up the code and added comments so others can extend it.

My Experience with GitHub Copilot

GitHub Copilot helped speed up the iteration loop: it suggested the initial projection formula, offered small refactors for the animation loop, and proposed color cycling logic. I reviewed and adapted the suggestions to match the visual style I wanted.

How to Run Locally
Save the HTML below as index.html and the JavaScript as zigzag.js in the same folder.

Open index.html in a modern browser (Chrome, Edge, Firefox).

Optionally serve with a static server (e.g., npx http-server) for a stable local URL.

Improved Code
index.html

html
<!doctype html>




3D Zig-Zag Matrix
<br> :root { background: #000; }<br> html,body { height:100%; margin:0; }<br> body {<br> display:flex;<br> align-items:center;<br> justify-content:center;<br> background:#000;<br> color:#fff;<br> font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;<br> }<br> canvas { display:block; width:100%; height:100vh; }<br> .ui {<br> position:fixed;<br> left:12px;<br> top:12px;<br> z-index:10;<br> background:rgba(0,0,0,0.4);<br> padding:8px 10px;<br> border-radius:8px;<br> backdrop-filter:blur(4px);<br> color:#fff;<br> font-size:13px;<br> }<br> .ui input { width:48px; }<br>



Size:
Cell:





zigzag.js

javascript
// Zig-zag matrix generator
function createZigZagMatrix(n) {
const matrix = Array.from({ length: n }, () => Array(n).fill(0));
let num = 0;
for (let d = 0; d < 2 * n - 1; d++) {
if (d % 2 === 0) {
for (let i = Math.min(d, n - 1); i >= Math.max(0, d - n + 1); i--) {
matrix[i][d - i] = num++;
}
} else {
for (let i = Math.max(0, d - n + 1); i <= Math.min(d, n - 1); i++) {
matrix[i][d - i] = num++;
}
}
}
return matrix;
}

// Canvas setup
const canvas = document.getElementById('zigzagCanvas');
const ctx = canvas.getContext('2d', { alpha: false });

function resizeCanvas() {
const dpr = Math.max(1, window.devicePixelRatio || 1);
canvas.width = Math.floor(window.innerWidth * dpr);
canvas.height = Math.floor(window.innerHeight * dpr);
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();

// UI controls
const sizeInput = document.getElementById('sizeInput');
const cellInput = document.getElementById('cellInput');

let size = Math.max(2, Math.min(40, parseInt(sizeInput.value, 10) || 8));
let cellSize = Math.max(8, Math.min(80, parseInt(cellInput.value, 10) || 28));

sizeInput.addEventListener('change', () => {
size = Math.max(2, Math.min(40, parseInt(sizeInput.value, 10) || 8));
resetMatrix();
});
cellInput.addEventListener('change', () => {
cellSize = Math.max(8, Math.min(80, parseInt(cellInput.value, 10) || 28));
});

// Colors
const colors = [
'#FF6B6B', '#FFD93D', '#6BCB77', '#4D96FF', '#9B5DE5',
'#00BBF9', '#FF7AB6', '#F6AE2D', '#2EC4B6', '#FF6F91'
];

// State
let zigZagMatrix = createZigZagMatrix(size);
let angleX = 0;
let angleY = 0;
let time = 0;

function resetMatrix() {
zigZagMatrix = createZigZagMatrix(size);
}

// Simple 3D rotate and perspective projection
function project3D(x, y, z, cameraZ = 800, fov = 800) {
const zRel = cameraZ - z;
const scale = fov / zRel;
return {
x: x * scale,
y: y * scale,
scale
};
}

function drawMatrix() {
// Clear with slight fade for motion trails
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);

const cx = canvas.width / (2 * (window.devicePixelRatio || 1));
const cy = canvas.height / (2 * (window.devicePixelRatio || 1));

for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
const value = zigZagMatrix[i][j];
// center grid around origin
const x0 = (j - (size - 1) / 2) * cellSize;
const y0 = (i - (size - 1) / 2) * cellSize;
// wave along value index and time
const wave = Math.sin(value * 0.25 + time * 0.02) * (cellSize * 0.9);
let x = x0;
let y = y0;
let z = wave;

  // rotate around Y then X
  const cosY = Math.cos(angleY), sinY = Math.sin(angleY);
  const cosX = Math.cos(angleX), sinX = Math.sin(angleX);

  // rotate Y
  let rx = x * cosY - z * sinY;
  let rz = x * sinY + z * cosY;
  // rotate X
  let ry = y * cosX - rz * sinX;
  rz = y * sinX + rz * cosX;

  const p = project3D(rx, ry, rz + 400, 1000, 1000);
  const screenX = cx + p.x;
  const screenY = cy + p.y;

  // size based on depth
  const radius = Math.max(2, 8 * p.scale);

  ctx.beginPath();
  ctx.fillStyle = colors[value % colors.length];
  ctx.globalAlpha = 0.95;
  ctx.arc(screenX, screenY, radius, 0, Math.PI * 2);
  ctx.fill();

  // subtle highlight
  ctx.beginPath();
  ctx.globalAlpha = 0.25;
  ctx.fillStyle = '#fff';
  ctx.arc(screenX - radius * 0.35, screenY - radius * 0.35, Math.max(0.6, radius * 0.35), 0, Math.PI * 2);
  ctx.fill();
  ctx.globalAlpha = 1;
}

}

// animate
angleX += 0.007;
angleY += 0.01;
time += 1;
requestAnimationFrame(drawMatrix);
}

drawMatrix();
Notes and Suggestions
Hosting: Use GitHub Pages for a quick demo URL. Add the demo link to your DEV post.

Accessibility: Add keyboard controls to pause/step the animation and ARIA labels for the UI.

Extensions: Try rendering lines between points in zig‑zag order, or add a depth-sorted glow for a neon look.

Performance: For very large sizes, consider OffscreenCanvas or WebGL for faster rendering.

Top comments (0)