Mike
Mike

Reputation: 1711

Matplotlib: Scatter Plot to Foreground on top of a Contour Plot

Does anyone know a way to bring a scatter plot to the foreground in matplotlib? I have to display the scatter plotting on top of the contour, but by default it is plotted underneath...

Upvotes: 74

Views: 93050

Answers (2)

cottontail
cottontail

Reputation: 23011

Set zorder with any float value

Everything on a matplotlib figure as zorder and you can find their default values in the docs. But the truth is, you don't even need to remember the default values because you can set the zorder with any number you want where the Artist with the higher zorder value will be drawn on top; only the ordinal rankings of the values (not the cardinal values) you assign matter when it comes to showing which plot on top of which.

For example, in the following figure, we assign zorder value to both scatter and contourf calls. As you can see, in both subplots, the plot with the higher zorder is shown on top.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from scipy import stats

delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)

X, Y = np.meshgrid(x, y)
Z1 = stats.multivariate_normal([0, 0], [[1, 0], [0, 1]]).pdf(np.dstack((X, Y)))
Z2 = stats.multivariate_normal([1, 1], [[1.5, 0], [0, 0.5]]).pdf(np.dstack((X, Y)))
Z = 10.0 * (Z2 - Z1)

vmax = np.abs(Z).max()
norm = mpl.colors.Normalize(vmin=-vmax, vmax=vmax)
cmap = mpl.colormaps["PRGn"]
levels = np.arange(-2.0, 1.601, 0.4)

x = np.random.uniform(-3,3,10)
y = np.random.uniform(-2,2,10)

fig, axes = plt.subplots(1, 2, sharey=True)

for ax, zord in zip(axes, [-0.5, 0.5]):
    ax.scatter(x, y, zorder=zord)
    #                ^^^^^^^^^^^  <--- zorder here
    ax.contourf(X, Y, Z, levels, 
                norm=norm, cmap=cmap, zorder=zord*2)
    #                                 ^^^^^^^^^^^^^  <--- zorder here
    ax.autoscale(False)
    ax.set_title(f'Scatter with zorder={zord}\ncontour with zorder={zord*2}')

plots with different zorder

The order in which the plots are drawn matters

If two Artists have the same zorder, then the one drawn last will be shown on top. In fact, contour and scatter are both a Collection, so by default, they have the same zorder. The reason scatter plots were not shown in the OP was scatter plot was plotted before the contour plot. So one solution to OP is to draw the scatter plot after the contour plot (so in sodd's answer, because scatter plot is drawn last, you don't even need to set zorder). You can confirm that with the following (using the same data as above):

plt.contourf(X, Y, Z, levels, norm=norm, cmap=cmap)
plt.scatter(x, y)

order matters

Change zorder using set_zorder()

As mentioned above, the Artists of both scatter plot and contour plot are stored in .collections of the Axes object they are drawn on (and they are stored in it in the order they are drawn). You can confirm that using the following code. Then you can change the zorder of these objects by calling set_zorder() on them as well. This is especially useful if the plots are created using some third party library that uses matplotlib in the backend.

The code below produces a figure the same as above.

fig, ax = plt.subplots()
ax.scatter(x, y)
ax.contourf(X, Y, Z, levels, norm=norm, cmap=cmap)
ax.collections                 # <Axes.ArtistList of 2 collections>
scatter = ax.collections[0]    # <matplotlib.collections.PathCollection at 0x223b2519610>
contour = ax.collections[1]    # <matplotlib.contour.QuadContourSet at 0x223a69dc390>

# set zorders here
scatter.set_zorder(1)
contour.set_zorder(0.9)

Upvotes: 0

sodd
sodd

Reputation: 12923

You can manually choose in which order the different plots are to be displayed with the zorder parameter of e.g. the scatter method.

To demonstrate, see the code below, where the scatter plot in the left subplot has zorder=1 and in the right subplot it has zorder=-1. The object with the highest zorder is placed on top. This means that the scatter will be placed on top of the contour in the first subplot, while it is placed underneath in the second subplot.

import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt

delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = 10.0 * (Z2 - Z1)

norm = cm.colors.Normalize(vmax=abs(Z).max(), vmin=-abs(Z).max())
cmap = cm.PRGn

levels = np.arange(-2.0, 1.601, 0.4)

fig, axes = plt.subplots(1,2, sharey=True)

for ax, zord in zip(axes, [1, -1]):
    ax.contourf(X, Y, Z, levels,
                cmap=cm.get_cmap(cmap, len(levels)-1),
                norm=norm)
    ax.autoscale(False) # To avoid that the scatter changes limits
    ax.scatter(np.random.uniform(-3,3,10),
               np.random.uniform(-2,2,10),
               zorder=zord)
    ax.set_title('Scatter with zorder={0}'.format(zord))

enter image description here

Upvotes: 102

Related Questions