Définition du milieu d'une palette de couleurs dans matplotlib

87

Je veux définir le point central d'une palette de couleurs, c'est-à-dire que mes données vont de -5 à 10, je veux que zéro soit le milieu. Je pense que la façon de le faire est de sous-classer la normalisation et d'utiliser la norme, mais je n'ai trouvé aucun exemple et je ne sais pas exactement ce que je dois implémenter.

tillsten
la source
c'est ce qu'on appelle une palette de couleurs «divergente» ou «bipolaire», où le point central de la carte est important et les données vont au-dessus et en dessous de ce point. sandia.gov/~kmorel/documents/ColorMaps
endolith
3
Toutes les réponses dans ce fil semblent plutôt compliquées. La solution facile à utiliser est montrée dans cette excellente réponse , qui entre-temps a également été intégrée à la documentation de matplotlib, section Normalisation personnalisée: deux plages linéaires .
ImportanceOfBeingErnest

Réponses:

14

Notez que dans matplotlib version 3.1, la classe DivergingNorm a été ajoutée. Je pense que cela couvre votre cas d'utilisation. Il peut être utilisé comme ceci:

from matplotlib import colors
colors.DivergingNorm(vmin=-4000., vcenter=0., vmax=10000)

Dans matplotlib 3.2, la classe a été renommée TwoSlopesNorm

macKaiver
la source
Cela semble intéressant, mais il semble que cela doive être utilisé pour transformer les données avant de tracer. La légende de la barre de couleur se rapportera aux données transformées, pas à l'original.
bli
3
@bli ce n'est pas le cas. le normfait la normalisation de votre image. normsaller de pair avec les couleurs.
Paul H
1
Malheureusement, cela est obsolète à partir de la version 3.2 sans document sur la façon de le remplacer: matplotlib.org/3.2.0/api/_as_gen
...
1
Ouais, les documents ne sont pas clairs. Je pense qu'il a été renommé en TwoSlopeNorm: matplotlib.org/3.2.0/api/_as_gen/…
macKaiver
91

Je sais que c'est tard dans le jeu, mais je viens de passer par ce processus et j'ai trouvé une solution qui peut-être moins robuste que le sous-classement normaliser, mais beaucoup plus simple. J'ai pensé que ce serait bien de le partager ici pour la postérité.

La fonction

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid

def shiftedColorMap(cmap, start=0, midpoint=0.5, stop=1.0, name='shiftedcmap'):
    '''
    Function to offset the "center" of a colormap. Useful for
    data with a negative min and positive max and you want the
    middle of the colormap's dynamic range to be at zero.

    Input
    -----
      cmap : The matplotlib colormap to be altered
      start : Offset from lowest point in the colormap's range.
          Defaults to 0.0 (no lower offset). Should be between
          0.0 and `midpoint`.
      midpoint : The new center of the colormap. Defaults to 
          0.5 (no shift). Should be between 0.0 and 1.0. In
          general, this should be  1 - vmax / (vmax + abs(vmin))
          For example if your data range from -15.0 to +5.0 and
          you want the center of the colormap at 0.0, `midpoint`
          should be set to  1 - 5/(5 + 15)) or 0.75
      stop : Offset from highest point in the colormap's range.
          Defaults to 1.0 (no upper offset). Should be between
          `midpoint` and 1.0.
    '''
    cdict = {
        'red': [],
        'green': [],
        'blue': [],
        'alpha': []
    }

    # regular index to compute the colors
    reg_index = np.linspace(start, stop, 257)

    # shifted index to match the data
    shift_index = np.hstack([
        np.linspace(0.0, midpoint, 128, endpoint=False), 
        np.linspace(midpoint, 1.0, 129, endpoint=True)
    ])

    for ri, si in zip(reg_index, shift_index):
        r, g, b, a = cmap(ri)

        cdict['red'].append((si, r, r))
        cdict['green'].append((si, g, g))
        cdict['blue'].append((si, b, b))
        cdict['alpha'].append((si, a, a))

    newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
    plt.register_cmap(cmap=newcmap)

    return newcmap

