strix
strix

Reputation: 83

matplotlib: subplot background (axes face + labels) colour [or, figure/axes coordinate systems]

I have a figure containing 3x2 subplots, and I would like to set a background colour on the middle pair of subplots to make it clearer which axis labels belong to which subplot.

Setting facecolor when constructing the subplot changes only the colour of the area defined by the axes; the tick and axis labels are still drawn on figure.patch. Assuming that there is no simple way to do this, I could add a rectangular patch behind the relevant instances in figure.axes.

After a bit of experimenting around, it appears that figure.axes[x].get_position() returns Axes coördinates (normalised coördinates [0.0-1.0]), yet Rectangle() appears to want Display coördinates (pixels). This code more or less works (ED: interactively, but when outputting to a png (using the Agg renderer), the positioning of the Rectangle is completely off):

import matplotlib.pyplot as plt
import matplotlib

f = plt.figure()
plt.subplot( 121 )
plt.title( 'model' )
plt.plot( range(5), range(5) )
plt.xlabel( 'x axis' )
plt.ylabel( 'left graph' )
plt.subplot( 122 )
plt.title( 'residuals' )
plt.plot( range(5), range(5) )
plt.xlabel( 'x axis' )
plt.ylabel( 'right graph' )
plt.tight_layout(pad=4)

bb = f.axes[0].get_position().transformed( f.transFigure ).get_points()
bb_pad = (bb[1] - bb[0])*[.20, .10]
bb_offs = bb_pad * [-.25, -.20]
r = matplotlib.patches.Rectangle( bb[0]-bb_pad+bb_offs, *(bb[1] - bb[0] + 2*bb_pad),
                                  zorder=-10, facecolor='0.85', edgecolor='none' )
f.patches.extend( [r] )

but seems very hackish, and it feels that I've missed out on something important. Can anybody explain what, whether there is a simpler/better way to do it, and if so, what it is?

Since I really need to write to a file, I presently don't have a solution.

Upvotes: 3

Views: 4874

Answers (1)

Joe Kington
Joe Kington

Reputation: 284582

Just use the transform kwarg to Rectangle, and you can use any coordinate system you'd like.

As a simple example:

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

fig, axes = plt.subplots(3, 2)

rect = Rectangle((0.08, 0.35), 0.85, 0.28, facecolor='yellow', edgecolor='none',
                 transform=fig.transFigure, zorder=-1)
fig.patches.append(rect)
plt.show()

enter image description here

However, if you wanted to do things more robustly, and calculate the extent of the axes, you might do something like this:

import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
from matplotlib.patches import Rectangle

def full_extent(ax, pad=0.0):
    """Get the full extent of an axes, including axes labels, tick labels, and
    titles."""
    # For text objects, we need to draw the figure first, otherwise the extents
    # are undefined.
    ax.figure.canvas.draw()
    items = ax.get_xticklabels() + ax.get_yticklabels() 
#    items += [ax, ax.title, ax.xaxis.label, ax.yaxis.label]
    items += [ax, ax.title]
    bbox = Bbox.union([item.get_window_extent() for item in items])
    return bbox.expanded(1.0 + pad, 1.0 + pad)


fig, axes = plt.subplots(3,2)

extent = Bbox.union([full_extent(ax) for ax in axes[1,:]])

# It's best to transform this back into figure coordinates. Otherwise, it won't
# behave correctly when the size of the plot is changed.
extent = extent.transformed(fig.transFigure.inverted())

# We can now make the rectangle in figure coords using the "transform" kwarg.
rect = Rectangle([extent.xmin, extent.ymin], extent.width, extent.height,
                 facecolor='yellow', edgecolor='none', zorder=-1, 
                 transform=fig.transFigure)
fig.patches.append(rect)

plt.show()

enter image description here

Upvotes: 7

Related Questions