12

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.

Saullo G. P. Castro
  • 56,802
  • 26
  • 179
  • 234
user2145054
  • 215
  • 2
  • 3
  • 8
  • Your colorbar _already has_ a logarithmic scale. – sodd Aug 12 '13 at 16:19
  • 2
    @nordev - I believe the OP is asking how to set the tick locator and formatter on the colorbar to display labels at regular log intervals. – Joe Kington Aug 12 '13 at 16:27
  • @JoeKington Ah, I seemed to recall that the OP in his [original question](http://stackoverflow.com/revisions/17951672/1) wanted the tickmarks to be placed at the values given in the array `lvls` with logarithmic spacing, but maybe I just misundertood what he meant. Thanks for pointing it out. – sodd Aug 12 '13 at 16:42
  • You can probably do something with `matplotlib.colors.LogNorm()` similar to [this answer](http://stackoverflow.com/a/17202196/420867) – drevicko Jul 06 '15 at 02:12

1 Answers1

13

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)))
Schorsch
  • 7,761
  • 6
  • 39
  • 65