6

I have data that I would like to plot with contourf/tricontourf using a nonlinear colormap.

I found a script (see below) that provides a good solution for the colormap as long as the levels are between 0 and a positive number.

However, my data is negative (levels between -50 and 0). Unfortunately, adjusting the levels to my case does not work at all (see figure, subplot 3). So is there anything I have to consider? Does anyone have any suggestions for me or maybe even faced the same problem?

I would really appreciate your help.

from pylab import *
from numpy import *
from matplotlib.colors import LinearSegmentedColormap

class nlcmap(LinearSegmentedColormap):
    """A nonlinear colormap"""

    name = 'nlcmap'

    def __init__(self, cmap, levels):
        self.cmap = cmap
        self.monochrome = self.cmap.monochrome
        self.levels = asarray(levels, dtype='float64')
        self._x = self.levels/ self.levels.max()
        self.levmax = self.levels.max()
        self.levmin = self.levels.min()
        self._y = linspace(self.levmin, self.levmax, len(self.levels))

    def __call__(self, xi, alpha=1.0, **kw):
        yi = interp(xi, self._x, self._y)
        return self.cmap(yi/self.levmax, alpha)

if __name__ == '__main__':

    y, x = mgrid[0.0:3.0:100j, 0.0:5.0:100j]
    H = 50.0 * exp( -(x**2 + y**2) / 4.0 )
    levels = [0, 1, 2, 3, 6, 9, 20, 50]

    H1 = -50.0 * exp( -(x**2 + y**2) / 4.0 )
    levels1 = [-50, -20, -9, -6, -3, -2, -1, 0]

    cmap_lin = cm.jet
    cmap_nonlin = nlcmap(cmap_lin, levels)
    cmap_lin1 = cm.jet
    cmap_nonlin1 = nlcmap(cmap_lin1, levels1)

    subplot(4,1,1)
    contourf(x, y, H, levels, cmap=cmap_nonlin)
    colorbar()
    subplot(4,1,2)
    contourf(x, y, H, levels, cmap=cmap_lin)
    colorbar()
    subplot(4,1,3)
    contourf(x, y, H1, levels1, cmap=cmap_nonlin1)
    colorbar()
    subplot(4,1,4)
    contourf(x, y, H1, levels1, cmap=cmap_lin1)
    colorbar()

    plt.show()  

contourf plot with nonlinear colormap

Matt Hall
  • 7,614
  • 1
  • 23
  • 36
Rike
  • 63
  • 1
  • 4

3 Answers3

3

In the example where levels1 = [-50, -20, -9, -6, -3, -2, -1, 0] you are dividing by zero when you say self._x = self.levels/ self.levels.max() . It seems that functions like pcolor and contourf rescale the input data between 0 and 1 before passing them to the colormap. Hence you also need to rescale your levels to that range, which your code does for the first example but not for the second. This seems to work:

class nlcmap(LinearSegmentedColormap):
    """A nonlinear colormap"""

    name = 'nlcmap'

    def __init__(self, cmap, levels):
        self.cmap = cmap
        self.monochrome = self.cmap.monochrome
        self.levels = asarray(levels, dtype='float64')
        self._x = self.levels-self.levels.min()
        self._x/= self._x.max()
        self._y = linspace(0, 1, len(self.levels))

    def __call__(self, xi, alpha=1.0, **kw):
        yi = interp(xi, self._x, self._y)
        return self.cmap(yi, alpha)
thomas
  • 1,773
  • 10
  • 14
3

Rather than modifying the colormap, a simpler approach would be to define a new type of normalization to apply to your data in order to map it into the range [0, 1].

Here I've modified an example from this previous answer:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize

class PiecewiseNorm(Normalize):
    def __init__(self, levels, clip=False):
        # the input levels
        self._levels = np.sort(levels)
        # corresponding normalized values between 0 and 1
        self._normed = np.linspace(0, 1, len(levels))
        Normalize.__init__(self, None, None, clip)

    def __call__(self, value, clip=None):
        # linearly interpolate to get the normalized value
        return np.ma.masked_array(np.interp(value, self._levels, self._normed))

For example:

y, x = np.mgrid[0.0:3.0:100j, 0.0:5.0:100j]
H = 50.0 * np.exp( -(x**2 + y**2) / 4.0 )
levels = [0, 1, 2, 3, 6, 9, 20, 50]

H1 = -50.0 * np.exp( -(x**2 + y**2) / 4.0 )
levels1 = [-50, -20, -9, -6, -3, -2, -1, 0]

fig, ax = plt.subplots(2, 2, gridspec_kw={'width_ratios':(20, 1), 'wspace':0.05})

im0 = ax[0, 0].contourf(x, y, H, levels, cmap='jet', norm=PiecewiseNorm(levels))
cb0 = fig.colorbar(im0, cax=ax[0, 1])

im1 = ax[1, 0].contourf(x, y, H1, levels1, cmap='jet', norm=PiecewiseNorm(levels1))
cb1 = fig.colorbar(im1, cax=ax[1, 1])

plt.show()

enter image description here

One advantage of this approach is that the same normalization can be used with any colormap.

Community
  • 1
  • 1
ali_m
  • 71,714
  • 23
  • 223
  • 298
2

The problem seems to be that you try to project the values in the [0, 1] range by dividing by the max, which works for positive values of max, but not negative ones... Try this:

from pylab import *
from numpy import *
from matplotlib.colors import LinearSegmentedColormap

class nlcmap(LinearSegmentedColormap):
    """A nonlinear colormap"""

    name = 'nlcmap'

    def __init__(self, cmap, levels):
        self.cmap = cmap
        self.monochrome = self.cmap.monochrome
        self.levels = asarray(levels, dtype='float64')
        self.levmax = self.levels.max()
        self.levmin = self.levels.min()
        self._x = (self.levels - self.levmin) / (self.levmax - self.levmin)
        self._y = linspace(0, 1, len(self.levels))

    def __call__(self, xi, alpha=1.0, **kw):
        yi = interp(xi, self._x, self._y)
        return self.cmap(yi, alpha)

if __name__ == '__main__':

    y, x = mgrid[0.0:3.0:100j, 0.0:5.0:100j]
    H = 50.0 * exp( -(x**2 + y**2) / 4.0 )
    levels = [0, 1, 2, 3, 6, 9, 20, 50]

    H1 = -50.0 * exp( -(x**2 + y**2) / 4.0 )
    levels1 = [-50, -20, -9, -6, -3, -2, -1, 0]

    cmap_lin = cm.jet
    cmap_nonlin = nlcmap(cmap_lin, levels)
    cmap_lin1 = cm.jet
    cmap_nonlin1 = nlcmap(cmap_lin1, levels1)

    subplot(4,1,1)
    contourf(x, y, H, levels, cmap=cmap_nonlin)
    colorbar()
    subplot(4,1,2)
    contourf(x, y, H, levels, cmap=cmap_lin)
    colorbar()
    subplot(4,1,3)
    contourf(x, y, H1, levels1, cmap=cmap_nonlin1)
    colorbar()
    subplot(4,1,4)
    contourf(x, y, H1, levels1, cmap=cmap_lin1)
    colorbar()

    plt.show()  
faltarell
  • 334
  • 1
  • 5