1

I am trying to obtain a 2D pseudocolor plot with a nonlinear color map (cmap). Independently I want to have a colobar that uses a similar cmap but differently scaled/stretched to avoid overlapping of the colorbar yticks.

The first one I can obtain using some nonlinear norm as an argument of pcolormesh.

But how to get the second part in an efficient way?

Finally, I was able to obtain the desired effect (see the bottom right corner in the below figure) but I am pretty sure that this is not the best/easiest/desired/Pythonic way of doing it.

Is there an easier way of obtaining such an effect?

Figure: enter image description here

Here is the code that reproduces the above figure:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cbook as cbook
from matplotlib import cm
import copy 

def transform_cmap(cmap_basic,
                  new_cmap_name = None,
                  cmap_trans_fun = None,
                  how_many_levels = 256,
                  ticks = None,
                  ticks_trans_fun = None):
   '''returns new cmap and new ticks locators for transformed ticks.
   If  ticks_trans_fun is None then ticks locators are linearly transformed 
   to the [0,1] interval, as a result, cmap is stretched, but when used with 
   colorbar than linearly spaced ticks are linearly spaced along the colorbar'''
   
   # be sure that cmap is really a cmap
   if not isinstance(cmap_basic,colors.Colormap):
       try: 
           cmap_basic = cm.get_cmap(name=cmap_basic)
       except:
           print('basic_cmap is not a valid cmap or cmap name!')
           
   if cmap_trans_fun is None:
       cmap_trans_fun = colors.Normalize()
   if new_cmap_name is None:
       new_cmap_name = cmap_basic.name+'_new'
       
   c_coords_linear = np.linspace(0,1, how_many_levels)

   cmap_trans_fun_copy = copy.deepcopy(cmap_trans_fun)
   # deppcopy to avoid overwritting the vmin, vmax values
   cmap_trans_fun_copy.autoscale([0,1])
   c_coords_after = cmap_trans_fun_copy(c_coords_linear)

   c_list_after = cmap_basic(c_coords_after)
   
   new_cmap = colors.LinearSegmentedColormap.from_list(new_cmap_name, 
                                                       c_list_after, 
                                                       N=how_many_levels)
   if ticks_trans_fun is None:                         
       ticks_trans_fun = colors.Normalize()
   
   ticks_trans_fun_copy = copy.deepcopy(ticks_trans_fun)
   ticks_trans_fun_copy.vmin = cmap_trans_fun.vmin
   ticks_trans_fun_copy.vmax = cmap_trans_fun.vmax
   new_ticks_locators = ticks_trans_fun_copy(ticks)
   
   return new_cmap, new_ticks_locators

###########################################
# Prepare some data
# based on https://matplotlib.org/stable/gallery/userdemo/colormap_normalizations.html#sphx-glr-gallery-userdemo-colormap-normalizations-py
N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]

# A low hump with a spike coming out of the top right.  Needs to have
# z/colour axis on a log scale so we see both hump and spike.  linear
# scale only shows the spike.
Z1 = np.exp(-X**2 - Y**2)
Z1b = 2*np.exp(-10*(X-0.5)**2 - 20*(Y-0.5)**2)
Z2 = np.exp(-(X * 10)**2 - (Y * 10)**2)
Z = Z1 + 50 * Z2+ Z1b
Z = Z*10
# data prepared!
###########################################


cbar_ticks = [0,1,10,50,100,200,300,400]

# prepare basic 'linear' cmap from matplotlib avaliable cmap
cmap = 'inferno'
lin_cmap = cm.get_cmap(cmap)

############################################
# prepare nonlinear norm (good for data viz)
gamma = 0.1
nonlin_norm = colors.PowerNorm(gamma=gamma)
nonlin_norm.autoscale(Z)

# prepare nonlinear norm b (better for colorbar)
gamma_b = 0.40
nonlin_norm_b = colors.PowerNorm(gamma=gamma_b) 
nonlin_norm.autoscale(Z)

################# PLOT ####################
#  create 4 plots in 2x2 grid

fig, axs = plt.subplots(nrows = 2, 
                       ncols = 2,
                       figsize=(6,5.5),
                       dpi=108,
                       squeeze = False)

fs = 8 # the same plot title fontsize
LLL_ax = axs[0,0]
LNL_ax = axs[0,1]
LNN_ax = axs[1,0]
LNNb_ax = axs[1,1]

