Basic Gantt Chart — Chart.js

A Gantt chart is a horizontal bar chart that visualizes project schedules and timelines. Each task is represented as a horizontal bar spanning from its start date to end date, making it easy to see task durations, overlaps, and the overall project timeline at a glance. Gantt charts are essential for project management, helping teams understand task sequences and identify scheduling conflicts.

Basic Gantt Chart rendered with Chart.js

Renders

JavaScript source (Chart.js)

// anyplot.ai
// gantt-basic: Basic Gantt Chart
// Library: chartjs 4.4.7 | JavaScript 22.23.2
// Quality: 92/100 | Created: 2026-09-05

const t = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic) ----------------------------------------
// A software-release schedule. Chart.js ships no date-scale adapter in this
// runtime, so dates are converted to day offsets from PROJECT_START and the
// linear x-axis ticks are formatted back into calendar dates.
const PROJECT_START = new Date(Date.UTC(2026, 0, 5)); // 2026-01-05
const REFERENCE_DATE = new Date(Date.UTC(2026, 2, 2)); // "current date" marker

const dayOffset = (date) => Math.round((date - PROJECT_START) / 86400000);

const CATEGORIES = ["Planning", "Design", "Development", "Testing", "Launch"];

const tasks = [
  { task: "Requirements Gathering", start: Date.UTC(2026, 0, 5), end: Date.UTC(2026, 0, 16), category: "Planning" },
  { task: "Stakeholder Review", start: Date.UTC(2026, 0, 12), end: Date.UTC(2026, 0, 19), category: "Planning" },
  { task: "Wireframes", start: Date.UTC(2026, 0, 19), end: Date.UTC(2026, 1, 2), category: "Design" },
  { task: "UI Design", start: Date.UTC(2026, 0, 26), end: Date.UTC(2026, 1, 13), category: "Design" },
  { task: "Design Review", start: Date.UTC(2026, 1, 9), end: Date.UTC(2026, 1, 16), category: "Design" },
  { task: "Backend API", start: Date.UTC(2026, 1, 16), end: Date.UTC(2026, 2, 20), category: "Development" },
  { task: "Frontend Build", start: Date.UTC(2026, 1, 23), end: Date.UTC(2026, 2, 27), category: "Development" },
  { task: "Integration", start: Date.UTC(2026, 2, 20), end: Date.UTC(2026, 3, 3), category: "Development" },
  { task: "QA Testing", start: Date.UTC(2026, 3, 3), end: Date.UTC(2026, 3, 17), category: "Testing" },
  { task: "Bug Fixes", start: Date.UTC(2026, 3, 10), end: Date.UTC(2026, 3, 21), category: "Testing" },
  { task: "Launch Prep", start: Date.UTC(2026, 3, 21), end: Date.UTC(2026, 3, 28), category: "Launch" },
  { task: "Go Live", start: Date.UTC(2026, 3, 28), end: Date.UTC(2026, 4, 1), category: "Launch" },
].map((row) => ({ ...row, start: dayOffset(new Date(row.start)), end: dayOffset(new Date(row.end)) }));

const totalDays = Math.max(...tasks.map((row) => row.end)) + 5;
const todayOffset = dayOffset(REFERENCE_DATE);
const isActive = (row) => row.start <= todayOffset && todayOffset <= row.end;

const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function formatDayOffset(offset) {
  const d = new Date(PROJECT_START.getTime() + Math.round(offset) * 86400000);
  return `${MONTHS[d.getUTCMonth()]} ${d.getUTCDate()}`;
}

// --- Mount -------------------------------------------------------------------
const canvas = document.createElement("canvas");
document.getElementById("container").appendChild(canvas);

// --- Chart -------------------------------------------------------------------
// Subtle alternating row bands (drawn behind the grid/bars) to make it easier
// to trace a task row across the full timeline width.
const rowBandPlugin = {
  id: "rowBands",
  beforeDraw(chart) {
    const { ctx, chartArea, scales } = chart;
    const rowHeight = chartArea.height / tasks.length;
    ctx.save();
    ctx.fillStyle = t.elevatedBg;
    tasks.forEach((_, i) => {
      if (i % 2 !== 0) return;
      const center = scales.y.getPixelForValue(i);
      ctx.fillRect(chartArea.left, center - rowHeight / 2, chartArea.width, rowHeight);
    });
    ctx.restore();
  },
};

