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
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
axes.grid is False, and the default line is solid grey at 0.8 width.| Setting | Default | What it controls |
|---|---|---|
axes.grid | False | Whether the grid shows at all |
axes.grid.axis | both | Which axes get lines |
axes.grid.which | major | Major, minor or both ticks |
grid.color | #b0b0b0 | Line colour |
grid.linewidth | 0.8 | Line 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
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
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
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
| Goal | Use |
|---|---|
| Lines at every y tick | ax.grid(axis="y") |
| One line at a value | ax.axhline(y) |
| Part of the width | ax.axhline(y, xmin=0.2, xmax=0.8) |
| A shaded band | ax.axhspan(low, high) |
| A vertical equivalent | ax.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
| Symptom | Cause | Fix |
|---|---|---|
| Grid covers the bars | Gridlines have a higher zorder | ax.set_axisbelow(True) |
| No minor gridlines | No minor ticks exist | ax.minorticks_on() first |
| Grid on the wrong axis | axis not set | axis="y" for horizontal |
| Nothing appears | Called on the wrong axes | Use ax.grid, not plt.grid |
xmin ignored | It is a fraction, not data | Use ax.hlines |
| Grid too dominant | Default solid 0.8 line | Lower alpha, use dashes |
More Matplotlib axis and styling guides:
- Set the y axis to a log scale
- Set the y axis range
- Scatter marker shape, size and color
- Invert the y axis
- Log-log scale, minor ticks and colorbars
- Matplotlib not showing a plot
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.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn