Is there a way to label each wedge of pie chart in this grid? - matplotlib

I want to have multiple pie charts in a grid.
Each pie chart will have a different number of wedges, values, and labels.
The code below shows multiple labels in one pie chart.
Is there a way to label each wedge of pie-charts in this grid?
import matplotlib.pyplot as plt
import numpy as np
def heatmap_with_circles(data_array,row_labels,column_labels,ax=None, cmap=None, norm=None, cbar_kw={}, cbarlabel="", **kwargs):
for row_index, row in enumerate(row_labels,0):
for column_index, column in enumerate(column_labels,0):
print('row_index: %d column_index: %d' %(row_index,column_index))
if row_index==0 and column_index==0:
colors=['indianred','orange','gray']
values=[10,20,30]
else:
values=[45,20,38]
colors=['pink','violet','green']
wedges, text = plt.pie(values,labels=['0', '2', '3'],labeldistance = 0.25,colors=colors)
print('len(wedges):%d wedges: %s, text: %s' %(len(wedges), wedges, text))
radius = 0.45
[w.set_center((column_index,row_index)) for w in wedges]
[w.set_radius(radius) for w in wedges]
# We want to show all ticks...
ax.set_xticks(np.arange(data_array.shape[1]))
ax.set_yticks(np.arange(data_array.shape[0]))
fontsize=10
ax.set_xticklabels(column_labels, fontsize=fontsize)
ax.set_yticklabels(row_labels, fontsize=fontsize)
#X axis labels at top
ax.tick_params(top=True, bottom=False,labeltop=True, labelbottom=False,pad=5)
plt.setp(ax.get_xticklabels(), rotation=55, ha="left", rotation_mode="anchor")
# We want to show all ticks...
ax.set_xticks(np.arange(data_array.shape[1]+1)-.5, minor=True)
ax.set_yticks(np.arange(data_array.shape[0]+1)-.5, minor=True)
ax.grid(which="minor", color="black", linestyle='-', linewidth=2)
ax.tick_params(which="minor", bottom=False, left=False)
data_array=np.random.rand(3,4)
row_labels=['Row1', 'Row2', 'Row3']
column_labels=['Column1', 'Column2', 'Column3','Column4']
fig, ax = plt.subplots(figsize=(1.9*len(column_labels),1.2*len(row_labels)))
ax.set_aspect(1.0)
ax.set_facecolor('white')
heatmap_with_circles(data_array,row_labels,column_labels, ax=ax)
plt.tight_layout()
plt.show()
After updating heatmap_with_circles
def heatmap_with_circles(data_array,row_labels,column_labels,ax=None, cmap=None, norm=None, cbar_kw={}, cbarlabel="", **kwargs):
labels = ['x', 'y', 'z']
for row_index, row in enumerate(row_labels,0):
for column_index, column in enumerate(column_labels,0):
print('row_index: %d column_index: %d' %(row_index,column_index))
if row_index==0 and column_index==0:
colors=['indianred','orange','gray']
values=[10,20,30]
else:
values=[45,20,38]
colors=['pink','violet','green']
# wedges, texts = plt.pie(values,labels=['0', '2', '3'],labeldistance = 0.45,colors=colors)
wedges, texts = plt.pie(values,labeldistance = 0.25,colors=colors)
print('text:%s len(wedges):%d wedges: %s' %(texts, len(wedges), wedges))
radius = 0.45
[w.set_center((column_index,row_index)) for w in wedges]
[w.set_radius(radius) for w in wedges]
[text.set_position((text.get_position()[0]+column_index,text.get_position()[1]+row_index)) for text in texts]
[text.set_text(labels[text_index]) for text_index, text in enumerate(texts,0)]
I got the following image :)

