Python Charts

Python plotting and visualization demystified

Add Gradient Fills Under a Line Plot in Matplotlib

Add shaded areas and true vertical gradient fills under Matplotlib line plots using fill_between and imshow with a clip path.

TL;DR

ax.plot(x, y, color='#2A9D8F', lw=2)
ax.fill_between(x, y, alpha=0.25, color='#2A9D8F')

Line plot with a semi-transparent solid fill beneath it

fill_between — the quick option

ax.fill_between(x, y1, y2=0) fills the region between your line and a baseline (zero by default). It is the fastest way to add a shaded area.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 300)
y = np.sin(x) * 0.7 + 1.2

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x, y, color='#2A9D8F', lw=2)
ax.fill_between(x, y, alpha=0.25, color='#2A9D8F')
plt.show()

The alpha argument controls how transparent the fill is. Values around 0.20.35 look natural for most chart styles.

True vertical gradient with imshow + clip path

fill_between produces a flat, uniform colour. For a gradient that fades from opaque at the line to transparent at the baseline, use imshow clipped to the area under the curve.

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.patches import Polygon
import numpy as np

x = np.linspace(0, 2 * np.pi, 300)
y = np.sin(x) * 0.7 + 1.2

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x, y, color='#264653', lw=2.5, zorder=3)

# Gradient: transparent at bottom, teal at top
cmap_fill = LinearSegmentedColormap.from_list(
    'fill_grad',
    [(0, mcolors.to_rgba('#2A9D8F', alpha=0)),
     (1, mcolors.to_rgba('#2A9D8F', alpha=0.75))],
)

gradient = np.linspace(0, 1, 256).reshape(-1, 1)
im = ax.imshow(
    gradient,
    extent=[x.min(), x.max(), 0, y.max()],
    aspect='auto',
    origin='lower',
    cmap=cmap_fill,
    zorder=2,
)

# Clip the image to the polygon formed by the line and baseline
verts = list(zip(x, y)) + [(x[-1], 0), (x[0], 0)]
clip_path = Polygon(verts, closed=True, transform=ax.transData)
im.set_clip_path(clip_path)

ax.set_xlim(x.min(), x.max())
ax.set_ylim(0, y.max() * 1.15)
plt.tight_layout()
plt.show()

Line plot with a true vertical gradient fill fading to transparent at the baseline

How it works:

  1. A 256 × 1 gradient array maps 0 → transparent to 1 → opaque teal.
  2. imshow renders that gradient over the full axes area.
  3. A Polygon traced along the line and back to baseline is used as a clip path, so only the area under the curve is visible.

Shading between two lines with a condition

fill_between accepts a where argument to shade only where a condition is true — useful for showing which of two series is higher:

y2 = np.cos(x) * 0.5 + 1.2

ax.plot(x, y,  color='#2A9D8F', lw=2, label='Upper')
ax.plot(x, y2, color='#E76F51', lw=2, label='Lower')

ax.fill_between(x, y, y2, where=(y >= y2), alpha=0.25, color='#2A9D8F', label='Upper > Lower')
ax.fill_between(x, y, y2, where=(y <  y2), alpha=0.25, color='#E76F51', label='Lower > Upper')

ax.legend()
plt.show()

Two line plots with the area between them shaded in different colors depending on which line is higher

Interpolating the crossing point

By default, fill_between uses the raw data points, which can leave a small unshaded gap right where the lines cross. Pass interpolate=True to fix that:

ax.fill_between(x, y, y2, where=(y >= y2), alpha=0.25,
                color='#2A9D8F', interpolate=True)