matplotlib contour plot: proportional colorbar levels in logarithmic scale

user2145054 picture user2145054 · Aug 12, 2013 · Viewed 11.5k times · Source

Would it be possible to have levels of the colorbar in log scale like in the image below?

enter image description here

Here is some sample code where it could be implemented:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LogNorm
delta = 0.025

x = y = np.arange(0, 3.01, delta)
X, Y = np.meshgrid(x, y)
Z1 = plt.mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = plt.mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = 1e6 * (Z1* Z2)

fig=plt.figure()
ax1 = fig.add_subplot(111)
lvls = np.logspace(0,4,20)
CF = ax1.contourf(X,Y,Z,
         norm = LogNorm(),
         levels = lvls
        )
CS = ax1.contour(X,Y,Z,
         norm = LogNorm(),
         colors = 'k',
         levels = lvls
        )
cbar = plt.colorbar(CF, ticks=lvls, format='%.4f')
plt.show()

enter image description here

I am using python 2.7.3 with matplotlib 1.1.1 on Windows 7.

Answer

Schorsch picture Schorsch · Aug 13, 2013

I propose to generate a pseudo colorbar as follows (see comments for explanations):

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LogNorm
import matplotlib.gridspec as gridspec

delta = 0.025

x = y = np.arange(0, 3.01, delta)
X, Y = np.meshgrid(x, y)
Z1 = plt.mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = plt.mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = 1e6 * (Z1 * Z2)

fig=plt.figure()

#
# define 2 subplots, using gridspec to control the 
# width ratios:
#
# note: you have to import matplotlib.gridspec for this
#
gs = gridspec.GridSpec(1, 2,width_ratios=[15,1])

# the 1st subplot
ax1 = plt.subplot(gs[0])

lvls = np.logspace(0,4,20)

CF = ax1.contourf(X,Y,Z,
                  norm = LogNorm(),
                  levels = lvls
                 )
CS = ax1.contour(X,Y,Z,
                 norm = LogNorm(),
                 colors = 'k',
                 levels = lvls
                )

#
# the pseudo-colorbar
#

# the 2nd subplot
ax2 = plt.subplot(gs[1])        

#
# new levels!
#
# np.logspace gives you logarithmically spaced levels - 
# this, however, is not what you want in your colorbar
#
# you want equally spaced labels for each exponential group:
#
levls = np.linspace(1,10,10)
levls = np.concatenate((levls[:-1],np.linspace(10,100,10)))
levls = np.concatenate((levls[:-1],np.linspace(100,1000,10)))
levls = np.concatenate((levls[:-1],np.linspace(1000,10000,10)))

#
# simple x,y setup for a contourf plot to serve as colorbar
#
XC = [np.zeros(len(levls)), np.ones(len(levls))]
YC = [levls, levls]
CM = ax2.contourf(XC,YC,YC, levels=levls, norm = LogNorm())
# log y-scale
ax2.set_yscale('log')  
# y-labels on the right
ax2.yaxis.tick_right()
# no x-ticks
ax2.set_xticks([])

plt.show()

This will give you a plot like this:

pseudo-colorbar

EDIT

Or, use something like the new levels and the spacing='proportional' option when calling the colorbar:

  1. replace this line:

    lvls = np.logspace(0,4,20)  
    

    with these:

    lvls = np.linspace(1,10,5)
    lvls = np.concatenate((lvls[:-1],np.linspace(10,100,5)))
    lvls = np.concatenate((lvls[:-1],np.linspace(100,1000,5)))
    lvls = np.concatenate((lvls[:-1],np.linspace(1000,10000,5)))
    
  2. replace this line:

    cbar = plt.colorbar(CF, ticks=lvls, format='%.4f')
    

    with this:

    cbar = plt.colorbar(CF, ticks=lvls, format='%.2f', spacing='proportional')
    

And you will end up with this plot:

real-colorbar

(the format was only changed, because the new ticks do not require 4 decimals)

EDIT 2
If you wanted to automatically generate levels like the ones I have used, you can consider this piece of code:

levels = []
LAST_EXP = 4
N_LEVELS = 5
for E in range(0,LAST_EXP):
    levels = np.concatenate((levels[:-1],np.linspace(10**E,10**(E+1),N_LEVELS)))