constrained_layout=True moves one column deeper - matplotlib

I used constrained_layout= True multiple times and everything worked good... now I added a second Colorbar an i get somthing like that:
How I can fix this? I want the same hight and space between the bars and plots.
colorbar_shrink = 0.6
pad=-0.15
fig, axarr = plt.subplots(nrows=2, ncols=2, figsize=(15, 10), constrained_layout=True,
sharey=True, subplot_kw={'projection': crs})
# fig.tight_layout()
# fig.subplots_adjust(wspace=0.01)
# fig.set_constrained_layout_pads(w_pad=0.02, h_pad=0.02, hspace=-0.2,
# wspace=0.2)
fig.suptitle('Bla', y=1.01)
# fig.set_constrained_layout_pads(w_pad=0, h_pad=0.05,hspace=0.1, wspace=0.2)
axlist = axarr.flatten()
for ax in axlist:
plot_background(ax)
print('First .')
levels = np.linspace(0, 2, 10)
cf1 = axlist[0].contourf(lon_2d, lat_2d, aod_dust_550nm, cmap='Oranges',
transform=ccrs.PlateCarree(), zorder=0, levels=levels, extend='both')
cf2 = axlist[0].contourf(lon_2d, lat_2d, tq, cmap='Blues', vmin=0.0001, vmax=10 ,
levels=tq_lev, norm=matplotlib.colors.LogNorm(),
transform=ccrs.PlateCarree(), zorder=0, extend='max')
cb1 = fig.colorbar(cf1, ax=axlist[0], orientation='horizontal', format='%.1f',
shrink=colorbar_shrink, pad=pad)
cb2 = fig.colorbar(cf2, ax=axlist[0], orientation='vertical', #format='%.4f',
shrink=colorbar_shrink)
cb1.set_label('Dust optical depth #550nm',labelpad=-45, fontsize=12)
cb2.ax.set_ylabel('TQ in kg m$^{-2}$')
cb2.formatter = LogFormatter()
cb2.set_ticks([1e-4,1e-3, 1e-2, 1e-1, 1e0, 10])
cb2.update_ticks()
print('Second..')
# Upper right plot - Wind and Windspeed
levels = np.linspace(0.0, 50.0, 50)
cf2 = axlist[1].contourf(lon_2d, lat_2d, windspeed_900, levels=levels,
cmap='jet', transform=ccrs.PlateCarree() ,zorder=0, extend='max')
#Every n-th quiver
wind_slice = (slice(None, None, 15), slice(None, None, 15))
c2 = axlist[1].quiver(lon_2d[wind_slice], lat_2d[wind_slice] ,
u_300.values[wind_slice] , v_300.values[wind_slice],
color='black', transform=ccrs.PlateCarree())
cb2 = fig.colorbar(cf2, ax=axlist[1], orientation='horizontal',format='%i',
shrink=colorbar_shrink)#, pad=paddy)
cb2.set_label('Wind speed in m s-1 #900hPa',labelpad=-45, fontsize=12)
plt.quiverkey(c2, 0.99, -0.16, 10,'velocity (10 m s$^{-1}$)',
labelpos='W',transform=ccrs.PlateCarree(), color='r')
# plt.show()
THanks a lot.

Related

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 label each wedge of pie chart in this grid?

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()

Matplotlib subplots how to align colorbars with other legends, or how to justify subplots to left

