3

In the graphic below, I want to put in a legend for the calendar plot. The calendar plot was made using ax.plot(...,label='a') and drawing rectangles in a 52x7 grid (52 weeks, 7 days per week).

The legend is currently made using:

plt.gca().legend(loc="upper right")

How do I correct this legend to something more like a colorbar? Also, the colorbar should be placed at the bottom of the plot.

EDIT: Uploaded code and data for reproducing this here: https://www.dropbox.com/sh/8xgyxybev3441go/AACKDiNFBqpsP1ZttsZLqIC4a?dl=0

enter image description here

3
  • 1
    Can you provide a MWE? So we can understand exactly what you are doing and experiment with it? Commented May 3, 2015 at 16:13
  • Thanks @Bakuriu, the code is a bit long so I have uploaded it here: dropbox.com/sh/8xgyxybev3441go/AACKDiNFBqpsP1ZttsZLqIC4a?dl=0 Commented May 3, 2015 at 17:07
  • 1
    this looks awesome, I would like to run the code but the data is not available via dropbox anymore. Could anyone provide the '94.DGN' dataset? Commented Aug 26, 2018 at 9:20

2 Answers 2

7
+250

Aside - existing bugs

The code you put on the dropbox doesn't work "out of the box". In particular - you're trying to divide a datetime.timedelta by a numpy.timedelta64 in two places and that fails.

You do your own normalisation and colour mapping (calling into color_list based on an int() conversion of your normalised value). You subtract 1 from this and you don't need to - you already floor the value by using int(). The result of doing this is that you can get an index of -1 which means your very smallest values are incorrectly mapped to the colour for the maximum value. This is most obvious if you plot column 'BIOM'.

I've hacked this by adding a tiny value (0.00001) to the total range of the values that you divide by. It's a hack - I'm not sure that this method of mapping is at all the best use of matplotlib, but that's a different question entirely.

Solution adapting your code

With those bugs fixed, and adding a last suplot below all the existing ones (i.e. replacing 3 with 4 on all your calls to subplot2grid(), you can do the following:

Replace your

plt.gca().legend(loc="upper right")

with

# plot an overall colorbar type legend
# Grab the new axes object to plot the colorbar on    
ax_colorbar = plt.subplot2grid((4,num_yrs), (3,0),rowspan=1,colspan=num_yrs)   
mappableObject = matplotlib.cm.ScalarMappable(cmap = palettable.colorbrewer.sequential.BuPu_9.mpl_colormap)
mappableObject.set_array(numpy.array(df[col_name]))
col_bar = fig.colorbar(mappableObject, cax = ax_colorbar, orientation = 'horizontal', boundaries = numpy.arange(min_val,max_val,(max_val-min_val)/10))
# You can change the boundaries kwarg to either make the scale look less boxy (increase 10)
# or to get different values on the tick marks, or even omit it altogether to let
col_bar.set_label(col_name)
ax_colorbar.set_title(col_name + ' color mapping')

I tested this with two of your columns ('NMN' and 'BIOM') and on Python 2.7 (I assume you're using Python 2.x given the print statement syntax)

The finalised code that works directly with your data file is in a gist here

You get

Picture of output axes (NMN and BIOM)

How does it work?

It creates a ScalarMappable object that matplotlib can use to map values to colors. It set the array to base this map on to all the values in the column you are dealing with. It then used Figure.colorbar() to add the colorbar - passing in the mappable object so that the labels are correct. I've added boundaries so that the minimum value is shown explicitly - you can omit that if you want matplotlib to sort that out for itself.

P.S. I've set the colormap to palettable.colorbrewer.sequential.BuPu_9.mpl_colormap, matching your get_colors() function which gets these colours as a 9 member list. I strongly recommend importing the colormap you want to use as a nice name to make the use of mpl_colors and mpl_colormap more easy to understand e.g.

import palettable.colorbrewer.sequential.BuPu_9 as color_scale

Then access it as

color_scale.mpl_colormap

That way, you can keep your code DRY and change the colors with only one change.

Layout (in response to comments)

The colorbar may be a little big (certainly tall) for aesthetic ideal. There are a few possible options to do that. I'll point you to two:

  1. The "right" way to do it is probably to use a Gridspec

  2. You could use your existing approach, but increase the number of rows and have the colorbar still in one row, while the other elements span more rows than they do currently.

I've implemented that with 9 rows, an extra column (so that the month labels don't get lost) and the colorbar on the bottom row, spanning 2 less columns than the main figure. I've also used tight_layout with w_pad=0.0 to avoid label clashes. You can play with this to get your exact preferred size. New code here.

This gives:

enter image description here:

Sign up to request clarification or add additional context in comments.

3 Comments

thanks @Richard! this is great. many thanks for the bug fix as well. Is there a way to reduce the height of the colorbar? I tried shrink=.5, pad=.2 but that does not seem to work...
@user308827 see update to answer- enjoy playing with theformatting :)
No problem, glad to help. Look forward to the bounty :)
1

There are functions to do this in matplotlib.colorbar. With some specific code from your example, I could give you a better answer, but you'll use something like:

myColorbar = matplotlib.colorbar.ColorbarBase(myAxes, cmap=myColorMap, norm=myNorm, orientation='vertical')

1 Comment

thanks, the code is a bit long so I have uploaded it here: dropbox.com/sh/8xgyxybev3441go/AACKDiNFBqpsP1ZttsZLqIC4a?dl=0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.