18

I am trying to to plot scatter points on a grid using python's matplotlib (1.4.3 but tried on other versions without success too). Everything works except the grid zorder position. I set grid's zorder to be lower that that of scatter but the grid still covers the scatter points.

Here is a simple code to reproduce the problem:

import numpy as np
from matplotlib import pyplot as plt

# create points
pnts = np.array([[1., 1.], [1., 1.2]])

# create figure
plt.grid(True, linestyle='-', color='r', zorder=1)
plt.scatter(pnts[:,0], pnts[:,1], zorder=2)
plt.ylim([0, 2])
plt.xlim([0, 2])
plt.savefig('testfig.png', dpi=350)

I get this:
If you zoom in on the dots, you can see the grid is on top, although it has lower zorder: enter image description here

Is this a bug or am I doing something wrong?

mmagnuski
  • 1,249
  • 1
  • 11
  • 23
  • 4
    Try changing the one you want on top to something larger, like `zorder=10` – Scott Jul 19 '15 at 23:37
  • Thanks, that worked! Why is `zorder=2` not enough? – mmagnuski Jul 20 '15 at 00:31
  • I don't know why `zorder=2` is not enough. I've had to do the same many times. – Scott Jul 20 '15 at 00:40
  • 1
    From [this](http://stackoverflow.com/questions/31222346/draw-minor-grid-lines-below-major-gridlines), etc., I suspect this is a bug -- that matplotlib uses 0 and 1 for the grid lines and doesn't pay attention to their z-order argument. But I haven't actually looked yet. – cphlewis Jul 20 '15 at 19:37

3 Answers3

13

In case anyone is still wondering about this years later (like myself), this is a long standing issue (Sept 2015):

Axes.grid() not honoring specified "zorder" kwarg

Quoting the issue:

Axes.grid() is not honoring a provided custom zorder. Instead the grid's zorder seems to be fixed at some miraculous value in between 2.5 and 2.6 (I could not find a key for grid zorder in rcParams).

Apparently the only solution so far is to:

just add 2.5 to all of your z-orders.

There's also an old answer in this question Matplotlib: draw grid lines behind other graph elements but it does not seem to work anymore.

Gabriel
  • 40,504
  • 73
  • 230
  • 404
  • 2
    You can also set a negative value for the z-order (ie, `zorder=-1`) for elements that aren't playing nicely, to force them to the back. – MMelnicki Feb 20 '19 at 18:18
  • 6
    Both adding 2.5 and setting one zorder value to -1 don't work for at least one plot I've tried this on (matplotlib 2.2.3), I don't think this is an actual solution to the problem. – Peter Apr 20 '19 at 17:27
  • In early 2021, this still doesn't work nicely. Minor gridlines are drawn on top of major gridlines regardless of zorder of either. – bfris Feb 18 '21 at 16:38
  • Vertical lines I have placed with -1 zorders aren't working at the moment. – Sanch Mar 04 '21 at 08:57
3

A couple years later, the issue quoted by Gabriel has been updated with the following and closed as of April 2019.

This would have to be part of a major overhaul of the tick and gridline system, which I suspect might have to be just one component of an even more major refactoring, so I think the solution for the foreseeable future is the documentation tweak in #13995.

One workaround for cases where you need a grid with arbitrary zorder values is to emulate grid() using plot(). I wrote the function to replicate the standard grid() behavior as closely as possible (using the rcParam values by default).

import matplotlib.pyplot as plt
from matplotlib import rcParams
import numpy as np

# emulate grid using Axes.plot()
def zorder_grid(ax=None, zorder=1.5, which=None, axis=None, alpha=None,
                color=None, linestyle=None, linewidth=None, **kwargs):
    # Honor rcParams values if keywords not specified
    if ax is None:
        ax = plt.gca()
    if which is None:
        which = rcParams['axes.grid.which']
    if axis is None:
        axis = rcParams['axes.grid.axis']
    if alpha is None:
        alpha = rcParams['grid.alpha']
    if color is None:
        color = rcParams['grid.color']
    if linestyle is None:
        linestyle = rcParams['grid.linestyle']
    if linewidth is None:
        linewidth = rcParams['grid.linewidth']

    # get coordinates for grid lines
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    xticks = []
    yticks = []
    if which in ['major', 'both']:
        xticks = np.concatenate((xticks, ax.get_xticks()))
        yticks = np.concatenate((yticks, ax.get_yticks()))
    if which in ['minor', 'both']:
        xticks = np.concatenate((xticks, ax.get_xticks(minor=True)))
        yticks = np.concatenate((yticks, ax.get_yticks(minor=True)))

    # plot grid using Axes.plot()
    if axis in ['x', 'both']:
        for tick in xticks:
            ax.plot([tick, tick], ylim, linestyle=linestyle, color=color,
                    linewidth=linewidth, alpha=alpha, zorder=zorder, **kwargs)
    if axis in ['y', 'both']:
        for tick in yticks:
            ax.plot(xlim, [tick, tick], linestyle=linestyle, color=color,
                    linewidth=linewidth, alpha=alpha, zorder=zorder, **kwargs)

Caveats:

  1. The x and y limits must be manually set before calling zorder_grid(). If specifying the ticks manually, that would also need to be done prior to plotting grid lines.
  2. This will have issues if the axis limits and/or ticks change dynamically (e.g. animations). Possible solutions might be clearing the axis between frames or returning a Line2D list and toggling visibility.

The function can be called as follows to produce the desired output in the MWE (any zorder < 1 would work).

pnts = np.array([[1., 1.], [1., 1.2]])
plt.scatter(pnts[:,0], pnts[:,1])
plt.ylim([0, 2])
plt.xlim([0, 2])
zorder_grid(zorder=0.99, linestyle='-', color='r')
plt.savefig('testfig.png', dpi=350)

Figure generated by the above code.

Figure generated by the above code.

gil
  • 81
  • 4
3

The following works for me:

ax.set_axisbelow(True)

From the "Notes" section in the docs for Axes.grid:

The axis is drawn as a unit, so the effective zorder for drawing the grid is determined by the zorder of each axis, not by the zorder of the Line2D objects comprising the grid. Therefore, to set grid zorder, use set_axisbelow or, for more control, call the set_zorder method of each axis.

The default value for this setting is

>>> import matplotlib as mpl
>>> mpl.rcParams["axes.axisbelow"]
'line'

According to the docs for Axes.set_axisbelow the possible values are:

  • True (zorder = 0.5): Ticks and gridlines are below all Artists.
  • 'line' (zorder = 1.5): Ticks and gridlines are above patches (e.g. rectangles, with default zorder = 1) but still below lines and markers (with their default zorder = 2).
  • False (zorder = 2.5): Ticks and gridlines are above patches and lines / markers.
Stan
  • 250
  • 2
  • 9