Un exemple

biased_data = np.random.random_integers(low=-15, high=5, size=(37,37))

orig_cmap = matplotlib.cm.coolwarm
shifted_cmap = shiftedColorMap(orig_cmap, midpoint=0.75, name='shifted')
shrunk_cmap = shiftedColorMap(orig_cmap, start=0.15, midpoint=0.75, stop=0.85, name='shrunk')

fig = plt.figure(figsize=(6,6))
grid = AxesGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.5,
                label_mode="1", share_all=True,
                cbar_location="right", cbar_mode="each",
                cbar_size="7%", cbar_pad="2%")

# normal cmap
im0 = grid[0].imshow(biased_data, interpolation="none", cmap=orig_cmap)
grid.cbar_axes[0].colorbar(im0)
grid[0].set_title('Default behavior (hard to see bias)', fontsize=8)

im1 = grid[1].imshow(biased_data, interpolation="none", cmap=orig_cmap, vmax=15, vmin=-15)
grid.cbar_axes[1].colorbar(im1)
grid[1].set_title('Centered zero manually,\nbut lost upper end of dynamic range', fontsize=8)

im2 = grid[2].imshow(biased_data, interpolation="none", cmap=shifted_cmap)
grid.cbar_axes[2].colorbar(im2)
grid[2].set_title('Recentered cmap with function', fontsize=8)

im3 = grid[3].imshow(biased_data, interpolation="none", cmap=shrunk_cmap)
grid.cbar_axes[3].colorbar(im3)
grid[3].set_title('Recentered cmap with function\nand shrunk range', fontsize=8)

for ax in grid:
    ax.set_yticks([])
    ax.set_xticks([])

Résultats de l'exemple:

entrez la description de l'image ici

Paul H
la source
Merci beaucoup pour votre formidable contribution! Cependant, le code n'était pas capable à la fois de recadrer et de déplacer la même carte de couleurs, et vos instructions étaient un peu imprécises et trompeuses. J'ai maintenant corrigé cela et pris la liberté de modifier votre message. De plus, je l'ai inclus dans l' une de mes bibliothèques personnelles et vous ai ajouté en tant qu'auteur. J'espère que ça ne te dérange pas.
TheChymera
@TheChymera la palette de couleurs dans le coin inférieur droit a été à la fois recadrée et recentrée. N'hésitez pas à l'utiliser comme bon vous semble.
Paul H
Oui, cela a malheureusement l'air juste comme une coïncidence. Si startet stopne sont pas respectivement 0 et 1, après cela reg_index = np.linspace(start, stop, 257), vous ne pouvez plus supposer que la valeur 129 est le point médian de la cmap d'origine, par conséquent, la remise à l'échelle entière n'a aucun sens chaque fois que vous recadrez. En outre, startdevrait être de 0 à 0,5 et stopde 0,5 à 1, pas les deux de 0 à 1 comme vous le demandez.
TheChymera
@TheChymera J'ai essayé votre version et j'ai eu deux pensées à ce sujet. 1) il me semble que les indices que vous avez créés ont tous une longueur de 257, et dans matplotlib, il est par défaut à 256 je suppose? 2) supposons que mes données vont de -1 à 1000, elles sont dominées par les positifs et donc plus de niveaux / couches devraient aller à la branche positive. Mais votre fonction donne 128 niveaux à la fois négatifs et positifs, il serait donc plus «juste» de diviser les niveaux de manière inégale, je pense.
Jason
C'est une excellente solution, mais elle échoue si le midpointdes données est égal à 0 ou 1. Voir ma réponse ci-dessous pour une solution simple à ce problème.
DaveTheScientist
22

Voici une solution sous-classant Normaliser. Pour l'utiliser

norm = MidPointNorm(midpoint=3)
imshow(X, norm=norm)

Voici la classe:

import numpy as np
from numpy import ma
from matplotlib import cbook
from matplotlib.colors import Normalize

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 * (value - 0.5)
            if val < 0: 
                return  val*abs(vmin-midpoint) + midpoint
            else:
                return  val*abs(vmax-midpoint) + midpoint
