使用 Matplotlib 的任意非线性颜色条
Arbirtrary non-linear colorbar using Matplotlib
我想使用 Networkx 和 Matplotlib 为网络的边缘着色,其中每个边缘 (i,j)
被赋予一个介于 0 和 1 之间的值 G[i][j]['label']
。
然而,这些值通常要么非常接近于 0,要么非常接近 1。然后很难将颜色的变化可视化,因为所有东西要么非常红,要么非常蓝(使用 coolwarm
颜色图)。
然后,我的想法是应用过滤器 filtR
,例如以下过滤器之一:
它只是一个多项式函数,提供从 [0,1] 到 [0,1] 的双射,并在 0 或 1 附近扩展更多值。如果需要,逆函数很容易处理。
现在,我只是将它应用到边缘的值,以定义它的颜色:
cm = plt.get_cmap('coolwarm')
cNorm = colors.Normalize(vmin=0., vmax=1.)
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
colorList = []
# The color is defined by filtR(G[i][j]['label'])
val_map = {(i,j): filtR(G[i][j]['label']) for (i,j) in G.edges()}
values = [scalarMap.to_rgba(val_map[e]) for e in G.edges()]
edges = nx.draw_networkx_edges(G,edge_color=values,edge_cmap=plt.get_cmap('coolwarm'))
# Definition of the colorbar :-(
sm = cmx.ScalarMappable(cmap=cmx.coolwarm)
sm.set_array(values)
plt.colorbar(sm)
现在的问题是: 我想定义对应的colorbar
现在,它显示了 filtR
函数对我的边缘的评估,这是没有意义的:过滤器的唯一目的是修改 [0,1] 区间上的颜色重新分配为了提高图表的可读性。
例如,我得到:
我对左边部分很满意,但对右边部分不满意,颜色栏应该是这样的:
这里的filter函数显然不是最好的,但是应该给大家一个更好的说明。
我试图在定义颜色条之前重新定义 values
:
# Definition of the colorbar :-(
new_val_map = {(i,j): filtR(G[i][j]['label']) for (i,j) in G.edges()}
new_values = [scalarMap.to_rgba(val_map[e]) for e in G.edges()]
sm = cmx.ScalarMappable(cmap=cmx.coolwarm)
sm.set_array(new_values)
plt.colorbar(sm)
但没有任何变化。
我对 Matplotlib 的理解有点有限,所提供的代码已经是堆栈溢出答案的拼凑而成。
您必须定义您的 own custom colormap 并在自定义 cbar 中使用它:
import matplotlib.pylab as plt
from matplotlib import colorbar, colors
def make_colormap(seq, name='mycmap'):
"""Return a LinearSegmentedColormap
seq: a sequence of floats and RGB-tuples. The floats should be increasing
and in the interval (0,1).
"""
seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
cdict = {'red': [], 'green': [], 'blue': []}
for i, item in enumerate(seq):
if isinstance(item, float):
r1, g1, b1 = seq[i - 1]
r2, g2, b2 = seq[i + 1]
cdict['red'].append([item, r1, r2])
cdict['green'].append([item, g1, g2])
cdict['blue'].append([item, b1, b2])
return colors.LinearSegmentedColormap(name, cdict)
def generate_cmap(lowColor, highColor, lowBorder, highBorder):
"""Apply edge colors till borders and middle is in grey color"""
c = colors.ColorConverter().to_rgb
return make_colormap([c(lowColor), c('grey'),l owBorder, c('grey'), .5, \
c('grey'), highBorder ,c('grey'), c(highColor)])
fig = plt.figure()
ax = fig.add_axes([.05, .05, .02, .7]) # position of colorbar
cbar = colorbar.ColorbarBase(ax, cmap=generate_cmap('b','r',.15,.85),
norm=colors.Normalize(vmin=.0, vmax=1)) # set min, max of colorbar
ticks = [0.,.1,.2,.3,.4,.5,.6,.7,.8,.9,1.]
cbar.set_ticks(ticks) # add ticks
plt.show()
你有你最喜欢的颜色图(比如 coolwarm
),你想根据 filtR
函数扭曲它:
Nb :此函数与最初问题中建议的函数相反。
感谢Serenity的指点:颜色图定义的工作还需完成:
def distortColorMap(cm,inv = lambda x:x):
"""Inspired from 'make_colormap' in Serenity's answer.
Inputs : a pre-existing colormap cm,
the distorsion function inv
Output : the distorted colormap"""
def f(color,inv):
"""In the sequence definition, modifies the position of stops tup[0] according the transformation function.
Returns the distorted sequence."""
return map(lambda tup:(inv(tup[0]),tup[1],tup[2]),color)
# Extract sequences from cm, apply inv
C = cm.__dict__['_segmentdata']
cdict = {'red': f(C['red'] ,inv), 'green': f(C['green'],inv), 'blue': f(C['blue'] ,inv)}
name = 'new_'+cm.__dict__['name']
return colors.LinearSegmentedColormap(name, cdict)
那么,这就非常好用了:
cm = plt.get_cmap('coolwarm')
cm = distortColorMap(cm,inv = filtR) # all the job is done here
cNorm = colors.Normalize(vmin=0., vmax=1.)
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
# The color is the natural value G[i][j]['label']
val_map = {(i,j): G[i][j]['label'] for (i,j) in G.edges()}
values = [scalarMap.to_rgba(val_map[e]) for e in G.edges()]
edges = nx.draw_networkx_edges(G,edge_color=values,edge_cmap=plt.get_cmap('coolwarm'))
# Definition of the colorbar : just use the new colormap
sm = cmx.ScalarMappable(cmap=cm)
sm.set_array(values)
plt.colorbar(sm)
然后我们得到相应的颜色条:
这很酷,因为您不再需要定义整个颜色序列(现在一切都从失真函数的定义中完成),并且因为您仍然可以使用 Matplotlib 提供的精美颜色图!
编辑
有关 filtR
功能和我的动机的更多信息。
在此示例中,filtR
定义为:
exponent = 7.
filtR = lambda y: ((2*y-1)**(1./exponent)+1.)/2.
对于 exponent
的不同值,我们有一个 class 函数(具有或多或少的平滑行为)。能够从一种定义跳到另一种定义有助于确定最佳可视化效果。
实际上,对于任何e
(奇偶数),Python不喜欢在x为负数时处理x**1/e
。但这没什么大不了的,我们只需正确定义 7 根(或任何其他奇数指数)即可。
然而,这不是热点:我们只需要从 [0,1] 到 [0,1] 的数学双射。然后我们就可以选择最符合我们需要的那个。
例如,我们也可以将 filtR
函数定义为 filtR = lambda y: y**4
,因为我们希望在最低值上有更好的可读性。我们会得到:
它也应该适用于对数、分段或阶梯函数...
我想要一个通用且灵活的工具,它可以让我快速专注于某些特定领域。我不想为每个可视化测试手动创建带有停止点和颜色值的序列。
如果需要,我还希望能够将这项工作重新用于其他项目。
基本上您根本不想更改颜色图。 Instaed 你想创建你的自定义规范化。为此,您可以子类化 matplotlib.colors.Normalize
并让它 return 自定义函数的值。该函数需要将 vmin
和 vmax
之间的值作为输入,并将 return 范围内的值作为输入 [0,1].
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors as mcolors
class MyNormalize(mcolors.Normalize):
def __call__(self, value, clip=None):
# function to normalize any input between vmin and vmax linearly to [0,1]
n = lambda x: (x-self.vmin)/(self.vmax-self.vmin)
# nonlinear function between [0,1] and [0,1]
f = lambda x,a: (2*x)**a*(2*x<1)/2. +(2-(2*(1-1*x))**a)*(2*x>=1)/2.
return np.ma.masked_array(f(n(value),0.5))
fig, (ax,ax2) = plt.subplots(ncols=2)
x = np.linspace(-0.3,1.2, num=101)
X = (np.sort(np.random.rand(100))*1.5-0.3)
norm= MyNormalize(vmin=-0.3, vmax=1.2)
ax.plot(x,norm(x))
im = ax2.imshow(X[::-1,np.newaxis], norm=norm, cmap="coolwarm", aspect="auto")
fig.colorbar(im)
plt.show()
所需颜色条的图像更像是一个部分线性函数,就像下面使用的蜜蜂一样。
class MyNormalize2(mcolors.Normalize):
def __call__(self, value, clip=None):
n = lambda x: self.vmin+(self.vmax-self.vmin)*x
x, y = [self.vmin, n(0.2), n(0.8), self.vmax], [0, 0.48,0.52, 1]
return np.ma.masked_array(np.interp(value, x, y))
我想使用 Networkx 和 Matplotlib 为网络的边缘着色,其中每个边缘 (i,j)
被赋予一个介于 0 和 1 之间的值 G[i][j]['label']
。
然而,这些值通常要么非常接近于 0,要么非常接近 1。然后很难将颜色的变化可视化,因为所有东西要么非常红,要么非常蓝(使用 coolwarm
颜色图)。
然后,我的想法是应用过滤器 filtR
,例如以下过滤器之一:
它只是一个多项式函数,提供从 [0,1] 到 [0,1] 的双射,并在 0 或 1 附近扩展更多值。如果需要,逆函数很容易处理。
现在,我只是将它应用到边缘的值,以定义它的颜色:
cm = plt.get_cmap('coolwarm')
cNorm = colors.Normalize(vmin=0., vmax=1.)
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
colorList = []
# The color is defined by filtR(G[i][j]['label'])
val_map = {(i,j): filtR(G[i][j]['label']) for (i,j) in G.edges()}
values = [scalarMap.to_rgba(val_map[e]) for e in G.edges()]
edges = nx.draw_networkx_edges(G,edge_color=values,edge_cmap=plt.get_cmap('coolwarm'))
# Definition of the colorbar :-(
sm = cmx.ScalarMappable(cmap=cmx.coolwarm)
sm.set_array(values)
plt.colorbar(sm)
现在的问题是: 我想定义对应的colorbar
现在,它显示了 filtR
函数对我的边缘的评估,这是没有意义的:过滤器的唯一目的是修改 [0,1] 区间上的颜色重新分配为了提高图表的可读性。
例如,我得到:
我对左边部分很满意,但对右边部分不满意,颜色栏应该是这样的:
这里的filter函数显然不是最好的,但是应该给大家一个更好的说明。
我试图在定义颜色条之前重新定义 values
:
# Definition of the colorbar :-(
new_val_map = {(i,j): filtR(G[i][j]['label']) for (i,j) in G.edges()}
new_values = [scalarMap.to_rgba(val_map[e]) for e in G.edges()]
sm = cmx.ScalarMappable(cmap=cmx.coolwarm)
sm.set_array(new_values)
plt.colorbar(sm)
但没有任何变化。
我对 Matplotlib 的理解有点有限,所提供的代码已经是堆栈溢出答案的拼凑而成。
您必须定义您的 own custom colormap 并在自定义 cbar 中使用它:
import matplotlib.pylab as plt
from matplotlib import colorbar, colors
def make_colormap(seq, name='mycmap'):
"""Return a LinearSegmentedColormap
seq: a sequence of floats and RGB-tuples. The floats should be increasing
and in the interval (0,1).
"""
seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
cdict = {'red': [], 'green': [], 'blue': []}
for i, item in enumerate(seq):
if isinstance(item, float):
r1, g1, b1 = seq[i - 1]
r2, g2, b2 = seq[i + 1]
cdict['red'].append([item, r1, r2])
cdict['green'].append([item, g1, g2])
cdict['blue'].append([item, b1, b2])
return colors.LinearSegmentedColormap(name, cdict)
def generate_cmap(lowColor, highColor, lowBorder, highBorder):
"""Apply edge colors till borders and middle is in grey color"""
c = colors.ColorConverter().to_rgb
return make_colormap([c(lowColor), c('grey'),l owBorder, c('grey'), .5, \
c('grey'), highBorder ,c('grey'), c(highColor)])
fig = plt.figure()
ax = fig.add_axes([.05, .05, .02, .7]) # position of colorbar
cbar = colorbar.ColorbarBase(ax, cmap=generate_cmap('b','r',.15,.85),
norm=colors.Normalize(vmin=.0, vmax=1)) # set min, max of colorbar
ticks = [0.,.1,.2,.3,.4,.5,.6,.7,.8,.9,1.]
cbar.set_ticks(ticks) # add ticks
plt.show()
你有你最喜欢的颜色图(比如 coolwarm
),你想根据 filtR
函数扭曲它:
Nb :此函数与最初问题中建议的函数相反。
感谢Serenity的指点:颜色图定义的工作还需完成:
def distortColorMap(cm,inv = lambda x:x):
"""Inspired from 'make_colormap' in Serenity's answer.
Inputs : a pre-existing colormap cm,
the distorsion function inv
Output : the distorted colormap"""
def f(color,inv):
"""In the sequence definition, modifies the position of stops tup[0] according the transformation function.
Returns the distorted sequence."""
return map(lambda tup:(inv(tup[0]),tup[1],tup[2]),color)
# Extract sequences from cm, apply inv
C = cm.__dict__['_segmentdata']
cdict = {'red': f(C['red'] ,inv), 'green': f(C['green'],inv), 'blue': f(C['blue'] ,inv)}
name = 'new_'+cm.__dict__['name']
return colors.LinearSegmentedColormap(name, cdict)
那么,这就非常好用了:
cm = plt.get_cmap('coolwarm')
cm = distortColorMap(cm,inv = filtR) # all the job is done here
cNorm = colors.Normalize(vmin=0., vmax=1.)
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
# The color is the natural value G[i][j]['label']
val_map = {(i,j): G[i][j]['label'] for (i,j) in G.edges()}
values = [scalarMap.to_rgba(val_map[e]) for e in G.edges()]
edges = nx.draw_networkx_edges(G,edge_color=values,edge_cmap=plt.get_cmap('coolwarm'))
# Definition of the colorbar : just use the new colormap
sm = cmx.ScalarMappable(cmap=cm)
sm.set_array(values)
plt.colorbar(sm)
然后我们得到相应的颜色条:
这很酷,因为您不再需要定义整个颜色序列(现在一切都从失真函数的定义中完成),并且因为您仍然可以使用 Matplotlib 提供的精美颜色图!
编辑
有关 filtR
功能和我的动机的更多信息。
在此示例中,filtR
定义为:
exponent = 7.
filtR = lambda y: ((2*y-1)**(1./exponent)+1.)/2.
对于 exponent
的不同值,我们有一个 class 函数(具有或多或少的平滑行为)。能够从一种定义跳到另一种定义有助于确定最佳可视化效果。
实际上,对于任何e
(奇偶数),Python不喜欢在x为负数时处理x**1/e
。但这没什么大不了的,我们只需正确定义 7 根(或任何其他奇数指数)即可。
然而,这不是热点:我们只需要从 [0,1] 到 [0,1] 的数学双射。然后我们就可以选择最符合我们需要的那个。
例如,我们也可以将 filtR
函数定义为 filtR = lambda y: y**4
,因为我们希望在最低值上有更好的可读性。我们会得到:
它也应该适用于对数、分段或阶梯函数...
我想要一个通用且灵活的工具,它可以让我快速专注于某些特定领域。我不想为每个可视化测试手动创建带有停止点和颜色值的序列。
如果需要,我还希望能够将这项工作重新用于其他项目。
基本上您根本不想更改颜色图。 Instaed 你想创建你的自定义规范化。为此,您可以子类化 matplotlib.colors.Normalize
并让它 return 自定义函数的值。该函数需要将 vmin
和 vmax
之间的值作为输入,并将 return 范围内的值作为输入 [0,1].
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors as mcolors
class MyNormalize(mcolors.Normalize):
def __call__(self, value, clip=None):
# function to normalize any input between vmin and vmax linearly to [0,1]
n = lambda x: (x-self.vmin)/(self.vmax-self.vmin)
# nonlinear function between [0,1] and [0,1]
f = lambda x,a: (2*x)**a*(2*x<1)/2. +(2-(2*(1-1*x))**a)*(2*x>=1)/2.
return np.ma.masked_array(f(n(value),0.5))
fig, (ax,ax2) = plt.subplots(ncols=2)
x = np.linspace(-0.3,1.2, num=101)
X = (np.sort(np.random.rand(100))*1.5-0.3)
norm= MyNormalize(vmin=-0.3, vmax=1.2)
ax.plot(x,norm(x))
im = ax2.imshow(X[::-1,np.newaxis], norm=norm, cmap="coolwarm", aspect="auto")
fig.colorbar(im)
plt.show()
所需颜色条的图像更像是一个部分线性函数,就像下面使用的蜜蜂一样。
class MyNormalize2(mcolors.Normalize):
def __call__(self, value, clip=None):
n = lambda x: self.vmin+(self.vmax-self.vmin)*x
x, y = [self.vmin, n(0.2), n(0.8), self.vmax], [0, 0.48,0.52, 1]
return np.ma.masked_array(np.interp(value, x, y))