W3docs

Matplotlib Grid

Learn how to create multi-plot grid layouts and add background gridlines in Matplotlib using subplots, GridSpec, subplot_mosaic, and grid styling options.

Matplotlib supports two distinct uses of the word "grid": multi-plot grid layouts (arranging several axes side by side in a figure) and background gridlines (the reference lines drawn behind the data on a single axis). This chapter covers both, starting with layout grids and ending with gridline styling.

You should already be comfortable with basic plotting — if not, see the Matplotlib Plotting and Matplotlib Subplot chapters first.

Multi-Plot Grid Layouts

When you need to display several related plots in one figure, you arrange them in a grid of rows and columns. Matplotlib offers three approaches, each suited to different levels of complexity.

plt.subplots() — the quick way

plt.subplots(nrows, ncols) is the fastest route for uniform grids where every subplot is the same size. It returns a Figure and a NumPy array of Axes.

import matplotlib.pyplot as plt

# 2-row, 2-column grid — four equal subplots
fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(8, 6))

axs[0, 0].plot([1, 2, 3], [4, 5, 6])
axs[0, 0].set_title("Line plot")

axs[0, 1].scatter([1, 2, 3], [4, 5, 6])
axs[0, 1].set_title("Scatter plot")

axs[1, 0].bar([1, 2, 3], [4, 5, 6])
axs[1, 0].set_title("Bar chart")

axs[1, 1].hist([1, 2, 3, 4, 5, 6], bins=3)
axs[1, 1].set_title("Histogram")

plt.tight_layout()
plt.show()

tight_layout() automatically adjusts spacing so titles and tick labels do not overlap. Pass figsize=(width, height) in inches to control the overall figure size.

Sharing axes

When plots share the same scale you can link their axes so that zooming one zooms all:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 100)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 3), sharey=True)

ax1.plot(x, np.sin(x))
ax1.set_title("sin(x)")

ax2.plot(x, np.cos(x))
ax2.set_title("cos(x)")

plt.tight_layout()
plt.show()

sharey=True links the y-axis range across both subplots; sharex=True does the same for the x-axis.

GridSpec — variable-size cells

GridSpec lets you assign different weights to rows and columns so that some subplots can be taller or wider than others. It also lets a single subplot span multiple cells.

import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec

fig = plt.figure(figsize=(9, 6))

# 2 rows, 3 columns; bottom row is twice as tall
gs = GridSpec(nrows=2, ncols=3, height_ratios=[1, 2], hspace=0.4, wspace=0.3)

ax_top_left   = fig.add_subplot(gs[0, 0])
ax_top_mid    = fig.add_subplot(gs[0, 1])
ax_top_right  = fig.add_subplot(gs[0, 2])
ax_bottom     = fig.add_subplot(gs[1, :])   # spans all three columns

ax_top_left.plot([0, 1, 2], [0, 1, 0])
ax_top_left.set_title("Top left")

ax_top_mid.bar([0, 1, 2], [3, 1, 4])
ax_top_mid.set_title("Top centre")

ax_top_right.scatter([0, 1, 2], [2, 7, 1])
ax_top_right.set_title("Top right")

ax_bottom.plot([0, 1, 2, 3, 4], [0, 1, 4, 9, 16], color="steelblue", linewidth=2)
ax_bottom.set_title("Bottom — full width")

plt.show()

Key GridSpec arguments:

ArgumentWhat it does
width_ratios=[2, 1, 1]Column widths as relative proportions
height_ratios=[1, 2]Row heights as relative proportions
hspace=0.4Vertical gap between rows (fraction of axis height)
wspace=0.3Horizontal gap between columns (fraction of axis width)

Slicing with gs[row, col] works just like NumPy: gs[1, :] spans all columns; gs[:, 0] spans all rows in the first column.

subplot_mosaic() — layout from a string

Introduced in Matplotlib 3.3, subplot_mosaic() lets you describe the layout with a string or nested list that looks like an ASCII map of the figure. This is often the most readable approach for asymmetric layouts.

import matplotlib.pyplot as plt

layout = """
AB
CC
"""

fig, axes = plt.subplot_mosaic(layout, figsize=(8, 5))

axes["A"].set_title("Panel A")
axes["A"].plot([1, 2, 3], [3, 1, 2])

axes["B"].set_title("Panel B")
axes["B"].scatter([1, 2, 3], [2, 3, 1])

