I want to plot 2D data in a small script and let the user choose vmin/vamx ( i call them threshold and -threshold) of the values in the dataset to be plotted. Ideally i want all the values below/above of vmin/vamx to be transparent. The midpoint of the colormap should always be at 0 and white!. I would like to have as much of the dynamic range as i can get. The color should range from blue for negative to white at 0 to red for positive values! The value threshold and number_of_contours is set by the user and the scale would have to be adapted accordingly.
I found a few hints on various forms but could not make them work for me. Here is what i have found so far:
If i use the solution supplied in the third link by user Paul H i get the error r, g, b, a = cmap(ri) TypeError: 'str' object is not callable no matter what i do. I tried to use the solution "midpointnorm" which can also be found in the links above. Here is a small example:
from numpy import*
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import pylab
from matplotlib.colors import Normalize
# class to norm a color sacle so the midpoint is ecatcly in the middle of the colormap . This is usefull if a colormap goes form color1 to white to color2
class MidPointNorm(Normalize):
def __init__(self, midpoint=0, vmin=None, vmax=None, clip=False):
Normalize.__init__(self,vmin, vmax, clip)
self.midpoint = midpoint
def __call__(self, value, clip=None):
if clip is None:
clip = self.clip
result, is_scalar = self.process_value(value)
self.autoscale_None(result)
vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint
if not (vmin < midpoint < vmax):
raise ValueError("midpoint must be between maxvalue and minvalue.")
elif vmin == vmax:
result.fill(0) # Or should it be all masked? Or 0.5?
elif vmin > vmax:
raise ValueError("maxvalue must be bigger than minvalue")
else:
vmin = float(vmin)
vmax = float(vmax)
if clip:
mask = ma.getmask(result)
result = ma.array(np.clip(result.filled(vmax), vmin, vmax),
mask=mask)
# ma division is very slow; we can take a shortcut
resdat = result.data
#First scale to -1 to 1 range, than to from 0 to 1.
resdat -= midpoint
resdat[resdat>0] /= abs(vmax - midpoint)
resdat[resdat<0] /= abs(vmin - midpoint)
resdat /= 2.
resdat += 0.5
result = ma.array(resdat, mask=result.mask, copy=False)
if is_scalar:
result = result[0]
return result
def inverse(self, value):
if not self.scaled():
raise ValueError("Not invertible until scaled")
vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint
if cbook.iterable(value):
val = ma.asarray(value)
val = 2 * (val-0.5)
val[val>0] *= abs(vmax - midpoint)
val[val<0] *= abs(vmin - midpoint)
val += midpoint
return val
else:
val = 2 * (val - 0.5)
if val < 0:
return val*abs(vmin-midpoint) + midpoint
else:
return val*abs(vmax-midpoint) + midpoint
# Make some illustrative fake data:
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
# Make the color i want to use
cdict3 = {'red': ((0.0, 0.0, 0.0),
(0.25, 0.0, 0.0),
(0.5, 0.8, 1.0),
(0.75, 1.0, 1.0),
(1.0, 0.4, 1.0)),
'green': ((0.0, 0.0, 0.0),
(0.25, 0.0, 0.0),
(0.5, 0.9, 0.9),
(0.75, 0.0, 0.0),
(1.0, 0.0, 0.0)),
'blue': ((0.0, 0.0, 0.4),
(0.25, 1.0, 1.0),
(0.5, 1.0, 0.8),
(0.75, 0.0, 0.0),
(1.0, 0.0, 0.0))
}
# Make a modified version of cdict3 with some transparency
# in the middle of the range.
cdict4 = cdict3.copy()
cdict4['alpha'] = ((0.0, 1.0, 1.0),
# (0.25,1.0, 1.0),
(0.5, 0.3, 0.3),
# (0.75,1.0, 1.0),
(1.0, 1.0, 1.0))
#########################################MY questions start here###########################################
threshold=10 # i only want to plot data in the range [-threshold,threshold]
number_of_contours=100
##Instead of just giving a number of contours i thought about giving a predefined list wich contains that speciefies the point of the contours.
##The poinst denisty should be heigher at the Ends of the intervall as the data at theses ends is of higher interest for me. The total number of points should be variable.
#levels = [-threshold, -0.005, -0.001, 0, 0.001,0.005, threshold]
norm = MidPointNorm(midpoint=0,vmin=-threshold, vmax=threshold) # norm the colormap
plt.register_cmap(name='BlueRedAlpha', data=cdict4)
im4 = pylab.contourf(x,y,Z,number_of_contours, cmap='BlueRedAlpha',interpolation='nearest',norm=norm)
#im4 = pylab.contourf(x,y,Z,number_of_contours, cmap='BlueRedAlpha',interpolation='nearest',vmin=-threshold,vmax=threshold,norm=norm)# is it enough to give the norm parameter after the normalisation or do i have to give vmin/vmas to be sure?
sm = plt.cm.ScalarMappable(cmap='BlueRedAlpha', norm=plt.Normalize(vmin=-threshold, vmax=threshold))
sm._A = []
plt.colorbar(sm,extend="both")
plt.show()
This somehow works now. I do not understand the code in the class midpointnorm. Is it enough to pass the norm parameter to contourf in the after the normalization or do i have to give vmin/vmax values to be sure that only values between vmin and vmax are ploted and to be sure? Are vmin and vmax ignored if i give a norm parameter?
But i would like to change the dynamic range in a way, that the color scale is not saturated for the vmin and vmax values.
More importantly, at the moment i am displaying values between -10 and 10 in the code. In my Datas i plot values between -0.03 and 0.03 and everything close to these values is already saturated as it is at the upper end of dynamic range. But this region is of highes interest for me. I need a colorscale wich goes from blue (negative) to red ( positive). I would like to have some control about the behavior were the contourlines are.
I know you can give a list of contourlines to contourf. Does somebody have an idea how to fill a list with length(number_of_contours+1) ranging from the values from "-threshold" to "+threshold" were the spacing of the elements is closer at the positive and negative end of the range? DO you have any other idea?