You could loop through the texts of each pie, get its xy position, add column_index and row_index, and set that as new position.
Some small changes to the existing code:
ax.grid(which="minor", ..., clip_on=False) to make sure the thick lines are shown completely, also near the border
ax.set_xlim(xmin=-0.5) to set the limits
import matplotlib.pyplot as plt
import numpy as np
def heatmap_with_circles(data_array, row_labels, column_labels, ax=None):
ax = ax or plt.gca()
for row_index, row in enumerate(row_labels, 0):
for column_index, column in enumerate(column_labels, 0):
colors = np.random.choice(['indianred', 'orange', 'gray', 'pink', 'violet', 'green'], 3, replace=False)
values = np.random.randint(10, 41, 3)
wedges, text = plt.pie(values, labels=['1', '2', '3'], labeldistance=0.25, colors=colors)
radius = 0.45
for w in wedges:
w.set_center((column_index, row_index))
w.set_radius(radius)
w.set_edgecolor('white')
# w.set_linewidth(1)
for t in text:
x, y = t.get_position()
t.set_position((x + column_index, y + row_index))
# We want to show all ticks...
ax.set_xticks(np.arange(data_array.shape[1]))
ax.set_yticks(np.arange(data_array.shape[0]))
fontsize = 10
ax.set_xticklabels(column_labels, fontsize=fontsize)
ax.set_yticklabels(row_labels, fontsize=fontsize)
# X axis labels at top
ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False, pad=5)
plt.setp(ax.get_xticklabels(), rotation=55, ha="left", rotation_mode="anchor")
# We want to show all minor ticks...
ax.set_xticks(np.arange(data_array.shape[1] + 1) - .5, minor=True)
ax.set_yticks(np.arange(data_array.shape[0] + 1) - .5, minor=True)
ax.set_xlim(xmin=-.5)
ax.set_ylim(ymin=-.5)
ax.grid(which="minor", color="black", linestyle='-', linewidth=2, clip_on=False)
ax.tick_params(axis="both", which="both", length=0) # hide tick marks
data_array = np.random.rand(3, 4)
row_labels = ['Row1', 'Row2', 'Row3']
column_labels = ['Column1', 'Column2', 'Column3', 'Column4']
fig, ax = plt.subplots(figsize=(1.9 * len(column_labels), 1.2 * len(row_labels)))
ax.set_aspect(1.0)
ax.set_facecolor('white')
heatmap_with_circles(data_array, row_labels, column_labels, ax=ax)
plt.tight_layout()
plt.show()

Related

Picker Event to display legend labels in matplotlib

I want the picker event to simply display the legend label when I click on any of the points on my scatter plot. This is what I have and looks like:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# x y data and legend labels
x = np.random.uniform(0, 100, 50)
y = np.random.uniform(0, 100, 50)
ID = np.random.randint(0,25,50)
# define the event
def onpick(event):
ind = event.ind
print('x:', x[ind], 'y:', y[ind])
# create the plot
fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c = ID, picker=True)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend(*scatter.legend_elements(num=list(np.unique(ID))),
loc="center left",
title='ID',
bbox_to_anchor=(1, 0.5),
ncol=2
)
ax.ticklabel_format(useOffset=False)
ax.tick_params(axis = 'x',labelrotation = 45)
plt.tight_layout()
# call the event
fig.canvas.mpl_connect('pick_event', onpick)
The scatter plot:
The current output on click:
I want it to print something like:
x: [76.25650514] y: [59.85198124] ID: 11 # the corresponding legend label
I have been searching through the web and couldn't find much I can duplicate from.
Generally, the way you would get the label of the point you clicked on would be print(event.artist.get_label()) but with your custom legends labels, the only thing that prints is _child0. However, due to your custom labels, you can use your variable ID just like how you are using your x and y variables e.g. print('id:', ID[ind]).
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# x y data and legend labels
x = np.random.uniform(0, 100, 50)
y = np.random.uniform(0, 100, 50)
ID = np.random.randint(0,25,50)
# define the event
def onpick(event):
ind = event.ind
print(event.artist.get_label()) # How you normally get the legend label
print('id:', ID[ind]) # How you can get your custom legend label
print('x:', x[ind], 'y:', y[ind])
# create the plot
fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c = ID, picker=True)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend(*scatter.legend_elements(num=list(np.unique(ID))),
loc="center left",
title='ID',
bbox_to_anchor=(1, 0.5),
ncol=2
)
ax.ticklabel_format(useOffset=False)
ax.tick_params(axis = 'x',labelrotation = 45)
plt.tight_layout()
# call the event
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
Clicking on the yellow most point gives:
_child0
id: [24]
x: [84.73899472] y: [3.07532246]
Clicking on a very purple point gives:
_child0
id: [2]
x: [99.88397652] y: [98.89144833]

Matplotlib gives ValueError: Image size pixels is too large when I add one more subplot