tillsten
la source
Est-il possible d'utiliser cette classe en plus de la mise à l'échelle du journal ou du sym-log sans avoir à créer plus de sous-classes? Mon cas d'utilisation actuel utilise déjà "norm = SymLogNorm (linthresh = 1)"
AnnanFay
Parfait, c'est exactement ce que je recherchais. Peut-être devriez-vous ajouter une image pour démontrer la différence? Ici, le milieu est centré dans la barre, contrairement aux autres normalisateurs de point médian où le point médian peut être déplacé vers les extrémités.
gaborous le
18

Il est plus facile d'utiliser simplement les arguments vminet vmaxpour imshow(en supposant que vous travaillez avec des données d'image) plutôt que de sous-classer matplotlib.colors.Normalize.

Par exemple

import numpy as np
import matplotlib.pyplot as plt

data = np.random.random((10,10))
# Make the data range from about -5 to 10
data = 10 / 0.75 * (data - 0.25)

plt.imshow(data, vmin=-10, vmax=10)
plt.colorbar()

plt.show()

entrez la description de l'image ici

Joe Kington
la source
1
Est-il possible de mettre à jour l'exemple en une courbe gaussienne afin que nous puissions mieux voir la gradation de la couleur?
Dat Chu
3
Je n'aime pas cette solution, car elle n'utilise pas toute la plage dynamique des couleurs disponibles. Je voudrais également un exemple de normalisation pour construire une normalisation de type symlog.
tillsten
2
@tillsten - Je suis confus, alors ... Vous ne pouvez pas utiliser la plage dynamique complète de la barre de couleurs si vous voulez 0 au milieu, non? Vous souhaitez alors une échelle non linéaire? Une échelle pour les valeurs supérieures à 0, une pour les valeurs inférieures? Dans ce cas, oui, vous devrez effectuer une sous-classe Normalize. J'ajouterai un exemple dans un instant (en supposant que quelqu'un d'autre ne me bat pas dessus ...).
Joe Kington
@Joe: Tu as raison, ce n'est pas linéaire (plus exactement deux parties linéaires). En utilisant vmin / vmax, la gamme de couleurs pour les valeurs inférieures à -5 n'est pas utilisée (ce qui a du sens dans certaines applications, mais pas la mienne.).
tillsten
2
pour les données génériques en Z:vmax=abs(Z).max(), vmin=-abs(Z).max()
endolith
12

Ici, je crée une sous-classe de Normalizesuivi d'un exemple minimal.

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt


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

    def __call__(self, value, clip=None):
        normalized_min = max(0, 1 / 2 * (1 - abs((self.midpoint - self.vmin) / (self.midpoint - self.vmax))))
        normalized_max = min(1, 1 / 2 * (1 + abs((self.vmax - self.midpoint) / (self.midpoint - self.vmin))))
        normalized_mid = 0.5
        x, y = [self.vmin, self.midpoint, self.vmax], [normalized_min, normalized_mid, normalized_max]
        return np.ma.masked_array(np.interp(value, x, y))


vals = np.array([[-5., 0], [5, 10]]) 
vmin = vals.min()
vmax = vals.max()

norm = MidpointNormalize(vmin=vmin, vmax=vmax, midpoint=0)
cmap = 'RdBu_r' 

plt.imshow(vals, cmap=cmap, norm=norm)
plt.colorbar()
plt.show()

Résultat: pic-1

Le même exemple avec seulement des données positives vals = np.array([[1., 3], [6, 10]])

pic-2

