A project Gantt chart where the today marker sweeps across the weeks, task bars fill with progress and milestones light up.
Q2 launch plan today Mar 4 Mar Apr May W9 W10 W11 W12 W13 W14 W15 W16 W17 W18 ────────────────────────▼───────────────────────────────────────────────────────────────────── Research 32% ███▒▒▒▒▒▒ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ Wireframes 0% ┊ │ ▒▒▒▒▒▒▒▒▒▒ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ Visual design 0% ┊ │ ┊ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┊ ┊ ┊ ┊ ┊ ┊ ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ API design 0% ┊ │ ┊▒▒▒▒▒▒▒▒▒▒▒▒ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ Backend build 0% ┊ │ ┊ ┊ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┊ ┊ ┊ ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ Frontend build 0% ┊ │ ┊ ┊ ┊ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┊ ┊ ┊ ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ Beta release ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ◆ ┊ ┊ ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ QA testing 0% ┊ │ ┊ ┊ ┊ ┊ ┊ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┊ ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ Docs 0% ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┊ ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ Launch ┊ │ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ◆ overall ░░░░░░░░░░░░ 2%
Paste two lines into any page, play it in a terminal, or put a GIF in a README. The code follows the look you pick above.
<script type="module" src="https://www.ascii-magic.com/ascii/ascii.js"></script>
<ascii-art piece="gantt-chart"></ascii-art>// copy gantt-chart.js and kit.js into your project, then:
import { useEffect, useRef } from "react";
import create from "./gantt-chart.js";
export function Piece() {
const ref = useRef(null);
useEffect(() => {
const draw = create(), t0 = performance.now();
let raf;
const tick = () => {
const f = draw((performance.now() - t0) / 1000);
ref.current.textContent = f.text ?? f;
raf = requestAnimationFrame(tick);
};
tick();
return () => cancelAnimationFrame(raf);
}, []);
return <pre ref={ref} style={{ lineHeight: 1, fontFamily: "Menlo, Consolas, monospace" }} />;
}npx --yes https://www.ascii-magic.com/ascii/cli.tgz gantt-chart<!-- Download GIF below, commit it, then: -->
<p align="center">
<img src="assets/gantt-chart.gif" alt="Gantt Chart, animated ASCII art" width="720">
</p>Pause on the frame you like, pick a look, then download it at an exact size.
MIT licensed. It imports one small helper file, kit.js.
/* gantt-chart: task bars on a timeline with a moving today marker and progress filling in. */
import { Grid, palette, clamp } from "../lib/kit.js";
export const meta = {
slug: "gantt-chart",
name: "Gantt Chart",
category: "explainers",
note: "A project Gantt chart where the today marker sweeps across the weeks, task bars fill with progress and milestones light up.",
tags: ["gantt", "project", "timeline", "planning", "diagram"],
cols: 94, rows: 24, fps: 20,
palette: palette(["#e6edf3", "#8b949e", "#30363d", "#79c0ff", "#1d3a5c", "#d2a8ff", "#3c2a5c", "#7ee787", "#1d4a2b", "#ffa657", "#5c3a1d", "#ff7b72", "#f2cc60", "#ffffff", "#21262d"]),
};
// name, start day, duration (0 = milestone), colour pair
const TASKS = [
["Research", 0, 9, "34"], ["Wireframes", 5, 10, "56"], ["Visual design", 12, 15, "56"], ["API design", 8, 12, "34"],
["Backend build", 18, 25, "78"], ["Frontend build", 24, 24, "78"], ["Beta release", 49, 0, "c"], ["QA testing", 40, 17, "9a"],
["Docs", 44, 14, "9a"], ["Launch", 63, 0, "c"],
];
const MONTHS = [["Mar", 0], ["Apr", 29], ["May", 59]];
export default function create() {
const { cols, rows } = meta;
const g = new Grid(cols, rows);
const X0 = 22, DAYS = 70;
return (t) => {
g.clear();
const LOOP = 14, lt = t % LOOP;
const today = clamp(lt / 12) * (DAYS - 1);
const td = Math.floor(today);
g.text(1, 0, "Q2 launch plan", "0");
// month and week headers
for (const [m, d] of MONTHS) g.text(X0 + d, 1, m, "1");
for (let d = 0; d < DAYS; d += 7) {
g.text(X0 + d, 2, "W" + (d / 7 + 9), "2");
for (let y = 3; y < rows - 1; y++) g.set(X0 + d, y, "┊", "e");
}
g.hline(0, cols - 1, 3, "─", "2");
TASKS.forEach(([name, s, dur, co], i) => {
const y = 4 + i * 2;
const p = dur ? clamp((today - s) / dur) : today >= s ? 1 : 0;
const pct = Math.round(p * 100);
g.text(1, y, name, p >= 1 ? "1" : today >= s ? "0" : "1");
if (dur) {
g.text(16, y, (pct + "%").padStart(4), p >= 1 ? "7" : p > 0 ? "0" : "2");
for (let d = 0; d < dur; d++) {
const done = d < p * dur;
g.set(X0 + s + d, y, done ? "█" : "▒", done ? co[0] : co[1]);
}
if (p >= 1) g.set(X0 + s + dur + 1, y, "✓", "7");
} else {
const on = today >= s;
g.set(X0 + s, y, "◆", on ? "c" : "2");
g.text(16, y, on ? "done" : "", "7");
g.text(X0 + s + 2, y, on ? "reached" : "", "c");
}
});
// today marker
const tx = X0 + td;
for (let y = 3; y < rows - 1; y++) if (g.get(tx, y) === " " || g.get(tx, y) === "┊" || g.get(tx, y) === "─") g.set(tx, y, "│", "b");
g.set(tx, 3, "▼", "b");
const date = new Date(Date.UTC(2026, 2, 2 + td));
const label = "today " + ["Jan", "Feb", "Mar", "Apr", "May", "Jun"][date.getUTCMonth()] + " " + date.getUTCDate();
const lx = Math.min(cols - label.length - 1, tx + 2);
g.text(lx, 0, label, "b");
const overall = TASKS.reduce((a, [, s, d]) => a + (d ? clamp((today - s) / d) * d : 0), 0) / TASKS.reduce((a, [, , d]) => a + d, 0);
g.text(1, rows - 1, "overall", "1");
const bw = 12, n = Math.round(overall * bw);
for (let k = 0; k < bw; k++) g.set(9 + k, rows - 1, k < n ? "█" : "░", k < n ? "7" : "2");
g.text(22, rows - 1, Math.round(overall * 100) + "%", "0");
return g.frame();
};
}
/YOUR OWN
The pieces here are hand-built animations. To turn your own photo, logo or video into animated ASCII, use the ASCII Magic editor.