Определение средней точки палитры в matplotlib

87

Я хочу установить среднюю точку цветовой карты, то есть мои данные идут от -5 до 10, я хочу, чтобы ноль был серединой. Я думаю, что способ сделать это - создать подклассы normalize и использовать норму, но я не нашел ни одного примера, и мне непонятно, что именно я должен реализовать.

Tillsten
источник
это называется «расходящейся» или «биполярной» цветовой картой, где важна центральная точка карты, а данные идут выше и ниже этой точки. sandia.gov/~kmorel/documents/ColorMaps
эндолиты
3
Все ответы в этой ветке кажутся довольно сложными. Простое в использовании решение показано в этом отличном ответе , который тем временем также вошел в документацию matplotlib, раздел Пользовательская нормализация: два линейных диапазона .
ImportanceOfBeingErnest

Ответы:

15

Обратите внимание, что в matplotlib версии 3.1 был добавлен класс DivergingNorm . Я думаю, это касается вашего варианта использования. Его можно использовать так:

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

В matplotlib 3.2 класс был переименован в TwoSlopesNorm

макКайвер
источник
Это выглядит интересно, но кажется, что это нужно использовать для преобразования данных перед построением графика. Легенда цветовой шкалы будет относиться к преобразованным данным, а не к исходным.
bli
3
@bli это не так. normделает нормализацию для вашего образа. normsидут рука об руку с палитрой.
Пол Х
1
Досадно, что это устарело с версии 3.2 без документации о том, как его заменить: matplotlib.org/3.2.0/api/_as_gen/…
daknowles
1
Да, документы неясны. Я думаю, он был переименован в TwoSlopeNorm: matplotlib.org/3.2.0/api/_as_gen/…
macKaiver
91

Я знаю, что это поздно для игры, но я просто прошел через этот процесс и придумал решение, которое, возможно, менее надежно, чем подкласс нормализации, но гораздо проще. Я подумал, что было бы хорошо поделиться этим здесь для потомков.

Функция

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

Пример

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([])

Результаты примера:

введите описание изображения здесь

Пол Х
источник
Большое спасибо за ваш потрясающий вклад! Однако код не мог одновременно обрезать и сдвигать одну и ту же цветовую карту, и ваши инструкции были немного неточными и вводящими в заблуждение. Теперь я исправил это и взял на себя смелость отредактировать ваш пост. Кроме того, я включил его в одну из моих личных библиотек и добавил вас в качестве автора. Надеюсь, вы не возражаете.
TheChymera
@TheChymera цветовая карта в правом нижнем углу была обрезана и повторно центрирована. Не стесняйтесь использовать это по своему усмотрению.
Paul H
Да, это так, к сожалению, это выглядит примерно как совпадение. Если startи stopне равны 0 и 1 соответственно, после того, как вы это сделаете reg_index = np.linspace(start, stop, 257), вы больше не сможете предполагать, что значение 129 является средней точкой исходного cmap, поэтому полное изменение масштаба не имеет смысла при кадрировании. Кроме того, startдолжно быть от 0 до 0,5 и stopот 0,5 до 1, а не от 0 до 1 одновременно, как вы указываете.
TheChymera
@TheChymera Я попробовал вашу версию, и у меня было две мысли по этому поводу. 1) мне кажется, что все индексы, которые вы создали, имеют длину 257, а в matplotlib по умолчанию установлено 256, я полагаю? 2) предположим, что мои данные находятся в диапазоне от -1 до 1000, в нем преобладают положительные элементы, и поэтому больше уровней / слоев должно идти в положительную ветвь. Но ваша функция дает 128 уровней как для отрицательных, так и для положительных сторон, поэтому, я думаю, было бы более «справедливым» разделить уровни неравномерно.
Джейсон
Это отличное решение, но оно не работает, если midpointданные равны 0 или 1. См. Мой ответ ниже, где описано простое решение этой проблемы.
DaveTheScientist
22

Вот подкласс решения Normalize. Использовать это

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

Вот класс:

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
источник
Можно ли использовать этот класс в дополнение к масштабированию журнала или символьного журнала без необходимости создавать дополнительные подклассы? В моем текущем варианте использования уже используется "norm = SymLogNorm (linthresh = 1)"
AnnanFay
Отлично, это именно то, что я искал. Может, стоит добавить картинку, чтобы продемонстрировать разницу? Здесь средняя точка находится в центре полосы, в отличие от других нормализаторов средней точки, где среднюю точку можно перетащить к конечностям.
gaborous
18

Это проще всего просто использовать vminи vmaxаргументы imshow(при условии , что вы работаете с данными изображения) , а не на подклассы matplotlib.colors.Normalize.

Например

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()

введите описание изображения здесь

Джо Кингтон
источник
1
Можно ли обновить пример до гауссовой кривой, чтобы мы могли лучше видеть градацию цвета?
Дат Чу
3
Мне это решение не нравится, потому что оно не использует весь динамический диапазон доступных цветов. Также я хотел бы привести пример нормализации для создания символьной нормализации.
tillsten
2
@tillsten - Я запутался, тогда ... Вы не можете использовать полный динамический диапазон шкалы палитры, если хотите 0 посередине, верно? Значит, вам нужна нелинейная шкала? Одна шкала для значений выше 0, одна для значений ниже? В этом случае, да, вам нужно создать подкласс Normalize. Я добавлю пример чуть позже (при условии, что кто-то другой меня не опередит ...).
Джо Кингтон,
@Joe: Вы правы, это не линейно (точнее, две линейные части). При использовании vmin / vmax цветовой диапазон для значений меньше -5 не используется (что имеет смысл в некоторых приложениях, но не в моем).
tillsten
2
для общих данных в Z:vmax=abs(Z).max(), vmin=-abs(Z).max()
endolith
12