#------------- Top left plot --------------
LLL_ax.set_title('linear cmap\nlinear norm\nlinear cbar',
                fontsize = fs)
LLL_pcm = LLL_ax.pcolormesh(X, Y, Z,
                      cmap = lin_cmap,
                      shading='auto')

# colorbar takes LLL_pcm object to figure out colormap and scale
fig.colorbar(LLL_pcm,
            ax=LLL_ax, 
            extend='both',
            ticks=cbar_ticks)

#------------- Top right plot -------------
# an easy way of obtaining good color-scaling
# the colorbar shows cmap in a linear way
# the cbar yticks are nonlinearly scaled but 
# they are overlapping
LNL_ax.set_title('linear cmap\nnonlinear norm\nlinear cbar cmap (nonlinear ticks)',
                fontsize = fs)
nonlin_norm.autoscale(Z)
LNL_pcm = LNL_ax.pcolormesh(X, Y, Z,
                      cmap = lin_cmap,
                      norm = nonlin_norm,
                      shading='auto')
fig.colorbar(LNL_pcm,
            ax=LNL_ax, 
            extend='both',
            ticks=cbar_ticks)

#------------- Bottom left plot -----------
# the colorbar cmap is nonlinear
# the cbar yticks are linearly scaled but
# the overall effect is not good 
# the cbar yticks are overlapping again
LNN_ax.set_title('linear cmap\nnonlinear norm\nnonlinear cbar cmap (linear ticks)',
                fontsize = fs)
LNN_pcm = LNN_ax.pcolormesh(X, Y, Z,
                      cmap = lin_cmap,
                      norm = nonlin_norm,
                      shading='auto')
# create new, nonlinear cmap 
nonlin_cmap, new_ticks_coords = transform_cmap(cmap_basic = lin_cmap ,
                                              cmap_trans_fun = nonlin_norm,
                                              how_many_levels = 256,
                                              ticks = cbar_ticks,
                                              ticks_trans_fun = None,
                                              new_cmap_name = 'nonlinear_cmap')

# create object based on new cmap for colorbar
scalar_mappable = cm.ScalarMappable(cmap=nonlin_cmap)

LNN_cbar = fig.colorbar(scalar_mappable,
                        ax=LNN_ax, 
                        extend='both',
                        ticks=new_ticks_coords)
# ticks are in correct places but they are normalized to [0,1] interval
# we need to overwrite them with desired labels
LNN_cbar.ax.set_yticklabels(cbar_ticks)

#------------- Bottom right plot ----------
# the colorbar shows cmap in a nonlinear way
# this is different nonlinear scaling than before (nonlin_norm_b)
# the cbar yticks are also nonlinearly scaled 
# this is A GOOD LOOKING PLOT 
LNNb_ax.set_title('linear cmap\nnonlinear norm\n2nd nonlinear cbar cmap (nonlinear ticks)',
                 fontsize = fs)
LNNb_pcm = LNNb_ax.pcolormesh(X, Y, Z,
                      cmap = lin_cmap,
                      norm = nonlin_norm,
                      shading='auto')

# this time as the cbar cmap is with different norm than data cmap
# we also need to recalculate positions of the cbar ticks using second norm 
nonlin_cmap_b, new_ticks_coords_b = transform_cmap(cmap_basic = lin_cmap ,
                                                  cmap_trans_fun = nonlin_norm_b,
                                                  how_many_levels = 256,
                                                  ticks = cbar_ticks,
                                                  ticks_trans_fun = nonlin_norm_b,
                                                  new_cmap_name = 'nonlinear_cmap_v2')

scalar_mappable_b = cm.ScalarMappable(cmap=nonlin_cmap_b)

LNNb_cbar = fig.colorbar(scalar_mappable_b,
                        ax=LNNb_ax, 
                        extend='both',
                        ticks=new_ticks_coords_b)

LNNb_cbar.ax.set_yticklabels(cbar_ticks)
#------------------------------

fig.tight_layout()
plt.show()

I was using this answer as a base:

Uniform tick labels for non-linear colorbar in Matplotlib

These answers may be useful but were looking too complicated:

Arbirtrary non-linear colorbar using Matplotlib

nonlinear colormap, matplotlib

I have a feeling that wiser usage of norm parameter in pcolor and perhaps in cbar should give me the desired result. Unfortunately, I was not able to obtain it in this way.

Aleksander_B
  • 123
  • 8

0 Answers0