How can I add a colorbar scale to the 2nd & 3rd subplots, such that it is inline with my legends in the 1st and 4th subplots? Or, another way to say the question: how can I add a colorbar scale without changing the alignment/justification of the 2nd & 3rd subplots?
There are good examples available on setting colorbar locations (e.g., here on stackoverflow and in the matplotlib docs), but I still haven't been able to solve this.
Below is a reproducible example. The real data are more complicated, and this is part of a loop to produce many figures, so the "extra" stuff about setting axis limits and subplot aspect ratios is needed and will change with different datasets.
Using Python 3.8.
Reproducible example without colorbar
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, xytick, ar = [-40000,120000], [-30000,10000], 20000, 0.8
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlabel('x');
ax1.set_ylabel('beta');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.xaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.yaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.legend(handles=leg, bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_xlabel('x');
ax2.set_ylabel('beta');
ax2.set_aspect(aspect=ar)
## Attempt to add colorbar
#cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right', aspect=25)
#cbar.ax.set_ylabel('y')
#cbar.ax.yaxis.set_label_position('left')
#cbar_range = [min(y), max(y)]
#ticklabels = cbar.ax.get_ymajorticklabels()
#cbarticks = list(cbar.get_ticks())
#cbar.set_ticks(cbar_range + cbarticks)
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
ax3.set_xlabel('x');
ax3.set_ylabel('y');
ax3.yaxis.set_major_formatter(FormatStrFormatter('%1.2g'))
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.set_xlabel('x');
ax4.set_ylabel('y');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Clean-up, turn axis ticks back on after messing with cbar
#ax1.tick_params(axis='both', which='both', labelbottom='on')
#ax2.tick_params(axis='both', which='both', labelbottom='on')
#ax3.tick_params(axis='both', which='both', labelbottom='on')
Result when trying colorbar, note misalignment of second plot
Suggest you simplify your code and make sure it all works; for instance I have no idea what sub does.
A partial solution to your problem could be panchor=False, which is a bit of an obscure kwarg, but...
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
## Specify axes limits, tick intervals, and aspect ratio
ar = 1.2
## Global plot layout stuff
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 4), constrained_layout=True, sharex=True, sharey=True)
## First Plot
ax1.plot([-20_000, 20_000], [-20_000, 20_000] )
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_aspect(aspect=ar)
cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right',
aspect=25, panchor=False)
plt.show()
Depending on the size of the figure, this could comically place the colorbar far to the right. The problem here is the aspect ratio of your plots, which makes the actual axes more narrow than the figure. But the colorbar doesn't really know about that, and places itself on the outside of the space allocated for the axes.
If this is displeasing, then you can also specify an inset axes for the colorbar.
cbax = ax2.inset_axes([1.05, 0.2, 0.05, 0.6], transform=ax2.transAxes)
cbar = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
Using inset_axes() solves this, as suggested in the other answer, but the parameters relative to the transform were not explained in the example, but I was able to figure it out with some research.
The parameters in inset_axes are [x-corner, y-corner, width, height] and the transform is like a local reference. So, using [1,0,0.5,0.75] means: x = 100% or end of parent ax; y = 0% or bottom of parent ax; width = 50% of parent ax; and height = 75% of parent ax.
Here I wanted the colorbar to be the same height as the parent ax (ax2 and ax3), very thin, and offset a little bit to be more in line with the other legends. Using cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes) achieves this.
This code works for any aspect ratio ar.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.colors as mcolors
from operator import sub
%matplotlib inline
plt.style.use('seaborn-whitegrid')
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, ar = [-40000,120000], [-30000,10000], .5
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_aspect(aspect=ar)
cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes)
cbar2 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
cbax = ax3.inset_axes([1.1, 0, 0.03, 1], transform=ax3.transAxes)
cbar3 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Colorbars
cbar2.ax.set_ylabel('y')
cbar2.ax.yaxis.set_label_position('left')
cbar3.ax.set_ylabel('y')
cbar3.ax.yaxis.set_label_position('left')
Result with aspect ratio = 0.5 for top 2 plots
Result with aspect ratio = 2 for top 2 plots

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)

How to increase padding (whitespace) for subplots when saving as png?

I have created a 4x2 subplot and I want to save the individual plots as separate png files. I am able to do this with the following code, however the tightbbox format is too tight and it makes it tough to read the title and x,y labels. Is there a way to increase the padding (whitespace) around each individual plot when using this tightbbox layout?
nrow = combined.shape[0]
ncol = combined.shape[1]
shape_row = int((nrow + 1)/2 if (nrow % 2) != 0 else nrow/2)
fig, axes = plt.subplots(shape_row, 2, figsize=(20,45))
fig.subplots_adjust(hspace=0.5, wspace=0.4)
plt.rcParams['savefig.facecolor'] = 'white'
axes = axes.ravel()
for i in range(nrow):
axes[i].bar(range(2), combined.iloc[i,:2].values, color='blue', label=label_1)
axes[i].plot(range(2,ncol-1), combined.iloc[i,2:-1], 'o-', color='orange', markersize=10, label=label_2)
axes[i].plot([-1, 7], [combined.iloc[i,-1]]*2, linestyle='--', color='red', label=label_3)
axes[i].plot([ncol-1,ncol-1],[0, combined.iloc[i,-1]], '--', color='red')
axes[i].set_title(combined.index[i], fontsize=26, fontweight='bold', pad=15)
axes[i].set_xlabel('')
axes[i].set_ylabel("(in local currency '000s)", fontsize=18, labelpad=10)
axes[i].set_xticks(range(ncol))
axes[i].set_xticklabels(combined.columns, rotation=45)
axes[i].tick_params(labelsize=18, pad=10)
axes[i].set_xlim([-.7, nrow-.97])
axes[i].margins(x=0, y=0.2)
blue_bar= mpatches.Patch(color='blue', label='aaa')
orange_line=mlines.Line2D(range(2,ncol-1), combined.iloc[i,2:-1], color='orange', linestyle='-', marker = 'o', markersize=10, label='bbb')
red_line=mlines.Line2D([-1, 7], [combined.iloc[i,-1]]*2, color='red', linestyle='--', label='ccc')
lgd = axes[i].legend(handles=[blue_bar, orange_line, red_line],
loc='upper right', bbox_to_anchor=(1,1), fontsize=18, shadow=True, borderpad=1)
bbox = axes[i].get_tightbbox(fig.canvas.get_renderer())
fig.savefig("subplot{}.png".format(i),
bbox_inches=bbox.transformed(fig.dpi_scale_trans.inverted()))