pyplot TwoSlopeNorm LinearSegmentedColormap 中零值的唯一颜色

Unique color for zero values in pyplot TwoSlopeNorm LinearSegmentedColormap

我想在数据为零的绘图上使用自定义(蓝色)颜色。我试过 set_under 方法,但失败了。所需的输出将是图形底部的一条蓝线和图形上部的两个蓝色方块。感谢任何帮助。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap,TwoSlopeNorm

# TwoSlopeNorm see:
# https://matplotlib.org/devdocs/tutorials/colors/colormapnorms.html#sphx-glr-tutorials-colors-colormapnorms-py
red2orange = np.array([np.linspace(1, 1, 256),
                       np.linspace(0, 165/256, 256),
                       np.linspace(0, 0, 256),
                       np.ones(256)]).T
grey2black = np.array([np.linspace(0.75, 0.25, 256),
                       np.linspace(0.75, 0.25, 256),
                       np.linspace(0.75, 0.25, 256),
                       np.ones(256)]).T
all_colors = np.vstack((grey2black,red2orange))
cmap = LinearSegmentedColormap.from_list('two_slope_cmap', all_colors)
divnorm = TwoSlopeNorm(vmin=1, vcenter=400, vmax=1000)

# seting bad and under
cmap.set_bad('mediumspringgreen')
cmap.set_under('blue')

#fake data
data = np.arange(1400)[:,None]* np.ones(200)
data[ 1100:1150, 50:150] = np.nan # bad data
data[ 1200:1250, 50:150] = 0 # zero data
data[ 1300:1350, 50:150] = -1 # under data

# plot
f,a = plt.subplots()
raster = a.pcolormesh(data,cmap=cmap, norm=divnorm)
cbar = f.colorbar(raster,ax=a, extend='both')

由于某些未知原因,TwoSlopeNorm 似乎不支持 underover 颜色。将代码更改为使用 plt.Normalize() 而不是 TwoSlopeNorm() 表示对于该规范,under 颜色按预期工作。

一种解决方法是再次绘制 pcolormesh,仅绘制 under 颜色。一个缺点是底色没有显示在颜色条扩展中。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, ListedColormap, TwoSlopeNorm

red2orange = np.array([np.linspace(1, 1, 256),
                       np.linspace(0, 165 / 256, 256),
                       np.linspace(0, 0, 256),
                       np.ones(256)]).T
grey2black = np.array([np.linspace(0.75, 0.25, 256),
                       np.linspace(0.75, 0.25, 256),
                       np.linspace(0.75, 0.25, 256),
                       np.ones(256)]).T
all_colors = np.vstack((grey2black, red2orange))
cmap = LinearSegmentedColormap.from_list('two_slope_cmap', all_colors)
divnorm = TwoSlopeNorm(vmin=1, vcenter=400, vmax=1000)

# seting bad and under
cmap.set_bad('mediumspringgreen')
cmap.set_under('dodgerblue')  # this doesn't seem to be used with a TwoSlopeNorm

# fake data
data = np.arange(1400)[:, None] * np.ones(200)
data[1100:1150, 50:150] = np.nan  # bad data
data[1200:1250, 50:150] = 0  # zero data
data[1300:1350, 50:150] = -1  # under data

# plot
f, a = plt.subplots()
raster = a.pcolormesh(data, cmap=cmap, norm=divnorm)
cbar = f.colorbar(raster, ax=a, extend='both')

# draw the mesh a second time, only for the under color
a.pcolormesh(np.where(data < 1, 0, np.nan), cmap=ListedColormap([cmap.get_under()]))

plt.show()

另一种解决方法是更改​​颜色图本身,将最低颜色设置为所需的底色。缺点是 vmin 需要移动一点。 (距离似乎是 vmin 和 vcenter 之间距离的 1/127。在内部,颜色图保持 256 种颜色,vcenter 的颜色在位置 128。)

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, TwoSlopeNorm, to_rgba

red2orange = np.array([np.linspace(1, 1, 256),
                       np.linspace(0, 165 / 256, 256),
                       np.linspace(0, 0, 256),
                       np.ones(256)]).T
grey2black = np.array([np.linspace(0.75, 0.25, 256),
                       np.linspace(0.75, 0.25, 256),
                       np.linspace(0.75, 0.25, 256),
                       np.ones(256)]).T
all_colors = np.vstack((grey2black, red2orange))
all_colors[0, :] = to_rgba('dodgerblue')
cmap = LinearSegmentedColormap.from_list('two_slope_cmap', all_colors)
vmin = 1
vcenter = 400
vmax = 1000
divnorm = TwoSlopeNorm(vmin=vmin-(vcenter-vmin)/127, vcenter=vcenter, vmax=vmax)

# seting bad and under
cmap.set_bad('mediumspringgreen')
cmap.set_under('red')  # this doesn't seem to be used with a TwoSlopeNorm

# fake data
data = np.arange(1400)[:, None] * np.ones(200)
data[1100:1150, 50:150] = np.nan  # bad data
data[1200:1250, 50:150] = 0  # zero data
data[1300:1350, 50:150] = -1  # under data

# plot
f, a = plt.subplots()
raster = a.pcolormesh(data, cmap=cmap, norm=divnorm)
cbar = f.colorbar(raster, ax=a, extend='both')
plt.show()

PS:下面的代码试图可视化两个规范如何以不同方式处理 under 颜色:

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

fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10, 3))

data = np.arange(0, 100)[:, None]
cmap = plt.get_cmap('viridis').copy()
cmap.set_under('crimson')
cmap.set_over('skyblue')

norm1 = plt.Normalize(20, 80)
im1 = ax1.imshow(data, cmap=cmap, norm=norm1, aspect='auto', origin='lower', interpolation='nearest')
plt.colorbar(im1, ax=ax1, extend='both')
ax1.set_title('using plt.Normalize()')

norm2 = TwoSlopeNorm(vmin=20, vcenter=30, vmax=80)
im2 = ax2.imshow(data, cmap=cmap, norm=norm2, aspect='auto', origin='lower', interpolation='nearest')
plt.colorbar(im2, ax=ax2, extend='both')
ax2.set_title('using TwoSlopeNorm')

plt.show()

PPS: 在github查看TwoSlopeNorm的源码,下个版本的matplotlib(当前版本是3.4.3)似乎解决了这个问题。所以,你可以尝试安装开发版。 (更改涉及将 left=-np.inf, right=np.inf 作为参数添加到 colors.pyTwoSlopeNorm class 的 __call__ 方法中的 np.interp