axes["C"].set_title("Panel C — full width")
axes["C"].bar(["x", "y", "z"], [5, 3, 7])

plt.tight_layout()
plt.show()

Each unique letter in the string becomes one Axes. Repeated letters in the same row/column cause that subplot to span those cells. The returned axes dict is keyed by label, so accessing panels by name is clearer than numeric indexing.

Controlling spacing: tight_layout vs constrained_layout

Two built-in systems handle automatic spacing:

  • plt.tight_layout() — call it before plt.show(). Adjusts subplot parameters so labels/titles fit. Simple and reliable for most layouts.
  • constrained_layout=True — pass it to plt.subplots() or plt.figure(). Solves a constraint system at draw time; handles colorbars and suptitles better than tight_layout.
import matplotlib.pyplot as plt

# constrained_layout keeps the super-title from overlapping the subplots
fig, axs = plt.subplots(1, 2, figsize=(8, 3), constrained_layout=True)

fig.suptitle("Two plots with constrained_layout", fontsize=13)

axs[0].plot([1, 2, 3], [1, 4, 9])
axs[0].set_title("Subplot 1")

axs[1].plot([1, 2, 3], [9, 4, 1])
axs[1].set_title("Subplot 2")

plt.show()

Do not use both at the same time — they conflict. constrained_layout is the more modern choice.

For manual control, fig.subplots_adjust(left, right, top, bottom, hspace, wspace) overrides automatic spacing entirely.

Background Gridlines

On a single axes, ax.grid() draws reference lines at the tick positions. These are different from the layout grid described above — they are visual guides drawn behind the plotted data.

Enabling gridlines

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 4))

ax.plot([1, 2, 3, 4, 5], [2, 5, 3, 8, 6], marker="o")

# Enable gridlines on both axes
ax.grid(True)

ax.set_title("Plot with gridlines")
plt.tight_layout()
plt.show()

Styling gridlines

Pass keyword arguments to ax.grid() to change appearance:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 4))

ax.plot([1, 2, 3, 4, 5], [2, 5, 3, 8, 6], marker="o", color="steelblue")

ax.grid(
    True,
    which="major",      # "major", "minor", or "both"
    axis="both",        # "x", "y", or "both"
    color="gray",
    linestyle="--",
    linewidth=0.7,
    alpha=0.7,
)

ax.set_title("Styled gridlines")
plt.tight_layout()
plt.show()

Major and minor gridlines

Matplotlib distinguishes major ticks (the labelled ones) from minor ticks (unlabelled subdivisions). You can show gridlines at both levels:

import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator

fig, ax = plt.subplots(figsize=(7, 4))

ax.plot([0, 1, 2, 3, 4, 5, 6], [0, 1, 4, 9, 16, 25, 36])

# Major gridlines every 1 unit, minor every 0.5
ax.xaxis.set_major_locator(MultipleLocator(1))
ax.xaxis.set_minor_locator(MultipleLocator(0.5))
ax.yaxis.set_major_locator(MultipleLocator(10))
ax.yaxis.set_minor_locator(MultipleLocator(5))

ax.grid(True, which="major", linestyle="-",  linewidth=0.8, color="gray",  alpha=0.6)
ax.grid(True, which="minor", linestyle=":",  linewidth=0.5, color="silver", alpha=0.5)

ax.set_title("Major and minor gridlines")
plt.tight_layout()
plt.show()

Enabling minor ticks without the locator

If you just want minor ticks shown without placing them manually:

ax.minorticks_on()
ax.grid(True, which="both")

Styling with Matplotlib style sheets

Gridlines are also controlled by style sheets. The seaborn-v0_8-whitegrid style, for example, enables horizontal gridlines automatically:

import matplotlib.pyplot as plt

plt.style.use("seaborn-v0_8-whitegrid")

fig, ax = plt.subplots(figsize=(6, 4))
ax.plot([1, 2, 3, 4], [3, 7, 2, 9], marker="s")
ax.set_title("seaborn-v0_8-whitegrid style")
plt.tight_layout()
plt.show()

Run plt.style.available to list all built-in styles.

Choosing the Right Approach

GoalBest tool
Equal-size subplot gridplt.subplots(nrows, ncols)
Subplots with different sizes or spanning cellsGridSpec
Asymmetric layout described visuallysubplot_mosaic()
Reference lines behind dataax.grid()
Fine-grained tick placement for gridlinesMultipleLocator + ax.grid(which=...)
Was this page helpful?