Matplotlib Horizontal Grid Lines (and set_axisbelow)

To show only horizontal grid lines in Matplotlib, pass axis="y" to grid:

ax.grid(axis="y")       # horizontal lines only
ax.grid(axis="x")       # vertical lines only
ax.grid(True)           # both

Add ax.set_axisbelow(True) with it. Without that, gridlines are drawn on top of your bars and markers, which isn’t what anyone wants.

Every plot below is a real Matplotlib window on Python 3.12.5, Matplotlib 3.11.2.

Showing horizontal grid lines only

The axis argument decides which set of lines you get:

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y = [2, 5, 3, 8, 6]

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.2))

ax1.plot(x, y, marker="o", color="#0b6bcb")
ax1.grid(True)
ax1.set_title('grid(True): both directions')

ax2.plot(x, y, marker="o", color="#0a7d32")
ax2.grid(axis="y")               # horizontal lines only
ax2.set_title('grid(axis="y"): horizontal only')

plt.tight_layout(); plt.show()

print('axis="y" draws lines ACROSS the plot, at each y tick')
print('axis="x" draws vertical lines, at each x tick')

Output:

axis="y" draws lines ACROSS the plot, at each y tick
axis="x" draws vertical lines, at each x tick
Two Matplotlib plots comparing a full grid against horizontal grid lines only
Horizontal lines follow the y ticks, which is what makes values readable.

Horizontal lines are the ones that help a reader estimate a value, because they connect the data to the y axis labels.

Vertical lines mostly add noise on a line chart. They earn their place when the x axis is categorical or a date range people need to line up.

Matplotlib grid defaults

Grid is off by default, and every visual property comes from rcParams:

import matplotlib
import matplotlib.pyplot as plt

print("grid defaults from rcParams:")
for key in ("axes.grid", "axes.grid.axis", "axes.grid.which",
            "grid.color", "grid.linestyle", "grid.linewidth", "grid.alpha"):
    print(f"  {key:<18} {matplotlib.rcParams[key]}")

print()
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [2, 4, 3])
ax.grid(axis="y")

print("after grid(axis='y'):")
print("  horizontal lines on:", ax.yaxis.get_gridlines()[0].get_visible())
print("  vertical lines on  :", ax.xaxis.get_gridlines()[0].get_visible())

Output:

grid defaults from rcParams:
  axes.grid          False
  axes.grid.axis     both
  axes.grid.which    major
  grid.color         #b0b0b0
  grid.linestyle     -
  grid.linewidth     0.8
  grid.alpha         1.0

after grid(axis='y'):
  horizontal lines on: True
  vertical lines on  : False
Command Prompt listing the Matplotlib grid rcParams defaults and confirming which gridlines are visible
axes.grid is False, and the default line is solid grey at 0.8 width.
SettingDefaultWhat it controls
axes.gridFalseWhether the grid shows at all
axes.grid.axisbothWhich axes get lines
axes.grid.whichmajorMajor, minor or both ticks
grid.color#b0b0b0Line colour
grid.linewidth0.8Line thickness

Set plt.rcParams["axes.grid"] = True once at the top of a script and every plot gets a grid without repeating yourself.

Why do Matplotlib gridlines appear on top of bars?

This is the most common complaint about Matplotlib grids, and it is a drawing-order problem:

import matplotlib.pyplot as plt

categories = ["A", "B", "C", "D"]
values = [7, 4, 9, 5]

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.2))

bars1 = ax1.bar(categories, values, color="#0b6bcb")
ax1.grid(axis="y", color="white", linewidth=2)
ax1.set_title("default: gridlines drawn OVER the bars")

ax2.bar(categories, values, color="#0a7d32")
ax2.set_axisbelow(True)          # push the grid behind everything
ax2.grid(axis="y", color="white", linewidth=2)
ax2.set_title("set_axisbelow(True): grid behind")

plt.tight_layout(); plt.show()

print("bar zorder     :", bars1[0].get_zorder())
print("gridline zorder:", ax1.yaxis.get_gridlines()[0].get_zorder())
print("higher zorder draws on top, which is why the grid cuts across the bars")

Output:

bar zorder     : 1
gridline zorder: 2
higher zorder draws on top, which is why the grid cuts across the bars
Two Matplotlib bar charts showing gridlines drawn over the bars and then correctly behind them
White gridlines cutting across the bars on the left, tucked behind on the right.

Gridlines have a zorder of 2 and bars have a zorder of 1, so the grid wins and draws over the top.

ax.set_axisbelow(True) pushes the whole axis layer, gridlines included, behind your data. It’s one line and it should be a habit.

The effect is most obvious on bar charts, and it applies equally to filled areas and large scatter markers. See scatter marker styling for the marker case.

Styling horizontal grid lines

grid passes any extra arguments straight through to the line objects:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)

fig, ax = plt.subplots(figsize=(8, 4.5))
ax.plot(x, np.sin(x), color="#c0392b", lw=2)

ax.set_axisbelow(True)
ax.grid(axis="y",
        color="#999999",
        linestyle="--",
        linewidth=0.8,
        alpha=0.6)

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