// Compact "Nd" duration labels next to each bar, with in-progress tasks
// (those straddling REFERENCE_DATE) called out as the chart's focal point.
const durationLabelPlugin = {
  id: "durationLabels",
  afterDatasetsDraw(chart) {
    const { ctx, chartArea } = chart;
    const meta = chart.getDatasetMeta(0);
    ctx.save();
    ctx.textBaseline = "middle";
    meta.data.forEach((bar, i) => {
      const row = tasks[i];
      const active = isActive(row);
      const label = `${row.end - row.start}d${active ? " · in progress" : ""}`;
      ctx.font = `${active ? "bold " : ""}11px -apple-system, BlinkMacSystemFont, sans-serif`;
      ctx.fillStyle = active ? t.ink : t.inkSoft;
      const labelWidth = ctx.measureText(label).width;
      if (bar.x + 6 + labelWidth <= chartArea.right) {
        ctx.textAlign = "left";
        ctx.fillText(label, bar.x + 6, bar.y);
      } else {
        ctx.textAlign = "right";
        ctx.fillText(label, bar.base - 6, bar.y);
      }
    });
    ctx.restore();
  },
};

// A dashed reference line marks REFERENCE_DATE using the theme's ink color
// (the "neutral" semantic anchor — reference lines share the text/grid hue).
const referenceLinePlugin = {
  id: "referenceLine",
  afterDraw(chart) {
    const { x: xScale, y: yScale } = chart.scales;
    const refX = xScale.getPixelForValue(dayOffset(REFERENCE_DATE));
    if (refX < xScale.left || refX > xScale.right) return;
    const { ctx } = chart;
    ctx.save();
    ctx.strokeStyle = t.ink;
    ctx.setLineDash([6, 4]);
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(refX, yScale.top);
    ctx.lineTo(refX, yScale.bottom);
    ctx.stroke();
    ctx.setLineDash([]);
    ctx.fillStyle = t.ink;
    ctx.font = "14px -apple-system, BlinkMacSystemFont, sans-serif";
    ctx.textAlign = "left";
    ctx.textBaseline = "bottom";
    ctx.fillText("Today", refX + 8, yScale.bottom - 6);
    ctx.restore();
  },
};

new Chart(canvas, {
  type: "bar",
  data: {
    labels: tasks.map((row) => row.task),
    datasets: [
      {
        label: "Tasks",
        data: tasks.map((row) => [row.start, row.end]),
        backgroundColor: tasks.map((row) => t.palette[CATEGORIES.indexOf(row.category) % t.palette.length]),
        borderColor: tasks.map(() => t.ink),
        borderWidth: tasks.map((row) => (isActive(row) ? 3 : 0)),
        borderRadius: 4,
        borderSkipped: false,
        barPercentage: 0.6,
        categoryPercentage: 0.7,
      },
    ],
  },
  options: {
    indexAxis: "y",
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    layout: { padding: { top: 24 } },
    plugins: {
      title: {
        display: true,
        text: "gantt-basic · javascript · chartjs · anyplot.ai",
        color: t.ink,
        font: { size: 22 },
      },
      legend: {
        labels: {
          color: t.ink,
          font: { size: 16 },
          generateLabels: () =>
            CATEGORIES.map((category, i) => ({
              text: category,
              fillStyle: t.palette[i % t.palette.length],
              strokeStyle: t.palette[i % t.palette.length],
              lineWidth: 0,
            })),
        },
        onClick: () => {},
      },
      tooltip: { enabled: false },
    },
    scales: {
      x: {
        type: "linear",
        min: 0,
        max: totalDays,
        ticks: {
          stepSize: 14,
          color: t.inkSoft,
          font: { size: 14 },
          callback: formatDayOffset,
        },
        grid: { color: t.grid },
        title: { display: true, text: "Timeline (2026)", color: t.ink, font: { size: 16 } },
      },
      y: {
        ticks: { color: t.inkSoft, font: { size: 15 } },
        grid: { display: false },
      },
    },
  },
  plugins: [rowBandPlugin, durationLabelPlugin, referenceLinePlugin],
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/gantt-basic/chartjs/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "gantt-basic",
  "language": "javascript",
  "library": "chartjs",
  "page": "https://anyplot.ai/gantt-basic/javascript/chartjs",
  "hub": "https://anyplot.ai/gantt-basic",
  "code_json": "https://api.anyplot.ai/specs/gantt-basic/chartjs/code",
  "spec_json": "https://api.anyplot.ai/specs/gantt-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/gantt-basic/javascript/chartjs/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/gantt-basic/javascript/chartjs/plot-dark.png",
  "quality_score": 92.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Basic Gantt Chart on anyplot.ai.

Other implementations