Propriétés:

  • Le milieu obtient la couleur du milieu.
  • Les plages supérieure et inférieure sont remises à l'échelle par la même transformation linéaire.
  • Seule la couleur qui apparaît sur l'image est affichée dans la barre de couleurs.
  • Semble fonctionner correctement même si vminest plus grand que midpoint(n'a pas testé tous les cas de bord cependant).

Cette solution est inspirée d'une classe du même nom de cette page

icemtel
la source
3
Meilleure réponse en raison de sa simplicité. Les autres réponses ne sont meilleures que si vous êtes déjà un expert Matplotlib essayant de devenir un super-expert. La plupart des demandeurs de réponses matplotlib essaient simplement de faire quelque chose pour rentrer chez eux avec leur chien et / ou leur famille, et pour eux, cette réponse est la meilleure.
sapo_cosmico
Cette solution semble en effet la meilleure, mais ne fonctionne pas! Je viens d'exécuter le script de test et le résultat est complètement différent (n'incluant que les carrés bleus et pas de rouge). @icemtel, pouvez-vous vérifier? (à côté du problème avec l'indentation sur def __call__)
Filipe
Ok, j'ai trouvé le (s) problème (s): les nombres dans le calcul de normalized_minet normalized_maxsont pris comme des entiers. Mettez-les simplement comme 0,0. Aussi, pour obtenir la sortie correcte de votre figure, j'ai dû utiliser vals = sp.array([[-5.0, 0.0], [5.0, 10.0]]) . Merci pour la réponse, en tout cas!
Filipe
Salut @Filipe Je ne peux pas reproduire votre problème sur ma machine (Python 3.7, matplotlib 2.2.3, et je pense que cela devrait être le même sur les versions plus récentes). Quelle version avez-vous? Quoi qu'il en soit, j'ai fait une petite modification rendant le tableau de type float, et corrigé le problème d'indentation. Merci de le signaler
icemtel
Hmm .. Je viens d'essayer avec python3 et ça marche aussi. Mais j'utilise python2.7. Merci pour la correction et pour la réponse. C'est très simple à utiliser! :)
Filipe
5

Je ne sais pas si vous cherchez toujours une réponse. Pour moi, essayer de sous-classer Normalizea échoué. Je me suis donc concentré sur la création manuelle d'un nouvel ensemble de données, de graduations et d'étiquettes de graduation pour obtenir l'effet que je pense que vous visez.

J'ai trouvé le scalemodule dans matplotlib qui a une classe utilisée pour transformer les tracés de lignes par les règles 'syslog', donc je l'utilise pour transformer les données. Ensuite, je redimensionne les données pour qu'elles passent de 0 à 1 (ce Normalizequi fait généralement), mais je redimensionne les nombres positifs différemment des nombres négatifs. C'est parce que votre vmax et vmin peuvent ne pas être les mêmes, donc .5 -> 1 peut couvrir une plage positive plus grande que 0,5 -> 0, la plage négative le fait. Il était plus facile pour moi de créer une routine pour calculer les valeurs de graduation et d'étiquette.

Vous trouverez ci-dessous le code et un exemple de figure.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mpl as mpl
import matplotlib.scale as scale

NDATA = 50
VMAX=10
VMIN=-5
LINTHRESH=1e-4

def makeTickLables(vmin,vmax,linthresh):
    """
    make two lists, one for the tick positions, and one for the labels
    at those positions. The number and placement of positive labels is 
    different from the negative labels.
    """
    nvpos = int(np.log10(vmax))-int(np.log10(linthresh))
    nvneg = int(np.log10(np.abs(vmin)))-int(np.log10(linthresh))+1
    ticks = []
    labels = []
    lavmin = (np.log10(np.abs(vmin)))
    lvmax = (np.log10(np.abs(vmax)))
    llinthres = int(np.log10(linthresh))
    # f(x) = mx+b
    # f(llinthres) = .5
    # f(lavmin) = 0
    m = .5/float(llinthres-lavmin)
    b = (.5-llinthres*m-lavmin*m)/2
    for itick in range(nvneg):
        labels.append(-1*float(pow(10,itick+llinthres)))
        ticks.append((b+(itick+llinthres)*m))
    # add vmin tick
    labels.append(vmin)
    ticks.append(b+(lavmin)*m)

    # f(x) = mx+b
    # f(llinthres) = .5
    # f(lvmax) = 1
    m = .5/float(lvmax-llinthres)
    b = m*(lvmax-2*llinthres) 
    for itick in range(1,nvpos):
        labels.append(float(pow(10,itick+llinthres)))
        ticks.append((b+(itick+llinthres)*m))
    # add vmax tick
    labels.append(vmax)
    ticks.append(b+(lvmax)*m)

    return ticks,labels


data = (VMAX-VMIN)*np.random.random((NDATA,NDATA))+VMIN

# define a scaler object that can transform to 'symlog'
scaler = scale.SymmetricalLogScale.SymmetricalLogTransform(10,LINTHRESH)
datas = scaler.transform(data)

# scale datas so that 0 is at .5
# so two seperate scales, one for positive and one for negative
data2 = np.where(np.greater(data,0),
                 .75+.25*datas/np.log10(VMAX),
                 .25+.25*(datas)/np.log10(np.abs(VMIN))
                 )

ticks,labels=makeTickLables(VMIN,VMAX,LINTHRESH)

cmap = mpl.cm.jet
fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(data2,cmap=cmap,vmin=0,vmax=1)
cbar = plt.colorbar(im,ticks=ticks)
cbar.ax.set_yticklabels(labels)

fig.savefig('twoscales.png')

vmax = 10, vmin = -5 et linthresh = 1e-4

N'hésitez pas à ajuster les "constantes" (par exemple VMAX) en haut du script pour confirmer qu'il se comporte bien.

Yann
la source
Merci pour votre suggestion, comme indiqué ci-dessous, j'ai eu du succès dans le sous-classement. Mais votre code est toujours très utile pour faire les bons ticklabels.
tillsten
4

J'utilisais l'excellente réponse de Paul H, mais j'ai rencontré un problème car certaines de mes données allaient de négatives à positives, tandis que d'autres ensembles allaient de 0 à positif ou de négatif à 0; dans les deux cas, je voulais que 0 soit coloré en blanc (le point médian de la palette de couleurs que j'utilise). Avec l'implémentation existante, si votre midpointvaleur est égale à 1 ou 0, les mappages d'origine n'étaient pas écrasés. Vous pouvez le voir dans l'image suivante: graphiques avant modification La 3ème colonne semble correcte, mais la zone bleu foncé dans la 2ème colonne et la zone rouge foncé dans les colonnes restantes sont toutes supposées être blanches (leurs valeurs de données sont en fait 0). Utiliser mon correctif me donne: graphiques après modification Ma fonction est essentiellement la même que celle de Paul H, avec mes modifications au début de la forboucle:

def shiftedColorMap(cmap, min_val, max_val, name):
    '''Function to offset the "center" of a colormap. Useful for data with a negative min and positive max and you want the middle of the colormap's dynamic range to be at zero. Adapted from /programming/7404116/defining-the-midpoint-of-a-colormap-in-matplotlib

    Input
    -----
      cmap : The matplotlib colormap to be altered.
      start : Offset from lowest point in the colormap's range.
          Defaults to 0.0 (no lower ofset). Should be between
          0.0 and `midpoint`.
      midpoint : The new center of the colormap. Defaults to
          0.5 (no shift). Should be between 0.0 and 1.0. In
          general, this should be  1 - vmax/(vmax + abs(vmin))
          For example if your data range from -15.0 to +5.0 and
          you want the center of the colormap at 0.0, `midpoint`
          should be set to  1 - 5/(5 + 15)) or 0.75
      stop : Offset from highets point in the colormap's range.
          Defaults to 1.0 (no upper ofset). Should be between
          `midpoint` and 1.0.'''
    epsilon = 0.001
    start, stop = 0.0, 1.0
    min_val, max_val = min(0.0, min_val), max(0.0, max_val) # Edit #2
    midpoint = 1.0 - max_val/(max_val + abs(min_val))
    cdict = {'red': [], 'green': [], 'blue': [], 'alpha': []}
    # regular index to compute the colors
    reg_index = np.linspace(start, stop, 257)
    # shifted index to match the data
    shift_index = np.hstack([np.linspace(0.0, midpoint, 128, endpoint=False), np.linspace(midpoint, 1.0, 129, endpoint=True)])
    for ri, si in zip(reg_index, shift_index):
        if abs(si - midpoint) < epsilon:
            r, g, b, a = cmap(0.5) # 0.5 = original midpoint.
        else:
            r, g, b, a = cmap(ri)
        cdict['red'].append((si, r, r))
        cdict['green'].append((si, g, g))
        cdict['blue'].append((si, b, b))
        cdict['alpha'].append((si, a, a))
    newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
    plt.register_cmap(cmap=newcmap)
    return newcmap

EDIT: J'ai rencontré à nouveau un problème similaire lorsque certaines de mes données allaient d'une petite valeur positive à une valeur positive plus grande, où les valeurs très faibles étaient colorées en rouge au lieu de blanc. Je l'ai corrigé en ajoutant une ligne Edit #2dans le code ci-dessus.

DaveTheScientist
la source
Cela a l'air sympa, mais il semble que les arguments ont changé depuis la réponse de Paul H (et les commentaires) ... Pouvez-vous ajouter un exemple d'appel à votre réponse?
Filipe
1

Si cela ne vous dérange pas de calculer le rapport entre vmin, vmax et zéro, il s'agit d'une jolie carte linéaire de base allant du bleu au blanc au rouge, qui définit le blanc en fonction du rapport z:

def colormap(z):
    """custom colourmap for map plots"""

    cdict1 = {'red': ((0.0, 0.0, 0.0),
                      (z,   1.0, 1.0),
                      (1.0, 1.0, 1.0)),
              'green': ((0.0, 0.0, 0.0),
                        (z,   1.0, 1.0),
                        (1.0, 0.0, 0.0)),
              'blue': ((0.0, 1.0, 1.0),
                       (z,   1.0, 1.0),
                       (1.0, 0.0, 0.0))
              }

    return LinearSegmentedColormap('BlueRed1', cdict1)

Le format cdict est assez simple: les lignes sont des points dans le dégradé qui est créé: la première entrée est la valeur x (le rapport le long du dégradé de 0 à 1), la seconde est la valeur finale du segment précédent, et la troisième est la valeur de départ du segment suivant - si vous voulez des dégradés lisses, les deux derniers sont toujours les mêmes. Consultez la documentation pour plus de détails.

rien101
la source
1
Il existe également la possibilité de spécifier dans les LinearSegmentedColormap.from_list()tuples (val,color)et de les passer sous forme de liste à l' colorargument de cette méthode où val0=0<val1<...<valN==1.
maurizio
0

J'ai eu un problème similaire, mais je voulais que la valeur la plus élevée soit entièrement rouge et coupe les faibles valeurs de bleu, ce qui donne l'impression que le bas de la barre de couleurs a été coupé. Cela a fonctionné pour moi (inclut la transparence facultative):

def shift_zero_bwr_colormap(z: float, transparent: bool = True):
    """shifted bwr colormap"""
    if (z < 0) or (z > 1):
        raise ValueError('z must be between 0 and 1')

    cdict1 = {'red': ((0.0, max(-2*z+1, 0), max(-2*z+1, 0)),
                      (z,   1.0, 1.0),
                      (1.0, 1.0, 1.0)),

              'green': ((0.0, max(-2*z+1, 0), max(-2*z+1, 0)),
                        (z,   1.0, 1.0),
                        (1.0, max(2*z-1,0),  max(2*z-1,0))),

              'blue': ((0.0, 1.0, 1.0),
                       (z,   1.0, 1.0),
                       (1.0, max(2*z-1,0), max(2*z-1,0))),
              }
    if transparent:
        cdict1['alpha'] = ((0.0, 1-max(-2*z+1, 0), 1-max(-2*z+1, 0)),
                           (z,   0.0, 0.0),
                           (1.0, 1-max(2*z-1,0),  1-max(2*z-1,0)))

    return LinearSegmentedColormap('shifted_rwb', cdict1)

cmap =  shift_zero_bwr_colormap(.3)

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) * 5 + 5
plt.plot([0, 10*np.pi], [0, 20*np.pi], color='c', lw=20, zorder=-3)
plt.imshow(Z, interpolation='nearest', origin='lower', cmap=cmap)
plt.colorbar()
ben.dichter
la source