Well, as previously mentioned, I thought I should talk a bit about how the background colours are produced. All rather surprisingly simple. Though I am having some problems with respect to getting a plot without any white around the sides and bottom. May try to fix, may not. Seems everytime I mess with the borders, axes and such I create another issue to resolve.

Single Solid Colour

I decided to start with a single, solid colour background. Though I also apply an opacity value to that background colour. Everything seemed simple enough. Matplotlib provides the set_facecolor method on the axes object for just this purpose. I am sure you know it didn’t work. Maybe even why.

For the images I have been saving I really didn’t want all the axes stuff included. So I turn them off, plt.axis('off').

The background patch is part of the axes. So if the axes is turned off, so will the background patch.

  • Turning axes off and setting facecolor at the same time not possible?

That article provided 3 potential solutions. After playing with the one that added a new patch, I decided to use the one that removed the axis spines and ticks from the plot.

So, I replaced plt.axis('off') with the following:

for spine in ax.spines.values():
  spine.set_visible(False)
ax.tick_params(bottom=False, labelbottom=False, left=False, labelleft=False)

And, so far that seems to work wonderfully well. Thank you ImportanceOfBeingErnest.

Then, it was really pretty simple to set the background colour.

# for some reason I chose only use the first 25% of the available colours in the current colour map
n_clr = int(lc_frq / 4)
c_bg = cycle[np.random.randint(0, lc_frq)]

# select opacity between 0.35 and 0.75 (inclusive?)
bg_lpha = np.random.randint(35, 75) / 100

ax.set_facecolor(c_bg)
ax.set_alpha(bg_lpha)

Well of course there was a lot of other code in my end-all-be-all module that is not being shown. The above is just the basic code/methodology I used.

And, here’s a couple of examples of the result not in the previous post. (Note, white space issue fixed more or less.)

image of plot generated using the spirograph script but with background colours image of plot generated using the spirograph script but with background colours

Must say I am liking the coloured backgrounds a touch more than the white.

Gradient for Background

Next I decided to look at using a colour gradient for the background. Another search provided a method and a new, for me, matplotlib function, matplotlib.pyplot.imshow(). Apparently, a most powerful method.

From what I could figure out a single invocation of imshow should produce the result I wanted. Something like:

ax.imshow([[-xmax,-ymax],[xmax,ymax]], cmap=cmap, interpolation='bicubic', extent=[xmin, xmax, ymin, ymax],
            origin='lower', alpha=bg_lpha)

Unfortunately that didn’t quite work. These are slightly older images so do display the issue with excess white boundaries on save.

image of plot generated using the spirograph script but with failed background colour gradient image of plot generated using the spirograph script but with failed background colour gradient

I resorted to a lot of trial and error without any success. So, I decided to do a little reading (see Resources section at end of post). Turns out I need to think about the coordinate systems being used under the hood by Matplotlib. So, I added the parameter transform=ax.transAxes to my call to imshow.

ax.imshow([[-xmax,-ymax],[xmax,ymax]], cmap=cmap, interpolation='bicubic', extent=[xmin, xmax, ymin, ymax],
            origin='lower', alpha=bg_lpha, transform=ax.transAxes)

And, lo and behold! (Apologize for the size of that last image, but had to use true colour to get the gradient displayed properly. Best 256 colours resulted in visible lines on the gradient.)

image of plot generated using the spirograph script but with background colour gradient

A Better Gradient

While looking for solutions to my various issue, I came across the following demo/example on the Matplotlib web site. It had a much nicer solution for generating a gradient background. The thing I liked best was that one could control the direction of the gradient. The approach above always generated a gradient running top to bottom.

I am not going to include the code here, but I modified my module to use the function in the demo rather than my one liner above. So do check out:

Bar chart with gradients

And, here’s an example with the gradient running at a “obvious” angle.

image of plot generated using the spirograph script but with background colour gradient

Blotchy/Splotchy Background

The first attempt at a splotchy background used an equation to generate the colour values for points on the grid. The grid had a high number of points to ensure reasonable coverage of the plot area. It worked just fine. But, of course, for any given colour map the background would always have the same pattern (ignoring the selected opacity).

def blotch_bg(ax, extent, **kwargs):
  dx, dy = 0.015, 0.015
  y, x = np.mgrid[slice(-2.25, 2.25 + dy, dy),
                  slice(-2.25, 2.25 + dx, dx)]
  z = (1 - x / 3. + x ** 5 + y ** 5) * np.exp(-x ** 2 - y ** 2)
  z = z[:-1, :-1]
  z_min, z_max = -np.abs(z).max(), np.abs(z).max()
    
  c = ax.imshow(z, vmin=z_min, vmax=z_max,
                  extent=extent, interpolation ='nearest', origin ='lower', **kwargs)
  return c                 

And, as it turns out it was perhaps more complicated than need be. The simple solution was to generate an array of random colour values and let imshow do the rest.

def blotch_bg_2(ax, extent, abg=None, dim=5, **kwargs):
  if abg is not None:
    A = abg
  else:
    A = np.random.rand(dim, dim)
  c = ax.imshow(A, extent=extent, interpolation='bilinear', **kwargs)
  return c, A

I added the abg parameter so that when I liked an image background I could get the function to re-use it. Note, lots more related code for that bit of control to work as desired. The parameter interpolation='bilinear' made sure the colours in the array were blended in some nice fashion. Didn’t want any overly straight edges.

An example or two of the latter approach. Sorry about the similar colour maps.

image of plot generated using the spirograph script but with blotchy colour background

The next one may make you think it is a gradient. But if you look carefully, I think you will realize it really isn’t.

image of plot generated using the spirograph script but with blotchy colour background

Unwanted White Space

You may have noticed that many of the images in this post do not contain that unwanted white space the images in the previous post displayed. Turns out the “secret” was to use a few of the many parameters provided by savefig.

bbox_inches: str or Bbox, default: rcParams["savefig.bbox"] (default: None)
Bounding box in inches: only the given portion of the figure is saved. If 'tight', try to figure out the tight bbox of the figure.
pad_inches: float, default: rcParams["savefig.pad_inches"] (default: 0.1)
Amount of padding around the figure when bbox_inches is 'tight'.

Adding bbox_inches='tight', pad_inches=0 to my calls to savefig seems to eliminate that undesireable white space.

Done

Well at least for now. The code module I have been using to generate the images for this and the previous post is huge, ugly and in need of much refactoring. So, my apologies, but you won’t be seeing that any time soon. However, with previously provided code, the bits above and the resources below, I expect you can code something better than I have so far managed.

I currenty anticipate that there will be one more post on coloured backgrounds. I want to look at using a variety of potential colour maps for the background rather than exactly the same one being used to generate the actual curve (simple, gnarly or otherwise).

You may also recall from the previous post, that I wanted to see if I could get something like those large gradients produced using the default colour map (tab10). But using the sequential or cyclical colour maps. Matplotlib classifies tab10 as a qualitative colour map

Until then have fun coding — whatever it is you are coding.

Resources