Matplotlibs discrete colorbar is missing one color define in my color map, and also used in the plot.
In my example code I have seven colors, but the colorbar only shows six colors, though the code for creating the colormap and colorbar seems identical to examples I found on the internet. The color red witht eh label "180" is missing. Even if I changed the boundaries and ticks either the beige-ish color or the light-blue color is extended in the colorbar.
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
import pandas as pd
# 4 marker
# 7 color
n=100
c = np.random.randint(1,8,size=n)
m = np.random.randint(1,5,size=n)
x = np.random.uniform(size=n)
y = np.random.uniform(size=n)
d_data = {'P':x, 'f':y, 'node':c, 'arch':m}
df = pd.DataFrame(d_data)
# Creating a unique list of elements
l_arch = df.arch.unique()
l_node = df.node.unique()
# Sorting is needd for good colormap
l_arch.sort()
l_node.sort()
# Creating a markers dictionary
zti_markers = ["v","^","s","o","x","+","D"]
d_marker = dict(zip(l_arch,zti_markers[:len(l_arch)] ))
# Creating a colormap and a color dictionary; A little cheat here: I know how
many different colors I need.
color_list = ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f']
cmap = matplotlib.colors.ListedColormap(color_list)
norm = matplotlib.colors.BoundaryNorm(l_node, cmap.N)
d_color = dict(zip(l_node, color_list))
fig, ax = plt.subplots()
df['color'] = df['node'].apply(lambda x: d_color[x])
df['marker'] = df['arch'].apply(lambda x: d_marker[x])
for idx, row in df.iterrows():
ax.scatter(row['P'], row['f'], color=row['color'], marker=row['marker'])
cax, _ = matplotlib.colorbar.make_axes(ax)
cb = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm,
spacing='uniform', orientation='vertical', extend='neither') #, ticks=l_node,
boundaries=l_node)
# cb = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm,
spacing='uniform', orientation='vertical', extend='neither', ticks=l_node, boundaries=l_node)
# cb = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm,
spacing='uniform', orientation='vertical', extend='neither', boundaries=[i-0.5 for i in l_node])
cb.set_ticklabels(['22','38','45','65','90','130','180'])
cb.set_ticks([0.5,1.5,2.5,3.5,4.5,5.5,6.5],update_ticks=True)
# cb.update_ticks()
cb.set_label('colorbar', rotation=90)
print(plt.gci()) # --> None
# gci(): Get the current colorable artist. Specifically, returns the current ScalarMappable instance (image or patch collection), or None if no images or patch collections have been defined.
plt.show()
How to fix the colorbar to include the missing red color as well?
The BoundaryNorm
, as the name suggests, defines the boundaries for the colormapping. You need to have one more boundary than colors. For example if you want to have all values between 20 and 50 mapped to the first color of the colormap, and all values between 50 and 60 to the second color of the colormap, you would need BoundaryNorm([20,50,60], 2)
.
In your case you do not actually perform any mapping, so all you need to do is to make sure the number of boundaries is one more than the number of colors.
norm = matplotlib.colors.BoundaryNorm(np.arange(len(l_node)+1), cmap.N)
If instead you wanted to actually use the mapping somewhere, you could define
norm = matplotlib.colors.BoundaryNorm(np.arange(len(l_node)+1)-0.5, cmap.N)
and use it in
ax.scatter(..., color=cmap(norm(row['node'])), )
I will provide the full code for the latter here, where I also simplified some stuff,
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
import pandas as pd
# 4 marker
# 7 color
n=100
c = np.random.randint(1,8,size=n)
m = np.random.randint(1,5,size=n)
x = np.random.uniform(size=n)
y = np.random.uniform(size=n)
d_data = {'P':x, 'f':y, 'node':c, 'arch':m}
df = pd.DataFrame(d_data)
# Creating a unique list of elements
l_arch = df.arch.unique()
l_node = df.node.unique()
# Sorting is needd for good colormap
l_arch.sort()
l_node.sort()
# Creating a markers dictionary
zti_markers = ["v","^","s","o","x","+","D"]
d_marker = dict(zip(l_arch,zti_markers[:len(l_arch)] ))
# Creating a colormap and a color dictionary; A little cheat here: I know how
#many different colors I need.
color_list = ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f']
cmap = matplotlib.colors.ListedColormap(color_list)
norm = matplotlib.colors.BoundaryNorm(np.arange(len(l_node)+1)-0.5, cmap.N)
d_color = dict(zip(l_node, color_list))
fig, ax = plt.subplots()
df['marker'] = df['arch'].apply(lambda x: d_marker[x])
for idx, row in df.iterrows():
ax.scatter(row['P'], row['f'], color=cmap(norm(row['node'])), marker=row['marker'])
sm = matplotlib.cm.ScalarMappable(cmap=cmap, norm=norm)
cb = fig.colorbar(sm, spacing='uniform', extend='neither')
cb.set_ticklabels(['22','38','45','65','90','130','180'])
cb.set_ticks(np.arange(len(l_node)), update_ticks=True)
cb.set_label('colorbar', rotation=90)
plt.show()
The above assumes that the "nodes" are subsequent integers starting at 0. If that is not the case, it's a bit more complicated to define the boundaries, e.g. taking the middle between unique values,
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
import pandas as pd
# 4 marker
# 7 color
n=100
c = np.random.choice([5,8,19,23,44,61,87], size=n)
m = np.random.randint(1,5,size=n)
x = np.random.uniform(size=n)
y = np.random.uniform(size=n)
d_data = {'P':x, 'f':y, 'node':c, 'arch':m}
df = pd.DataFrame(d_data)
# Creating a unique list of elements
l_arch = df.arch.unique()
l_node = df.node.unique()
# Sorting is needd for good colormap
l_arch.sort()
l_node.sort()
# Creating a markers dictionary
zti_markers = ["v","^","s","o","x","+","D"]
d_marker = dict(zip(l_arch,zti_markers[:len(l_arch)] ))
# Creating a colormap and a color dictionary; A little cheat here: I know how
#many different colors I need.
color_list = ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f']
cmap = matplotlib.colors.ListedColormap(color_list)
bounds = np.concatenate(([l_node[0]-1], l_node[:-1] + np.diff(l_node)/2,[l_node[-1]+1] ))
norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N)
d_color = dict(zip(l_node, color_list))
fig, ax = plt.subplots()
df['marker'] = df['arch'].apply(lambda x: d_marker[x])
for idx, row in df.iterrows():
ax.scatter(row['P'], row['f'], color=cmap(norm(row['node'])), marker=row['marker'])
sm = matplotlib.cm.ScalarMappable(cmap=cmap, norm=norm)
cb = fig.colorbar(sm, spacing='uniform', extend='neither')
cb.set_ticklabels(['22','38','45','65','90','130','180'])
cb.set_ticks(bounds[:-1]+np.diff(bounds)/2, update_ticks=True)
cb.set_label('colorbar', rotation=90)
plt.show()