Basic Span Plot (Highlighted Region) — Pygal

A span plot highlights a specific region of interest on a chart using a shaded rectangular area that spans the full height or width of the plot. Vertical spans mark ranges along the x-axis (e.g., time periods), while horizontal spans mark ranges along the y-axis (e.g., value thresholds). The semi-transparent fill allows underlying data to remain visible while drawing attention to the highlighted region.

Basic Span Plot (Highlighted Region) rendered with Pygal

Python source (Pygal)

""" anyplot.ai
span-basic: Basic Span Plot (Highlighted Region)
Library: pygal 3.1.3 | Python 3.13.14
Quality: 88/100 | Created: 2026-07-25
"""

import os
import sys

import numpy as np


# Pop script dir so this file (pygal.py) doesn't shadow the installed pygal package
_script_dir = sys.path.pop(0)
import pygal
from pygal.style import Style


sys.path.insert(0, _script_dir)

# Theme tokens
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"

# Span fill opacity stays within the spec's 0.2-0.3 range on both themes
SPAN_OPACITY = ".3" if THEME == "dark" else ".25"

# Semantic colors (style guide's semantic-exception rule): the recession span is a
# negative economic event (matte red), the risk zone is a warning threshold (amber),
# freeing brand green for the primary stock-price line rather than a secondary span
RECESSION_COLOR = "#AE3030"  # Imprint position 5 - loss / bad-event anchor
RISK_COLOR = "#DDCC77"  # amber semantic anchor - warning / caution
PRICE_COLOR = "#009E73"  # brand green - primary data series

# Data - Stock prices with highlighted events
np.random.seed(42)
dates = np.arange(2006, 2016, 0.1)
price = 100 + np.cumsum(np.random.randn(len(dates)) * 2)
recession_mask = (dates >= 2008) & (dates < 2010)
price[recession_mask] -= np.linspace(0, 30, recession_mask.sum())
price[dates >= 2010] -= 30
price = price + np.abs(price.min()) + 50

# Tight range around the actual price floor/ceiling (~141-204) to avoid dead canvas space
y_min = float(price.min()) - 20
y_max = float(price.max()) + 15

# Risk Zone sits inside the observed price range (not below it) so it visibly
# intersects the line near the recession trough and the post-2010 plateau
risk_low, risk_high = 140.0, 160.0

# Style
custom_style = Style(
    background=PAGE_BG,
    plot_background=PAGE_BG,
    foreground=INK,
    foreground_strong=INK,
    foreground_subtle=INK_MUTED,
    colors=(RECESSION_COLOR, RISK_COLOR, PRICE_COLOR),
    opacity=SPAN_OPACITY,
    opacity_hover=".5",
    title_font_size=66,
    label_font_size=56,
    major_label_font_size=44,
    legend_font_size=44,
    value_font_size=36,
    stroke_width=2.5,
)

# Plot — x-guides disabled for a cleaner grid; human_readable for polished tooltips;
# truncate_legend=-1 keeps "Recession Period" from being clipped in the legend;
# spacing (pygal's uniform title/legend/axis padding knob) is raised from the
# native 10px default so the legend isn't flush against the canvas edge; the
# inline css softens pygal's default solid axis box lines to match the subtle
# gridline treatment (opacity ~0.25) instead of a hard foreground_strong outline
chart = pygal.XY(
    style=custom_style,
    width=3200,
    height=1800,
    title="span-basic · pygal · anyplot.ai",
    x_title="Year",
    y_title="Price ($)",
    show_dots=False,
    show_x_guides=False,
    show_y_guides=True,
    range=(y_min, y_max),
    xrange=(2005.5, 2016.5),
    fill=True,
    stroke=True,
    truncate_legend=-1,
    human_readable=True,
    spacing=28,
    css=("file://style.css", "file://graph.css", "inline:.axis .line { stroke-opacity: .25; }"),
)

# Vertical span: Recession Period (2008-2009) — closed polygon
recession_span = [(2008, y_min), (2008, y_max), (2009, y_max), (2009, y_min), (2008, y_min)]
chart.add("Recession Period", recession_span)

# Horizontal span: Risk Zone (price 140-160) — closed polygon
risk_span = [(2005.5, risk_low), (2005.5, risk_high), (2016.5, risk_high), (2016.5, risk_low), (2005.5, risk_low)]
chart.add("Risk Zone", risk_span)

# Main line — dict format enables per-point custom tooltip labels (pygal interactive feature)
main_data = [{"value": (float(x), float(y)), "label": f"${y:.0f}"} for x, y in zip(dates, price, strict=True)]
chart.add("Stock Price", main_data, fill=False, stroke=True)

# Save
chart.render_to_png(f"plot-{THEME}.png")
with open(f"plot-{THEME}.html", "wb") as f:
    f.write(chart.render())

Part of Basic Span Plot (Highlighted Region) on anyplot.ai.

Other implementations