问题
I want to plot some data with a logarithmic color code where a decade limit is indicated by a white/black interface. Grey levels are used to show some subdivisions of a decade. My problem is that there are two white neighboring regions in each decade even though the color map has the right number of entries (at least I think). Could someone help please?
In the meantime I made some tests and I found that it's the second color of the repeating pattern that is not used (the gray(0.25)), but I still have no idea why.
Here is the short version of the code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
# generate data
x = y = np.linspace(-3, 3, 200)
im = 1800*np.exp(-(np.outer(x,x) + y**2))
im = im / im.max() # normalize
# set logarithic levels (with small steps)
levS = np.array([1e-3,2e-3,4e-3,6e-3,8e-3,
1e-2,2e-2,4e-2,6e-2,8e-2,
0.1,0.2,0.4,0.6,0.8,
1])
# (5 intervals in one decade )
# avoid white patches by filling up to lowest level
im[ im < levS.min() ] = levS.min()
# make a list of 5 colors to create a colormap from
mapColS = [plt.cm.gray(0),plt.cm.gray(0.25),plt.cm.gray(0.50),
plt.cm.gray(0.7),plt.cm.gray(0.99)]
# repeat 3 times for the three decades
mapColS = mapColS + mapColS + mapColS
MyCmap=colors.ListedColormap(mapColS) # make color map
fig13g = plt.figure(1000) #create figure
ax13g = fig13g.add_subplot(111)
# plot lines
cax = plt.contour(im, levS, linewidths = 0.5,
norm=colors.LogNorm(), colors = 'k')
# fill with colors
cax = plt.contourf(im, levS, norm=colors.LogNorm(),
cmap=MyCmap) # plt.cm.jet OR MyCmap
# show log color bar
cbar = fig13g.colorbar(cax, orientation='vertical',
spacing='regular',ticks= levS)
Here are the results:
For comparisson, using 'jet' there is no problem:
回答1:
The problem is that different values end up producing the same color. This is due to the non-linear norm in use.
For a linear norm, the colors for the layers of the contourf plot would be taken at the arithmetic mean between the levels. While this may also cause problems when comparing images and contour plots (as shown in How does pyplot.contourf choose colors from a colormap?), it would still leed to N unique colors being used for N+1 levels.
For a LogNorm, the geometric mean is used instead of the arithmetic mean.
In the following the values used to produce the colors from the colormap are shown. As can be seen several end up in the same bin.
Increasing the number of colors will allow each value to be in its own colorbin.
This is in principle exactly why the use of the 'jet' colormap works fine, because you have 256 different colors.
Hence a possible solution is to use more colors for the colormap creation,
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
# generate data
x = y = np.linspace(-3, 3, 200)
im = 1800*np.exp(-(np.outer(x,x) + y**2))
im = im / im.max() # normalize
# set logarithic levels (with small steps)
levS = np.array([1e-3,2e-3,4e-3,6e-3,8e-3,
1e-2,2e-2,4e-2,6e-2,8e-2,
0.1,0.2,0.4,0.6,0.8,
1])
# (5 intervals in one decade )
# avoid white patches by filling up to lowest level
im[ im < levS.min() ] = levS.min()
# make a list of N colors to create a colormap from
N = 20
mapColS = list(plt.cm.gray(np.linspace(0,1,N)))
# repeat 3 times for the three decades
mapColR = mapColS + mapColS + mapColS
MyCmap=colors.ListedColormap(mapColR) # make color map
fig13g = plt.figure(1000) #create figure
ax13g = fig13g.add_subplot(111)
# plot lines
c = plt.contour(im, levS, linewidths = 0.5,
norm=colors.LogNorm(), colors = 'k')
# fill with colors
cf = plt.contourf(im, levS, norm=colors.LogNorm(),
cmap=MyCmap) # plt.cm.jet OR MyCmap
cbar = fig13g.colorbar(cf, orientation='vertical',
spacing='regular',ticks= levS)
plt.show()
The drawback of this is that you loose dynamic range because the lowest color is not black but dark grey.
A different option would hence be to calculate those layer values and create a colormap with the respective colors at exactly those positions.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
# generate data
x = y = np.linspace(-3, 3, 200)
im = 1800*np.exp(-(np.outer(x,x) + y**2))
im = im / im.max() # normalize
# set logarithic levels (with small steps)
levS = np.array([1e-3,2e-3,4e-3,6e-3,8e-3,
1e-2,2e-2,4e-2,6e-2,8e-2,
0.1,0.2,0.4,0.6,0.8,
1])
# (5 intervals in one decade )
# avoid white patches by filling up to lowest level
im[ im < levS.min() ] = levS.min()
# make a list of N colors to create a colormap from
N = 5
mapColS = list(plt.cm.gray(np.linspace(0,1,N)))
# repeat 3 times for the three decades
mapColR = mapColS + mapColS + mapColS
#calculate layer values for lognorm
layers = np.sqrt(levS[:-1]) * np.sqrt(levS[1:])
norm = colors.LogNorm(levS.min(), levS.max())
#add outmost values and colors
lvals = np.concatenate(([0.], norm(layers), [1.]))
cvals = [mapColR[0]] + mapColR + [mapColR[-1]]
# make the colormap from values and colors
MyCmap=colors.LinearSegmentedColormap.from_list("", list(zip(lvals,cvals)))
fig13g = plt.figure(1000) #create figure
ax13g = fig13g.add_subplot(111)
# plot lines
c = plt.contour(im, levS, linewidths = 0.5,
norm=norm, colors = 'k')
# fill with colors
cf = plt.contourf(im, levS, norm=norm,
cmap=MyCmap)
cbar = fig13g.colorbar(cf, orientation='vertical',
spacing='regular',ticks= levS)
plt.show()
回答2:
The problem is that you were repeating the same color levels mapColS
3 times by using mapColS = mapColS + mapColS + mapColS
. The straight forward solution is to create a single continuous grayscale by dividing linearly the scale between plt.cm.gray(0)
and plt.cm.gray(0.99)
into 15 equal levels as
mapColS = [plt.cm.gray(i) for i in np.linspace(0, 0.99, 15)]
MyCmap=colors.ListedColormap(mapColS) # make color map
Output
来源:https://stackoverflow.com/questions/52498858/pyplot-contourf-with-custom-colormap-repeats-color-instead-of-changing