4

I want to merge two colormaps for an imshow plot. I want to use 'RdBu' for the range -0.4 to 0.4, then from 0.4 to the maximum value (say 1.5) I want to use a gradient from the same blue to another color (say green for example).

How can I do that?

This is how far I got so far:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from matplotlib.mlab import bivariate_normal

N = 100
'''
Custom Norm: An example with a customized normalization.  This one
uses the example above, and normalizes the negative data differently
from the positive.
'''
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2  \
    - 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2
Z1 = Z1/0.03

# Example of making your own norm.  Also see matplotlib.colors.
# From Joe Kington: This one gives two different linear ramps:

class MidpointNormalize(colors.Normalize):
    def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
        self.midpoint = midpoint
        colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        # I'm ignoring masked values and all kinds of edge cases to make a
        # simple example...
        x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
        return np.ma.masked_array(np.interp(value, x, y))

fig, ax = plt.subplots(1, 1)

minValue = Z1.min()
maxValue = 0.4

pcm = ax.imshow(Z1,
                norm=MidpointNormalize(midpoint=0.),
                vmin=minValue, vmax=maxValue,
                cmap='RdBu',
                origin='lower',
                aspect=1.0,
                interpolation='none')
cbar = fig.colorbar(pcm, ax=ax, extend='both', ticks=[minValue, 0.0, maxValue])

fig.tight_layout()

plt.show()

enter image description here

fromGiants
  • 474
  • 1
  • 7
  • 16

1 Answers1

6

The purpose is to create a colormapping, which has several predefined values. The start of colormap should be at vmin, white (which is in the middle of the "RdBu" colormap) should be at 0, another predefined point (0.4) shall be the upper end of the RdBu colormap and then the color shall fade towards some end color.

For this purpose we need two things. (a) a colormap that has all those colors in it and (b) a Normalization that allows to map the intermediate points to the respective colors.

(a) Creating the colormap

Colormaps range between 0 and 1. We may create the colormap such that the colors from the "RdBu" colormap extend over the first half of the desired colormap, such that 0 is red, 0.25 is white and 0.5 is blue. The second half of the colormap then ranges from 0.5 (the same blue) over some intermediate turquoise at 0.75 to green at 1. (The intermediate turquoise is chosen because a direct transition from blue to green would result in some smeared brownish blue in the middle, which is probably undesired.) Those steps are accomplished via the following code

colors = plt.cm.RdBu(np.linspace(0,1.,128)) 
colors = zip(np.linspace(0,0.5,128),colors) 
colors += [ (0.75,"#1fa187"),(1., "#76d154")] 
cmap = matplotlib.colors.LinearSegmentedColormap.from_list('mycmap', colors)

such that cmap is the desired colormap.

(b) Creating the normalization

Unlike the MidpointNormalization, which has one intermediate point, we now need two intermediate points: one being the white color at 0 value and one being the end of the first half of the colormap. We can hence use two values in the custom normalization (here called low and up), such the interpolation ranges over 4 points in total and low corresponds to the 0.25 value of the colormap and up corresponds to the 0.5 value.

x, y = [self.vmin, self.low, self.up, self.vmax], [0, 0.25, 0.5, 1]

Complete code:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
from matplotlib.mlab import bivariate_normal

N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2  \
    - 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2
Z1 = Z1/0.03


class TwoInnerPointsNormalize(matplotlib.colors.Normalize):
    def __init__(self, vmin=None, vmax=None, low=None, up=None, clip=False):
        self.low = low
        self.up = up
        matplotlib.colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        x, y = [self.vmin, self.low, self.up, self.vmax], [0, 0.25, 0.5, 1]
        return np.ma.masked_array(np.interp(value, x, y))

colors = plt.cm.RdBu(np.linspace(0,1.,128)) 
colors = zip(np.linspace(0,0.5,128),colors) 
colors += [ (0.75,"#1fa187"),(1., "#76d154")] 
cmap = matplotlib.colors.LinearSegmentedColormap.from_list('mycmap', colors)


fig, ax = plt.subplots(1, 1)

norm = TwoInnerPointsNormalize(vmin=-0.4, vmax=1.5, low=0., up=0.4)
pcm = ax.imshow(Z1, norm=norm, cmap=cmap,
                origin='lower', aspect=1.0, interpolation='none')
cbar = fig.colorbar(pcm, ax=ax, ticks=[-0.4,0.0, 0.4,1.5]) 

fig.tight_layout()
plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • This solved my issue. I like the solution as it is easily generalizable to merge any colormaps, as well as adding an arbitrary number of segments in the colormap. Thanks! – fromGiants Aug 28 '17 at 18:33