Здесь я создаю подкласс, за Normalizeкоторым следует минимальный пример.

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()

Результат: рис-1

Тот же пример только с положительными данными vals = np.array([[1., 3], [6, 10]])

рис-2

Свойства:

  • Средняя точка получает средний цвет.
  • Верхний и нижний диапазоны масштабируются с помощью одного и того же линейного преобразования.
  • На палитре цветов отображается только тот цвет, который изображен на картинке.
  • Кажется, работает нормально, даже если vminбольше, чем midpoint(хотя не проверял все крайние случаи).

Это решение вдохновлено классом с таким же именем на этой странице.

Icemtel
источник
3
Лучший ответ благодаря своей простоте. Другие ответы лучше всего подходят только в том случае, если вы уже являетесь экспертом по Matplotlib, пытающимся стать супер-экспертом. Большинство искателей ответов на matplotlib просто пытаются что-то сделать, чтобы вернуться домой к своей собаке и / или семье, и для них этот ответ является лучшим.
sapo_cosmico 07
Это решение действительно кажется лучшим, но не работает! Я только что запустил тестовый сценарий, и результат совершенно другой (включая только синие квадраты, а не красный). @icemtel, вы можете проверить? (помимо проблемы с отступом def __call__)
Filipe
Хорошо, я нашел проблему (ы): числа при вычислении normalized_minи normalized_maxпринимаются как целые числа. Просто поставьте их как 0,0. Также, чтобы получить правильный вывод вашей фигуры, мне пришлось использовать vals = sp.array([[-5.0, 0.0], [5.0, 10.0]]) . В любом случае спасибо за ответ!
Filipe
Привет @Filipe, я не могу воспроизвести вашу проблему на своем компьютере (Python 3.7, matplotlib 2.2.3, и я думаю, что в более новых версиях должно быть то же самое). Какая у тебя версия? В любом случае, я сделал небольшую правку, сделав массив типа float, и исправил проблему с отступом. Спасибо, что указали на это
icemtel
Хм .. Я только что пробовал с python3, он тоже работает. Но я использую python2.7. Спасибо за исправление и за ответ. Пользоваться очень просто! :)
Filipe
5

Не уверен, что вы все еще ищете ответ. Для меня попытка создания подкласса Normalizeне увенчалась успехом. Поэтому я сосредоточился на ручном создании нового набора данных, меток и меток, чтобы получить эффект, к которому, я думаю, вы стремитесь.

Я нашел scaleмодуль в matplotlib, у которого есть класс, используемый для преобразования линейных графиков по правилам syslog, поэтому я использую его для преобразования данных. Затем я масштабирую данные так, чтобы они менялись от 0 до 1 (чтоNormalize обычно и происходит), но я масштабирую положительные числа иначе, чем отрицательные числа. Это связано с тем, что ваши vmax и vmin могут не совпадать, поэтому .5 -> 1 может охватывать больший положительный диапазон, чем .5 -> 0, отрицательный диапазон имеет. Мне было проще создать процедуру для расчета значений тиков и меток.

Ниже приведен код и пример рисунка.

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 и linthresh = 1e-4

Не стесняйтесь настраивать «константы» (например VMAX) в верхней части скрипта, чтобы убедиться, что он ведет себя хорошо.

Янн
источник
Спасибо за ваше предложение, как показано ниже, я успешно создал подклассы. Но ваш код по-прежнему очень полезен для создания правильных меток.
tillsten
4

Я использовал отличный ответ от Paul H, но столкнулся с проблемой, потому что некоторые из моих данных варьировались от отрицательных до положительных, а другие наборы варьировались от 0 до положительных или от отрицательных до 0; в любом случае я хотел, чтобы 0 был окрашен в белый цвет (середина цветовой карты, которую я использую). В существующей реализации, если ваше midpointзначение равно 1 или 0, исходные сопоставления не перезаписывались. Вы можете видеть это на следующем рисунке: графики до редактирования 3-й столбец выглядит правильно, но темно-синяя область во 2-м столбце и темно-красная область в остальных столбцах должны быть белыми (их значения данных фактически равны 0). Использование моего исправления дает мне: графики после редактирования Моя функция по сути такая же, как у Пола Х, с моими правками в начале forцикла:

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 #2в код выше.

Дэйв Ученый
источник
Это выглядит красиво, но кажется, что аргументы изменились по сравнению с ответом Пола Х (и комментариями) ... Вы можете добавить пример вызова к своему ответу?
Filipe
1

Если вы не возражаете против определения соотношения между vmin, vmax и нулем, это довольно простая линейная карта от синего к белому и к красному, которая устанавливает белый цвет в соответствии с соотношением 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)

Формат cdict довольно прост: строки - это точки в создаваемом градиенте: первая запись - это значение x (соотношение по градиенту от 0 до 1), вторая - конечное значение для предыдущего сегмента и третий - начальное значение для следующего сегмента - если вы хотите плавные градиенты, последние два всегда одинаковы. Подробнее см. В документации .

ничто101
источник
1
Существует также возможность указать в LinearSegmentedColormap.from_list()кортежах (val,color)и передать их как список colorаргументу этого метода where val0=0<val1<...<valN==1.
Маурицио
0

У меня была аналогичная проблема, но я хотел, чтобы максимальное значение было полностью красным, а низкие значения синего отсекали, чтобы это выглядело по существу так, как будто нижняя часть шкалы цветов была обрезана. Это сработало для меня (включая дополнительную прозрачность):

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()
Бен Дихтер
источник