Basic Funnel Chart — Chart.js

A funnel chart visualizes sequential stages of a process where values progressively decrease from one stage to the next. Each stage is represented as a trapezoidal segment that narrows from top to bottom, making it easy to identify drop-offs between stages. This chart is ideal for tracking conversion rates, sales pipelines, and multi-step processes where understanding stage-to-stage transitions is critical.

Basic Funnel Chart rendered with Chart.js

Renders

JavaScript source (Chart.js)

// anyplot.ai
// funnel-basic: Basic Funnel 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) ----------------------------------------
// E-commerce checkout funnel: visitors thinning out at each subsequent step.
const stages = [
  "Website Visitors",
  "Product Page Views",
  "Added to Cart",
  "Checkout Started",
  "Purchase Completed",
];
const values = [12000, 6400, 3100, 1450, 820];

// Floating (centered) horizontal bars: each spans [-value/2, value/2], so
// stacking them by decreasing value produces the tapering funnel silhouette.
const segments = values.map((v) => [-v / 2, v / 2]);
const maxHalfWidth = values[0] / 2;
const colors = stages.map((_, i) => t.palette[i % t.palette.length]);

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

// --- Funnel shape + label plugin (native Chart.js plugin API) ---------------
// The underlying "bar" dataset only supplies layout (category bands, x scale
// pixel mapping) and is drawn fully transparent. This plugin paints the real
// funnel silhouette: each stage is a trapezoid whose top width is its own
// value and whose bottom width is the *next* stage's value, so consecutive
// segments share an identical edge and the whole shape tapers continuously
// instead of stacking as separated rectangles.
const funnelShape = {
  id: "funnelShape",
  afterDatasetsDraw(chart) {
    const { ctx, scales } = chart;
    const meta = chart.getDatasetMeta(0);
    const centerX = scales.x.getPixelForValue(0);
    const halfWidthPx = values.map(
      (v) => scales.x.getPixelForValue(v / 2) - centerX,
    );

    ctx.save();
    meta.data.forEach((bar, i) => {
      const { y, height } = bar.getProps(["y", "height"], true);
      const top = y - height / 2;
      const bottom = y + height / 2;
      const topHalf = halfWidthPx[i];
      const bottomHalf =
        i < values.length - 1 ? halfWidthPx[i + 1] : halfWidthPx[i];

      ctx.beginPath();
      ctx.moveTo(centerX - topHalf, top);
      ctx.lineTo(centerX + topHalf, top);
      ctx.lineTo(centerX + bottomHalf, bottom);
      ctx.lineTo(centerX - bottomHalf, bottom);
      ctx.closePath();
      ctx.fillStyle = colors[i];
      ctx.fill();

      if (i > 0) {
        ctx.strokeStyle = t.pageBg;
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(centerX - topHalf, top);
        ctx.lineTo(centerX + topHalf, top);
        ctx.stroke();
      }
    });

    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.font = "700 20px sans-serif";
    ctx.fillStyle = "#FFFFFF";
    ctx.shadowColor = "rgba(26, 26, 23, 0.5)";
    ctx.shadowBlur = 6;
    ctx.shadowOffsetY = 1;
    meta.data.forEach((bar, i) => {
      const { x, y } = bar.getCenterPoint();
      const share = Math.round((values[i] / values[0]) * 100);
      const label = `${values[i].toLocaleString()} (${share}%)`;
      ctx.fillText(label, x, y);
    });
    ctx.restore();
  },
};

// --- Chart -------------------------------------------------------------------
new Chart(canvas, {
  type: "bar",
  data: {
    labels: stages,
    datasets: [
      {
        data: segments,
        backgroundColor: "transparent",
        borderWidth: 0,
      },
    ],
  },
  options: {
    indexAxis: "y",
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    barPercentage: 1,
    categoryPercentage: 1,
    plugins: {
      title: {
        display: true,
        text: "funnel-basic · javascript · chartjs · anyplot.ai",
        color: t.ink,
        font: { size: 22, weight: "500" },
        padding: { bottom: 24 },
      },
      legend: { display: false },
    },
    scales: {
      x: {
        display: false,
        min: -maxHalfWidth * 1.05,
        max: maxHalfWidth * 1.05,
        grid: { display: false },
        border: { display: false },
      },
      y: {
        ticks: { color: t.inkSoft, font: { size: 16 } },
        grid: { display: false },
        border: { display: false },
      },
    },
  },
  plugins: [funnelShape],
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/funnel-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": "funnel-basic",
  "language": "javascript",
  "library": "chartjs",
  "page": "https://anyplot.ai/funnel-basic/javascript/chartjs",
  "hub": "https://anyplot.ai/funnel-basic",
  "code_json": "https://api.anyplot.ai/specs/funnel-basic/chartjs/code",
  "spec_json": "https://api.anyplot.ai/specs/funnel-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/funnel-basic/javascript/chartjs/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/funnel-basic/javascript/chartjs/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/funnel-basic/javascript/chartjs/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/funnel-basic/javascript/chartjs/plot-dark.html",
  "quality_score": 92.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Basic Funnel Chart on anyplot.ai.

Other implementations