I have been struggling today almost all day with Image size pixels is too large error when I try to combine multiple plots into one figure using matplotlib add_subplot function.
I can plot each plot individually but when I combine them I face with this problem.
Here is the simplified code to replicate this Value error.
from matplotlib import pyplot as plt
import numpy as np
import os
def plot_in_subplots():
fig= plt.figure(figsize=(15, 10))
axis1=fig.add_subplot(311)
# Uncomment line below to replicate ValueError: Image size of 3719x61904113 pixels is too large. It must be less than 2^16 in each direction.
# axis2=fig.add_subplot(312)
plot_bar_plot_in_given_axis(axis1)
# plot_in_given_axis(axis2)
figFile = os.path.join('/Users/burcakotlu/Desktop/Test_subplot.png')
fig.savefig(figFile, dpi=100, bbox_inches="tight")
plt.cla()
plt.close(fig)
def plot_in_given_axis(ax):
xticklabels_list = ['a','b','c','d','e','f'] * 6
rows=['row1']
ax.set_xlim([0, 36])
ax.set_xticklabels([])
ax.tick_params(axis='x', which='minor', length=0, labelsize=35)
ax.set_xticks(np.arange(0, 36, 1))
ax.set_xticks(np.arange(0, 36, 1) + 0.5, minor=True)
ax.set_xticklabels(xticklabels_list, minor=True)
ax.xaxis.set_label_position('top')
ax.xaxis.set_ticks_position('top')
plt.tick_params(
axis='x', # changes apply to the x-axis
which='major', # both major and minor ticks are affected
bottom=False, # ticks along the bottom edge are off
top=False) # labels along the bottom edge are off
ax.set_ylim([0, len(rows)])
ax.set_yticklabels([])
ax.tick_params(axis='y', which='minor', length=0, labelsize=40)
ax.set_yticks(np.arange(0, len(rows), 1))
ax.set_yticks(np.arange(0, len(rows), 1) + 0.5, minor=True)
ax.set_yticklabels(rows, minor=True) # fontsize
plt.tick_params(
axis='y', # changes apply to the x-axis
which='major', # both major and minor ticks are affected
left=False) # labels along the bottom edge are off
ax.grid(which='major', color='black', zorder=3)
def plot_bar_plot_in_given_axis(ax):
x_axis_labels = ['a', 'b', 'c', 'd', 'e', 'f']
real_values1 = [266655.0, 0.0, 14072.0, 4137.0, 6752.5, 0.0]
real_values2 = [273342.5, 0.0, 12598.5, 4240.0, 7425.5, 0.0]
unreal_values1 = [326188.16, 0.0, 15828.42, 4666.825000000001, 8109.87, 0.0]
unreal_values2 = [344462.07, 0.0, 16368.664999999999, 5180.2, 8721.64, 0.0]
q_values = [2.5309603790195403e-28, 1.0, 1.8194829804783173e-33, 0.003603381046779825, 1.0, 1.0]
name1 = 'X'
name2 = 'Y'
color1 = 'r'
color2 = 'b'
width = 0.1
ind = np.arange(len(x_axis_labels))
legend=None
rects3=None
rects4=None
rects1 = ax.bar(ind, real_values1, width=width, edgecolor='black', color=color1)
rects2 = ax.bar(ind + width, real_values2, width=width, edgecolor='black', color=color2)
if ((unreal_values1 is not None) and unreal_values1):
rects3 = ax.bar(ind+ 2*width, unreal_values1, width=width, edgecolor='black', color=color1, hatch = 'X')
if ((unreal_values2 is not None) and unreal_values2):
rects4 = ax.bar(ind +3*width, unreal_values2, width=width, edgecolor='black', color=color2, hatch = 'X')
ax.tick_params(axis='x', labelsize=35)
ax.tick_params(axis='y', labelsize=35)
locs, labels = plt.yticks()
ax.set_ylim(0, locs[-1] + 5000)
ax.set_title('%s vs. %s' %(name1,name2), fontsize=20,fontweight='bold')
ax.set_xticklabels(x_axis_labels, fontsize=35)
plt.ylabel('Y axis label', fontsize=35, fontweight='normal')
ax.set_xticks(ind + (3 * width) / 2)
realStrand1Name = 'Real %s' % (name1)
realStrand2Name = 'Real %s' % (name2)
simulationsStrand1Name = 'Unreal %s' % (name1)
simulationsStrand2Name = 'Unreal %s' % (name2)
if ((rects1 is not None) and (rects2 is not None) and (rects3 is not None) and (rects4 is not None)):
if ((len(rects1) > 0) and (len(rects2) > 0) and (len(rects3) > 0) and (len(rects4) > 0)):
legend = ax.legend((rects1[0], rects2[0], rects3[0], rects4[0]),
(realStrand1Name, realStrand2Name, simulationsStrand1Name, simulationsStrand2Name),prop={'size': 25}, ncol=1, loc='best')
ax.set_facecolor('white')
ax.spines["bottom"].set_color('black')
ax.spines["left"].set_color('black')
ax.spines["top"].set_color('black')
ax.spines["right"].set_color('black')
if (legend is not None):
frame = legend.get_frame()
frame.set_facecolor('white')
frame.set_edgecolor('black')
if q_values is not None:
for q_value, rect1, rect2 in zip(q_values,rects1,rects2):
# Get X and Y placement of label from rect.
y_value = max(rect1.get_height(),rect2.get_height())
x_value = rect1.get_x() + rect1.get_width()
space = 3
va = 'bottom'
if y_value < 0:
space *= -1
va = 'top'
if ((q_value is not None) and (q_value)<=0.05):
plt.annotate(
'***', # Use `label` as label
(x_value, y_value), # Place label at end of the bar
xytext=(0, space), # Vertically shift label by `space`
textcoords="offset points", # Interpret `xytext` as offset in points
ha='center', # Horizontally center label
va=va,
fontsize=20) # Vertically align label differently for
plot_in_subplots()
Please uncomment this line to replicate ValueError: Image size of 3719x61904113 pixels is too large. It must be less than 2^16 in each direction.
axis2=fig.add_subplot(312)
Here are the plots I want to combine using add_subplot
I have upgraded to Matplotlib 3.4.2.
I changed plt.xxx into ax.xxx (object oriented) and using gridspec, I'm almost there. Thanks.
from matplotlib import pyplot as plt
from matplotlib import gridspec
import numpy as np
import os
def plot_in_subplots():
fig= plt.figure(figsize=(30, 10))
gs = gridspec.GridSpec(10, 4, width_ratios=[1,1,1,1], height_ratios=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
axis1 = plt.subplot(gs[0:7, 1:3])
axis2 = plt.subplot(gs[9, :])
# bottom_left_axis = plt.subplot(gs[-1,0])
# bottom_right_axis = plt.subplot(gs[-1,-1])
# axis1=fig.add_subplot(211)
# axis2=fig.add_subplot(212)
plot_bar_plot_in_given_axis(axis1)
plot_in_given_axis(axis2)
figFile = os.path.join('/Users/burcakotlu/Desktop/Test_subplot.png')
fig.savefig(figFile, dpi=100, bbox_inches="tight")
plt.cla()
plt.close(fig)
def plot_in_given_axis(ax):
xticklabels_list = ['a','b','c','d','e','f'] * 6
rows=['row1']
ax.set_xlim([0, 36])
ax.set_xticklabels([])
ax.tick_params(axis='x', which='minor', length=0, labelsize=35)
ax.set_xticks(np.arange(0, 36, 1))
ax.set_xticks(np.arange(0, 36, 1) + 0.5, minor=True)
ax.set_xticklabels(xticklabels_list, minor=True)
ax.xaxis.set_label_position('top')
ax.xaxis.set_ticks_position('top')
ax.tick_params(
axis='x', # changes apply to the x-axis
which='major', # both major and minor ticks are affected
bottom=False, # ticks along the bottom edge are off
top=False) # labels along the bottom edge are off
ax.set_ylim([0, len(rows)])
ax.set_yticklabels([])
ax.tick_params(axis='y', which='minor', length=0, labelsize=40)
ax.set_yticks(np.arange(0, len(rows), 1))
ax.set_yticks(np.arange(0, len(rows), 1) + 0.5, minor=True)
ax.set_yticklabels(rows, minor=True) # fontsize
ax.tick_params(
axis='y', # changes apply to the x-axis
which='major', # both major and minor ticks are affected
left=False) # labels along the bottom edge are off
ax.grid(which='major', color='black', zorder=3)
def plot_bar_plot_in_given_axis(ax):
x_axis_labels = ['a', 'b', 'c', 'd', 'e', 'f']
real_values1 = [266655.0, 0.0, 14072.0, 4137.0, 6752.5, 0.0]
real_values2 = [273342.5, 0.0, 12598.5, 4240.0, 7425.5, 0.0]
unreal_values1 = [326188.16, 0.0, 15828.42, 4666.825000000001, 8109.87, 0.0]
unreal_values2 = [344462.07, 0.0, 16368.664999999999, 5180.2, 8721.64, 0.0]
q_values = [2.5309603790195403e-28, 1.0, 1.8194829804783173e-33, 0.003603381046779825, 1.0, 1.0]
name1 = 'X'
name2 = 'Y'
color1 = 'r'
color2 = 'b'
width = 0.1
ind = np.arange(len(x_axis_labels))
legend=None
rects3=None
rects4=None
rects1 = ax.bar(ind, real_values1, width=width, edgecolor='black', color=color1)
rects2 = ax.bar(ind + width, real_values2, width=width, edgecolor='black', color=color2)
if ((unreal_values1 is not None) and unreal_values1):
rects3 = ax.bar(ind+ 2*width, unreal_values1, width=width, edgecolor='black', color=color1, hatch = 'X')
if ((unreal_values2 is not None) and unreal_values2):
rects4 = ax.bar(ind +3*width, unreal_values2, width=width, edgecolor='black', color=color2, hatch = 'X')
ax.tick_params(axis='x', labelsize=35)
ax.tick_params(axis='y', labelsize=35)
locs=ax.get_yticks()
ax.set_ylim(0, locs[-1] + 5000)
ax.set_title('%s vs. %s' %(name1,name2), fontsize=20,fontweight='bold')
ax.set_xticklabels(x_axis_labels, fontsize=35)
ax.set_ylabel('Y axis label', fontsize=35, fontweight='normal')
ax.set_xticks(ind + (3 * width) / 2)
realStrand1Name = 'Real %s' % (name1)
realStrand2Name = 'Real %s' % (name2)
simulationsStrand1Name = 'Unreal %s' % (name1)
simulationsStrand2Name = 'Unreal %s' % (name2)
if ((rects1 is not None) and (rects2 is not None) and (rects3 is not None) and (rects4 is not None)):
if ((len(rects1) > 0) and (len(rects2) > 0) and (len(rects3) > 0) and (len(rects4) > 0)):
legend = ax.legend((rects1[0], rects2[0], rects3[0], rects4[0]),
(realStrand1Name, realStrand2Name, simulationsStrand1Name, simulationsStrand2Name),prop={'size': 25}, ncol=1, loc='best')
ax.set_facecolor('white')
ax.spines["bottom"].set_color('black')
ax.spines["left"].set_color('black')
ax.spines["top"].set_color('black')
ax.spines["right"].set_color('black')
if (legend is not None):
frame = legend.get_frame()
frame.set_facecolor('white')
frame.set_edgecolor('black')
if q_values is not None:
for q_value, rect1, rect2 in zip(q_values,rects1,rects2):
# Get X and Y placement of label from rect.
y_value = max(rect1.get_height(),rect2.get_height())
x_value = rect1.get_x() + rect1.get_width()
space = 3
va = 'bottom'
if y_value < 0:
space *= -1
va = 'top'
if ((q_value is not None) and (q_value)<=0.05):
ax.annotate(
'***', # Use `label` as label
(x_value, y_value), # Place label at end of the bar
xytext=(0, space), # Vertically shift label by `space`
textcoords="offset points", # Interpret `xytext` as offset in points
ha='center', # Horizontally center label
va=va,
fontsize=20,
transform=ax.transAxes) # Vertically align label differently for
plot_in_subplots()

Is there a way to plot pie chart in matplotlib heatmap?

I have a heatmap with several rows and columns.
Formerly, I was plotting a circle for each (row_index,column_index) and appending this circle to a circle_list. I was adding circle_list as a collection to the axes.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import PatchCollection
def heatmap_with_circles(data_array,row_labels,column_labels,ax=None, cmap=None, norm=None, cbar_kw={}, cbarlabel="", **kwargs):
circles=[]
for row_index, row in enumerate(row_labels):
for column_index, column in enumerate(column_labels):
circles.append(plt.Circle((row_index,column_index),radius=0.4))
col = PatchCollection(circles, array=data_array.flatten(), cmap=cmap, norm=norm)
ax.add_collection(col)
# We want to show all ticks...
ax.set_xticks(np.arange(data_array.shape[1]))
ax.set_yticks(np.arange(data_array.shape[0]))
fontsize=10
ax.set_xticklabels(column_labels, fontsize=fontsize)
ax.set_yticklabels(row_labels, fontsize=fontsize)
#X axis labels at top
ax.tick_params(top=True, bottom=False,labeltop=True, labelbottom=False,pad=5)
plt.setp(ax.get_xticklabels(), rotation=55, ha="left", rotation_mode="anchor")
# We want to show all ticks...
ax.set_xticks(np.arange(data_array.shape[1]+1)-.5, minor=True)
ax.set_yticks(np.arange(data_array.shape[0]+1)-.5, minor=True)
ax.grid(which="minor", color="black", linestyle='-', linewidth=3)
ax.tick_params(which="minor", bottom=False, left=False)
data_array=np.random.rand(3,4)
row_labels=['Row1', 'Row2', 'Row3']
column_labels=['Column1', 'Column2', 'Column3','Column4']
fig, ax = plt.subplots(figsize=(1.9*len(row_labels),1.2*len(column_labels)))
ax.set_aspect(1.0)
ax.set_facecolor('white')
heatmap_with_circles(data_array,row_labels,column_labels, ax=ax)
plt.tight_layout()
plt.show()
However, now I need to plot a pie chart instead of a circle.
And pie chart does not have (row_index,column_index) parameters.
Is there a way to plot pie chart in each cell of matplotlib heatmap?
Updating the for loop in heatmap_with_circles as follows:
for row_index, row in enumerate(row_labels,0):
for column_index, column in enumerate(column_labels,0):
wedges, _ = plt.pie([20, 10, 5])
radius = 0.45
[w.set_center((column_index,row_index)) for w in wedges]
[w.set_radius(radius) for w in wedges]
results in
You can access each wedge created by plt.pie individually and then use set_radius and set_position to rescale the different wedges.
wedges, _ = plt.pie([1,2,3])
x_position, y_position = 0, 0
radius = 0.2
[w.set_center((x_position,y_position)) for w in wedges]
[w.set_radius(radius) for w in wedges]
Edit:
On your code, in the for loop
for row_index, row in enumerate(row_labels):
for column_index, column in enumerate(column_labels):
wedges, _ = plt.pie([1,2,3])
[w.set_center((row_index,column_index)) for w in wedges]
[w.set_radius(0.4) for w in wedges]

How to show ranges of values with a color assigned in the legend?

With this code i'm creating colorbar scales with the function make_colormap. Source:Create own colormap using matplotlib and plot color scale
import matplotlib.colors as mcolors
def make_colormap(seq):
"""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 mcolors.LinearSegmentedColormap('CustomMap', cdict)
c = mcolors.ColorConverter().to_rgb
rvb = make_colormap([c('grey'), c('grey'), norm(3), c('sandybrown'), c('sandybrown'),
norm(5), c('yellow'), c('yellow'), norm(10), c('navajowhite'),
c('navajowhite'), norm(15),c('lightgreen'), c('lightgreen'),norm(20),c('lime'), c('lime'),
norm(50),c('limegreen'), c('limegreen'),norm(80),c('forestgreen'), c('forestgreen'),norm(120),
c('green'), c('green'),norm(160),c('darkgreen'), c('darkgreen'),norm(200),c('teal'), c('teal'),norm(300),
c('mediumaquamarine'), c('mediumaquamarine'),norm(500),c('lightseagreen'), c('lightseagreen'),norm(700),
c('lightskyblue'), c('lightskyblue')])
So in variable rvb i'm asssing a color to ranges of values. How can i assing a color to an specific ranges of values? For example: Grey to 0-3, sandybrown to 4-5, yellow to 6-10, etc.
The map is this:
Also i want to the legend show those values assigned. For example Grey color 0-3, sandybrown 4-5, etc.
Something similar to this image (no need to be equal to the image, just need to show ranges with colors):
I also will show you part of my code when i create the map:
fig = plt.figure('map', figsize=(7,7), dpi=200)
ax = fig.add_axes([0.1, 0.12, 0.80, 0.75], projection=ccrs.PlateCarree())
plt.title('xxx')
plt.xlabel('LONGITUD')
plt.ylabel('LATITUD')
ax.outline_patch.set_linewidth(0.3)
l = NaturalEarthFeature(category='cultural', name='admin_0_countries', scale='50m', facecolor='none')
ax.add_feature(l, edgecolor='black', linewidth=0.25)
img = ax.scatter(lons, lats, s=7, c=ppvalues, cmap=rvb,norm=norm,
marker='o', transform=ccrs.PlateCarree())
handles, labels = img.legend_elements(alpha=0.2)
plt.legend(handles, labels,prop={'weight':'bold','size':10}, title='Meteorological\nStations',title_fontsize=9, scatterpoints=2);
cb = plt.colorbar(img, extend='both',
spacing='proportional', orientation='horizontal',
cax=fig.add_axes([0.12, 0.12, 0.76, 0.02]))
ax.set_extent([-90.0, -60.0, -20.0, 0.0], crs=ccrs.PlateCarree())
I don't understand the function in the question, but I have coded how to create a legend with a specified color, specified label, and specified ticks, and how to give a color bar a specified tick. Please correct the addition of colors and the tick spacing in the color bar.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.colors import LinearSegmentedColormap
list_color = ['grey','sandybrown','sandybrown','yellow',
'navajowhite','lightgreen','lime','limegreen',
'forestgreen','green','darkgreen','teal',
'mediumaquamarine','lightseagreen','lightskyblue']
list_label = ['0-3', '4-5', '6-10', '11-15',
'16-20', '21-50', '51-80', '81-120',
'121-160', '161-200','201-300','301-500',
'501-700','701-900','901-1200']
list_ticks = np.linspace(0, 1, 15)
vmin,vmax = 0, 1
cm = LinearSegmentedColormap.from_list('custom_cmap', list_color, N=len(list_color))
plt.imshow(np.linspace(0, 1, 25).reshape(5,5), cmap=cm, interpolation='nearest', vmin=vmin, vmax=vmax)
cbar = plt.colorbar( orientation='horizontal', extend='neither', ticks=list_ticks)
cbar.ax.set_xticklabels(list_label, rotation=45, fontsize=14)
all_patches = []
for h,l in zip(list_color, list_label):
patch = mpatches.Patch(color=h, label=l)
all_patches.append(patch)
plt.legend(handles=all_patches, loc='upper right', ncol=3, bbox_to_anchor=(3, 1))
plt.show()

Can ticks and ticklabels be placed outside axes limits?

I have profiles that plot outside the axes limits. That is a given. It cannot be extended as it is shared with more axes below and above that have raster data with a strict extent.
I would like to provide a scale in the form of an axis spine to the first profile (see attached code and figure).
Is there a way to place ticks and ticklabels outside the axis limit?
fig, ax = plt.subplots()
y = np.linspace(0, 10, 100)
x = 10 * np.sin(y)
x_offsets = np.linspace(0, 100, 20)
for offset in x_offsets:
if offset == 0:
color = 'tab:blue'
ax.axvline(0, color=color, ls='dotted', lw=0.5)
else:
color = 'k'
ax.plot(x + offset, y, color, clip_on=False)
ax.spines['left'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
major_ticks = np.linspace(x.min(), x.max(), 5)
minor_ticks = np.linspace(x.min(), x.max(), 9)
ax.set_xticks(major_ticks)
ax.set_xticks(minor_ticks, True)
ax.spines['top'].set_bounds(major_ticks[0], major_ticks[-1])
ax.spines['top'].set_color('tab:blue')
ax.xaxis.tick_top()
ax.tick_params('x', which='both', color='tab:blue', labelcolor='tab:blue')
ax.set_xlabel('x label', position=(0, -0.1), color='tab:blue')
ax.xaxis.set_label_position('top')
# ax.tick_params('x', which='both', bottom=False, top=False, labelbottom=False)
ax.tick_params('y', which='both', left=False, right=False, labelleft=False)
ax.axis((0, 100, 0, 11))
Ok, so there is a very easy solution to this, however, unfortunately, I cannot really explain why it works. All you need to do is to put the repositioning of the axes at the beginning and not at the end:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.axis((0, 100, 0, 11)) # just move this line here
y = np.linspace(0, 10, 100)
x = 10 * np.sin(y)
x_offsets = np.linspace(0, 100, 20)
for offset in x_offsets:
if offset == 0:
color = 'tab:blue'
ax.axvline(0, color=color, ls='dotted', lw=0.5)
else:
color = 'k'
ax.plot(x + offset, y, color, clip_on=False)
ax.spines['left'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
xticks = ax.get_xticklines()
for tick in xticks:
tick.set_clip_on(False)
major_ticks = np.linspace(x.min(), x.max(), 5)
minor_ticks = np.linspace(x.min(), x.max(), 9)
ax.set_xticks(major_ticks)
ax.set_xticks(minor_ticks, True)
ax.spines['top'].set_bounds(major_ticks[0], major_ticks[-1])
ax.spines['top'].set_color('tab:blue')
ax.xaxis.tick_top()
ax.tick_params('x', which='both', color='tab:blue', labelcolor='tab:blue')
ax.set_xlabel('x label', position=(0.12, 0), color='tab:blue')
ax.xaxis.set_label_position('top')
# ax.tick_params('x', which='both', bottom=False, top=False, labelbottom=False)
ax.tick_params('y', which='both', left=False, right=False, labelleft=False)
xticks = ax.get_xticks()
axtrans = (ax.transData + ax.transAxes.inverted()).transform
figtrans = (ax.transData + fig.transFigure.inverted()).transform
for xtick in xticks:
print(axtrans((0, xtick)), figtrans((0, xtick)))
fig.show()
What is curious is that, if we believe the transformation data printed at the end, some of the ticks(-labels) are not only located outside of the axis, but even outside of the figure, although we can clearly see that they are still inside the figure. I am not sure what to make of this, especially since the same ticks(-labels) are also outside (although at different values), when the repositioning of the axes is done at the end. It would be interesting to have someone more knowledgeble to explain what is going on.
Here is another answer, which I hope should satisfy your requirement. Collect all the relevant ticks and labels and add them to the axes (again?):
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
y = np.linspace(0, 10, 100)
x = 10 * np.sin(y)
x_offsets = np.linspace(0, 100, 20)
for offset in x_offsets:
if offset == 0:
color = 'tab:blue'
ax.axvline(0, color=color, ls='dotted', lw=0.5)
else:
color = 'k'
ax.plot(x + offset, y, color, clip_on=False)
ax.spines['left'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
major_ticks = np.linspace(x.min(), x.max(), 5)
minor_ticks = np.linspace(x.min(), x.max(), 9)
ax.set_xticks(major_ticks)
ax.set_xticks(minor_ticks, True)
ax.spines['top'].set_bounds(major_ticks[0], major_ticks[-1])
ax.spines['top'].set_color('tab:blue')
ax.xaxis.tick_top()
ax.tick_params('x', which='both', color='tab:blue', labelcolor='tab:blue')
ax.set_xlabel('x label', position=(0, -0.1), color='tab:blue')
ax.xaxis.set_label_position('top')
ax.tick_params('y', which='both', left=False, right=False, labelleft=False)
ax.axis((0, 100, 0, 11))
ticks = ax.get_xticklines()
mticks = ax.get_xaxis().get_minor_ticks()
labels = ax.get_xticklabels()
for artist in [*ticks, *mticks, *labels]:
if artist.get_visible():
print(artist.axes)
ax.add_artist(artist)
artist.set_clip_on(False)
fig.show()
I find it very curious that:
there are more major xticks than there should be, and that the exessive ones aren't visible
the ticks and labels outside the axes that are obviously not visible, since they are not drawn, are alledgedly visible according to the artists.
except for the minor ticks, none of the artists are assigned to the axes, although half of them can clearly be seen to be part of the axes
even though all the minor ticks are supposed to be visible and belong to the axes, you still need to add them to the axes again or they won't show up
Thus, I cannot think of a way of how to only add the artists that are truely not visible but are actually supposed to be, other than to look at their x-axis position.
The solution was to use a blended transform to add an individual axes for the left most (or any) profile:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import blended_transform_factory
# make some sample data
dx = dy = 1
y = np.arange(80, 0 - dy, -dy)
x = np.arange(0, 100 + dx, dx)
x_offsets = np.linspace(0, 100, 11)
xx, yy = np.meshgrid(0.05 * (x + 10), 0.1 * (y - 40))
data1 = np.exp(-xx**2 - yy**2) - np.exp(-(xx - 1)**2 - (yy - 1)**2)
xx, yy = np.meshgrid(0.05 * (x - 90), 0.1 * (y - 40))
data2 = np.exp(-xx**2 - yy**2) - np.exp(-(xx - 1)**2 - (yy - 1)**2)
data = data1 + data2
data += np.random.rand(data.shape[0], data.shape[1]) * 0.5 * data
extent = (x[0] - 0.5 * dx, x[-1] + 0.5 * dx, y[-1] - 0.5 * dy, y[0] + 0.5 * dy)
# set up the plot
fig, ax = plt.subplots(
2, 2, sharey=True, figsize=(8, 4),
gridspec_kw=dict(width_ratios=(0.2, 1), wspace=0.1)
)
axTL = ax[0, 0]
axTR = ax[0, 1]
axBL = ax[1, 0]
axBR = ax[1, 1]
trans = blended_transform_factory(axTR.transData, axTR.transAxes)
data_abs_max = np.abs(data).max()
im = axBR.imshow(data, 'RdBu_r', vmin=-data_abs_max, vmax=data_abs_max,
extent=extent, aspect='auto', interpolation='bilinear')
axBR.axis(extent)
axBL.plot(data.sum(axis=1), y, 'k')
scale = 8
for offset in x_offsets:
profile = data[:, int(offset / dx)]
profile = scale * profile
xmin, xmax = profile.min(), profile.max()
if offset == 0:
bounds = (offset + xmin, 0, xmax - xmin, 1)
inset_ax = axTR.inset_axes(bounds, transform=trans)
inset_ax.set_ylim(axTR.get_ylim())
inset_ax.set_xlim(xmin, xmax)
color = 'tab:blue'
inset_ax.axvline(0, color=color, ls='dotted', lw=0.5)
inset_ax.plot(profile, y, color, clip_on=False, zorder=1)
inset_ax.set_facecolor('none')
inset_ax.spines['left'].set_visible(False)
inset_ax.spines['bottom'].set_visible(False)
inset_ax.spines['right'].set_visible(False)
inset_ax.spines['top'].set_color('tab:blue')
inset_ax.tick_params(
'both', which='both',
top=True, left=False, right=False, bottom=False,
labeltop=True, labelleft=False,
color='tab:blue', labelcolor='tab:blue'
)
inset_ax.set_xlabel('x label', color='tab:blue')
inset_ax.xaxis.set_label_position('top')
inset_ax.xaxis.tick_top()
else:
color = 'k'
axTR.plot(profile + offset, y, color, clip_on=False, zorder=0)
# remove unwanted spines and ticks
axTR.axis('off')
axTL.spines['top'].set_visible(False)
axTL.spines['right'].set_visible(False)
axTL.spines['bottom'].set_visible(False)
axTL.tick_params('both', which='both', top=False, right=False, bottom=False,
labelbottom=False)
axBR.tick_params('both', which='both', labelleft=False)
axTR.axis(extent)