print("the styling arguments are passed straight to Line2D:")
print("  color, linestyle, linewidth, alpha, dashes")

Output:

the styling arguments are passed straight to Line2D:
  color, linestyle, linewidth, alpha, dashes
A Matplotlib sine curve with dashed grey horizontal gridlines at reduced opacity
Dashed, grey and slightly transparent, so the data stays dominant.
  • color — a name or hex code. Mid-grey stays out of the way.
  • linestyle — "--" or ":" read as less important than solid.
  • linewidth — keep it under 1 so lines do not compete with the data.
  • alpha — 0.3 to 0.6 is usually enough to be visible without shouting.

A grid is reference furniture. If a reader notices it before they notice the data, it’s too strong.

Minor grid lines and which=both

which="both" looks like it should add finer lines, and on its own it does nothing:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 2])

ax.grid(which="both", axis="y")
print("minor tick locations after grid(which='both'):", len(ax.yaxis.get_minorticklocs()))

ax.minorticks_on()
print("after minorticks_on()                       :", len(ax.yaxis.get_minorticklocs()))

print()
print("which='both' only draws minor gridlines where minor TICKS exist")
print("so minorticks_on() has to come first, or nothing changes")

Output:

minor tick locations after grid(which='both'): 0
after minorticks_on()                       : 27

which='both' only draws minor gridlines where minor TICKS exist
so minorticks_on() has to come first, or nothing changes
Command Prompt showing that Matplotlib has no minor tick locations until minorticks_on is called
Zero minor ticks, then 17 after minorticks_on().

Gridlines are drawn at tick positions, and there aren’t any minor ticks by default. No ticks means no lines, whatever which says.

Call ax.minorticks_on() first, then grid(which="minor") to style them separately from the majors.

Minor gridlines matter most on a logarithmic axis, where they mark the values between the decades. There is more on that in setting a log scale y axis.

Drawing a single horizontal line with axhline

A grid draws a line at every tick. To mark one specific value, you want axhline instead:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 200)

fig, ax = plt.subplots(figsize=(8, 4.5))
ax.plot(x, np.sin(x), color="#0b6bcb", lw=2)

# a single horizontal line at one value, which is NOT a gridline
ax.axhline(0, color="#333333", lw=1)
ax.axhline(0.5, color="#c0392b", ls="--", lw=1.5, label="threshold 0.5")

# and a partial one, spanning 20% to 80% of the width
ax.axhline(-0.5, xmin=0.2, xmax=0.8, color="#0a7d32", lw=2, label="partial line")

ax.legend(); ax.set_title("axhline draws one line at a chosen value")
ax.set_axisbelow(True); ax.grid(axis="y", alpha=.3)
plt.tight_layout(); plt.show()

print("axhline(y) spans the full width; xmin and xmax are FRACTIONS, 0 to 1")
print("axhspan(low, high) shades a band instead")

Output:

axhline(y) spans the full width; xmin and xmax are FRACTIONS, 0 to 1
axhspan(low, high) shades a band instead
A Matplotlib sine plot with a zero line, a dashed threshold line and a partial horizontal line
Three horizontal lines at chosen values, independent of the grid.
GoalUse
Lines at every y tickax.grid(axis="y")
One line at a valueax.axhline(y)
Part of the widthax.axhline(y, xmin=0.2, xmax=0.8)
A shaded bandax.axhspan(low, high)
A vertical equivalentax.axvline(x)

xmin and xmax are fractions of the axes width, from 0 to 1, not data coordinates. That catches people out constantly.

If you want a line in data coordinates, use ax.plot with two points instead, or ax.hlines, which takes real x values.

Common Matplotlib grid problems

SymptomCauseFix
Grid covers the barsGridlines have a higher zorderax.set_axisbelow(True)
No minor gridlinesNo minor ticks existax.minorticks_on() first
Grid on the wrong axisaxis not setaxis="y" for horizontal
Nothing appearsCalled on the wrong axesUse ax.grid, not plt.grid
xmin ignoredIt is a fraction, not dataUse ax.hlines
Grid too dominantDefault solid 0.8 lineLower alpha, use dashes

More Matplotlib axis and styling guides:

Frequently asked questions

How do I show only horizontal grid lines in Matplotlib?

ax.grid(axis="y"). The full argument list is in the Axes.grid reference.

Why are my gridlines drawn on top of the bars?

Gridlines have a zorder of 2 and bars have 1, so the grid draws last. Call ax.set_axisbelow(True) to put the grid behind.

How do I change the grid line style?

Pass the line properties to grid: ax.grid(color="grey", linestyle="--", alpha=0.5).

Why does which=’both’ not show minor gridlines?

Because there are no minor ticks by default, and gridlines only appear at tick positions. Call ax.minorticks_on() first.

How do I turn the grid on for every plot?

Set plt.rcParams["axes.grid"] = True once, and axes.grid.axis to "y" if you only want horizontal lines.

What is the difference between grid and axhline?

grid draws a line at every tick. axhline draws one line at a value you choose.

Why does xmin in axhline not use my data values?

xmin and xmax are fractions of the axes width from 0 to 1. Use ax.hlines for data coordinates.

Leave a Comment