Basic Funnel Chart — Pygal

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 Pygal

Python source (Pygal)

""" anyplot.ai
funnel-basic: Basic Funnel Chart
Library: pygal 3.1.0 | Python 3.13.13
Quality: 87/100 | Updated: 2026-06-16
"""

import os

import pygal
from pygal.style import Style


# Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome")
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Imprint palette — first series is ALWAYS brand green (#009E73)
IMPRINT_PALETTE = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030")

# Data — sales conversion funnel, stages ordered largest (top) → smallest (bottom)
# Deterministic data, no random seed needed
stages = ["Awareness", "Interest", "Consideration", "Intent", "Purchase"]
values = [1000, 600, 400, 200, 100]
base_value = values[0]

# Style — pygal's Style carries every theme token. Font sizes are unitless and
# render straight onto the 3200-px source grid (see default-style-guide.md
# "Why the Native-pixel numbers look so much bigger").
custom_style = Style(
    background=PAGE_BG,
    plot_background=PAGE_BG,
    foreground=INK,
    foreground_strong=INK,
    foreground_subtle=INK_MUTED,
    colors=IMPRINT_PALETTE,
    title_font_size=66,
    label_font_size=56,
    major_label_font_size=44,
    legend_font_size=48,
    value_font_size=44,
    tooltip_font_size=40,
    opacity=1,
)

# pygal's Funnel pads its y-box to [-max, +max] while the tallest segment only
# spans [-max/2, +max/2], so by default the funnel fills just half the plot
# height. An explicit symmetric range tightens that padding to ~0.6·max, giving
# the funnel a more commanding vertical presence without clipping any segment.
funnel_half = base_value * 0.6

# Plot — pygal's native Funnel chart; each stage is its own series so the
# segments carry distinct Imprint colors and the legend names every stage.
chart = pygal.Funnel(
    width=3200,
    height=1800,
    title="funnel-basic · python · pygal · anyplot.ai",
    style=custom_style,
    print_values=True,
    value_formatter=lambda v: f"{v:,.0f}  ({v / base_value * 100:.0f}%)",
    range=(-funnel_half, funnel_half),
    margin=50,
    margin_bottom=15,
    show_legend=True,
    legend_at_bottom=True,
    legend_at_bottom_columns=5,
    legend_box_size=36,
    show_x_labels=False,
    show_y_labels=False,
    show_y_guides=False,
    truncate_legend=-1,
)

# Each stage added as a single-value series — the value (and conversion %) is
# printed on the segment via the value_formatter above.
for stage, value in zip(stages, values, strict=True):
    chart.add(stage, value)

# Save — theme-suffixed PNG (cairosvg) plus interactive HTML (pygal is interactive)
chart.render_to_png(f"plot-{THEME}.png")
with open(f"plot-{THEME}.html", "wb") as f:
    f.write(chart.render())

Part of Basic Funnel Chart on anyplot.ai.

Other implementations