How to set your own color levels in a block plot?

1,246 views
Skip to first unread message

Rutger Dankers

unread,
Jan 9, 2013, 9:24:40 AM1/9/13
to scitoo...@googlegroups.com

When doing a contour plot, it is relatively straightforward to set your own color levels, something like this:

mylevels = numpy.array([-100,-50,-20,-10,10,20,50,100])
plot_reldif = iplt.contourf(reldif_field, mylevels, cmap=brewer_cmap,extend='max') # extend='max' to plot values beyond range
plt.gca().coastlines()
plt.show()

But a contour plot is not appropriate for the type of data I want to plot, I want to make a block plot instead. However:

plot_reldif = qplt.pcolormesh(reldif_field, mylevels, cmap=brewer_cmap)

fails with an error message 'Illegal arguments to pcolormesh'. Leaving 'mylevels' out works, and I can even restrict the plotting range like this:

plot_reldif = qplt.pcolormesh(reldif_field,cmap=brewer_cmap,vmin=-100,vmax=100)

but the result is always a linear interpolation of the range (equal intervals). How can I set my own classes?
(In tIDL's pp_contour this is done with block=1, I think.)

Cheers
Rutger
Message has been deleted

Bernd Becker

unread,
Jan 9, 2013, 10:23:39 AM1/9/13
to scitoo...@googlegroups.com
Rutger,
 
It might work if you provide a list of levels and a list of colors to your plot call.
 
brewer_cmap = mpl_cm.get_cmap('brewer_RdBu_11') 
colors=[ 2, 5, 8, 12,  15, 18 , 21 ,27 ,32 ]
levels=[-100,-50,-20,-10,10,20,50,100]
my_cmap = subset_colormap('mycmap',brewer_cmap, colors  ) 
qplt.contourf(t0+273.15,levels=levels,cmap=my_cmap,extend='both')
 
along those lines...
 
Bernd

Rutger Dankers

unread,
Jan 9, 2013, 12:21:33 PM1/9/13
to scitoo...@googlegroups.com
Thanks Bernd

.... but that won't work, I think, as qplt.pcolormesh doesn't accept the levels keyword it seems, as opposed to qplt.contourf in your example.

Cheers
Rutger

Rutger Dankers

unread,
Jan 15, 2013, 6:33:59 AM1/15/13
to scitoo...@googlegroups.com

It looks like I am able to answer my own question... But it's not easy to find!

It appears pcolormesh accepts a BoundaryNorm keyword that generates "...a colormap index based on discrete intervals." In my original question I had 7 classes + 1 class for values that are larger than 100, so I can create a block plot like this:

import matplotlib.colors as colors
plot_reldif = iplt.pcolormesh(reldif_field,cmap=brewer_cmap,norm = colors.BoundaryNorm(mylevels,ncolors=8,clip=False))

Like this the color scheme of my plot will only use the first 8 colours of my selected colour map, though, while I had selected a Brewer palette (brewer_RdYlBu_11) that nicely goes from dark red to light colours in the middle to dark blue at the end. How can I make sure that my middle class (from -10 to 10) is plotted with the light colour in the middle of my palette? The Brewer palette has 11 colours, and setting ncolors=10 or ncolors=11 sort of works, but means the 11 colours of the palette are distributed over the 7+1 classes, giving not quite the result you would expect (when setting ncolors=11 the class >100 also has the same colour has the last "normal" class, 50-100).

So can we subset the Brewer palette and use only those colours that we want? Turns out you can:

brewer_cmap = mpl_cm.get_cmap('brewer_RdYlBu_11')
# subset the color map for the levels (classes) defined above
my_cmap = brewer_cmap([1,2,3,5,7,8,9,10]) # indices of colors in brewer_cmap to use
new_cmap = colors.ListedColormap(my_cmap,"my_colormap") # creates a new color map with 8 colours

Now we need to call BoundaryNorm with ncolors=7 to use the first 7 colours in my new colour map for my 7 classes; the 8th colour is then used for values that are larger than the maximum class limit (>100). I think that values smaller than the minimum value (< -100 in my case) would always be plotted with the same colour as the first "proper" class (-100 to -50), which might be a limitation, but this does not happen in my specific case. 

plot_reldif = iplt.pcolormesh(reldif_field,cmap=new_cmap,norm = colors.BoundaryNorm(mylevels,ncolors=len(mylevels)-1,clip=False))

Finally we can plot the colour bar so that it extends beyond the maximum value:

colorbar = plt.colorbar(plot_reldif, colorbar_axes, orientation='horizontal',extend='max')

... and we get something that looks like what I wanted.

If you think this looks relatively straightforward, a necessary condition is that you first spend hours (if not days) trawling through pages and pages and pages of highly technical, but not very practical matplotlib and pylab documentation before you get any hints of how this kind of (what seems to me) fairly standard operations can be done.

HTH
Rutger

Rutger Dankers

unread,
Jan 15, 2013, 6:39:10 AM1/15/13
to scitoo...@googlegroups.com

It appears pcolormesh accepts a BoundaryNorm keyword

To be precise, it accepts a norm keyword, and you can then use BoundaryNorm to define discrete intervals.

Gr.R.


Phil Elson

unread,
Jan 15, 2013, 7:30:04 AM1/15/13
to scitoo...@googlegroups.com
> If you could send me an example for the other method then that would be great. I'd like to see whether it works on ORCA025 as well though as most of my work these days is based on that resolution.

They are all (relatively) simple once you know how - as you said, the real pain is finding how to do it in the first place!

I'm sorry nobody was able to help you sooner with this - I guess technically this is a matplotlib question so you might have got a response sooner on their mailinglist. Having said that, in all my travels, I've only seen what you have done a couple of times on mpl so it may not have been the case anyway.

I definitely think we can make this better: whether that be by adding an function in matplotlib or iris, or whether we get a gallery addition added to either matplotlib or iris. Its not clear to me the best path at the moment, so I think its best brought up on the matplotlib-devel mailing list to scope out feeling over there. I'd be happy enough to do that for you if you like?

Finally, thanks for sharing your recipe - hopefully by sharing you've saved others (and your future self) a lot of time!

All the best,

Phil

Phil Elson

unread,
Jan 15, 2013, 8:54:23 AM1/15/13
to scitoo...@googlegroups.com
To clarify any confusion - I was having copy&paste issues with the quotation I provided (re. "If you could send me...") - I was meaning to quote your statement:


> If you think this looks relatively straightforward, a necessary condition is that you first spend hours (if not days) trawling through pages and pages and pages of highly technical

Sorry for any confusion!

Phil Elson

unread,
May 23, 2013, 5:27:42 AM5/23/13
to scitoo...@googlegroups.com
The difficulty in doing custom levels & colours has sat badly with me since you posted this so I've added a function (currently under review) to matplotlib to simplify the process (and reduce the chance of getting it wrong).

I've put your example into a notebook so that you can see the commands I used, and the associated output. Please note that to produce the notebook I have development versions of matplotlib and cartopy - but I'm hopeful you can expect this capability in the near future.

http://nbviewer.ipython.org/5628989

Hope it's useful.

Rutger Dankers

unread,
May 24, 2013, 10:39:10 AM5/24/13
to scitoo...@googlegroups.com
Thanks Phil

That looks very promising indeed.

One minor detail if I may: the colormap you show in your notebook (after [4]) is not the same as the brewer_RdYlBu_11 colormap shown here: http://scitools.org.uk/iris/docs/latest/userguide/plotting_a_cube.html#brewer-colour-palettes. It wasn't immediately obvious to me how the colour indices used in the final example:

colors = nice_cmap([9, 8, 6, 3, 2, 0])

relate to the colormap just above - until I realised they don't, but relate to the original 11-colour palette instead.

Many thanks!
Rutger

Phil Elson

unread,
May 24, 2013, 12:52:25 PM5/24/13
to scitoo...@googlegroups.com
Ah thanks Rutger - well spotted.
I've updated the notebook and added a mention of my proposed "peek" method.

HTH
Reply all
Reply to author
Forward
0 new messages