Can ticks and ticklabels be placed outside axes limits? - matplotlib

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)

Related

create a cartesian 2D plane coordinate system using plotly

This creates the graph that I'm talking about using matplotlib:
import matplotlib.pyplot as plt
import numpy as np
xmin, xmax, ymin, ymax = -9, 9, -9, 9
fig, ax = plt.subplots(figsize=(20, 20))
ax.set(xlim=(xmin - 1, xmax + 1), ylim=(ymin - 1, ymax + 1), aspect='equal')
ax.spines['bottom'].set(position="zero", linewidth=2.5)
ax.spines['left'].set(position="zero", linewidth=2.5)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.text(10.15, 0, "x", fontdict=font, va="center")
ax.text(0, 10.15, "y", fontdict=font, ha="center")
x_ticks = np.arange(xmin, xmax)
y_ticks = np.arange(ymin, ymax)
ax.set_xticks(x_ticks[x_ticks != x_ticks])
ax.set_yticks(y_ticks[y_ticks != y_ticks])
ax.set_xticks(np.arange(xmin, xmax+1), minor=True)
ax.set_yticks(np.arange(ymin, ymax+1), minor=True)
ax.grid(which='both', color='grey', linewidth=1, linestyle='-', alpha=0.25)
plt.show()
Output: 2d cartesian plane as below
Is it possible to get a similar result with plotly?
This is how it can be done in Plotly:
import plotly.graph_objects as go
axis_range = [-9,9]
fig = go.Figure()
fig.update_xaxes(range=axis_range,title = 'y', tickmode = 'linear',
showticklabels = False, side='top',gridcolor="rgb(224,224,224)")
fig.update_yaxes(range=axis_range,title = 'x', tickmode = 'linear',
showticklabels = False, side='right', gridcolor="rgb(224,224,224)")
fig.add_vline(x=0, line_width=3)
fig.add_hline(y=0, line_width=3)
fig.update_layout(plot_bgcolor='rgb(255,255,255)', height=800, width=800)
fig.show()
The only drawback here is the label of x-axis cannot be rotated in Plotly as documented here.

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

How can I have a colormap legend for different circle labels

I am drawing different circles with Matplotlib. Each circle has a label, and each label has a colour. What can I do to have a colourmap legend for these different labels?
I have tried a lot of solutions online, including the most naive one by just adding plt.colorbar(), which I will get the error
RuntimeError('No mappable was found to use for colorbar')
Here is my complete code. It's a little bit long. Please note that the key part just starts from if labels is None:. I just include everything for completeness.
def plot_gaussian_circles(loc_list, scale_list, save_path=None, sigma_coe=3, num_to_plot=300, labels=None):
mu_x_max = -float('inf')
mu_y_max = -float('inf')
mu_x_min = float('inf')
mu_y_min = float('inf')
color_idx = 0
rvs = []
lim_loc_list = loc_list[:num_to_plot]
lim_scale_list = scale_list[:num_to_plot]
for a_mu_, a_sigma_ in zip(lim_loc_list, lim_scale_list):
a_mu = a_mu_.squeeze()
a_sigma_ = a_sigma_.squeeze()
if not type(a_sigma_) is np.ndarray:
a_sigma_ = a_sigma_.numpy()
radius = sigma_coe * np.max(a_sigma_)
a_mu_x = a_mu[0]
a_mu_y = a_mu[1]
if (a_mu_x + radius) >= mu_x_max:
mu_x_max = a_mu_x + radius
if (a_mu_x - radius) <= mu_x_min:
mu_x_min = a_mu_x - radius
if (a_mu_y + radius) >= mu_y_max:
mu_y_max = a_mu_y + radius
if (a_mu_y - radius) <= mu_y_min:
mu_y_min = a_mu_y - radius
if labels is None:
rv = plt.Circle(a_mu, radius, fill=False, clip_on=False)
else:
colors = cm.rainbow(np.linspace(0, 1, len(set(labels))))
rv = plt.Circle(a_mu, radius, color=colors[labels[color_idx]], fill=False, clip_on=False)
rvs.append(rv)
color_idx = (color_idx + 1)
fig, ax = plt.subplots()
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
axes = plt.gca()
axes.set_xlim([mu_x_min - 1, mu_x_max + 1])
axes.set_ylim([mu_y_min - 1, mu_y_max + 1])
for rv in rvs:
ax.add_artist(rv)
if not(labels is None):
# plt.legend(colors, list(range(len(set(labels)))))
plt.colorbar()
if save_path is None:
plt.plot()
plt.show()
# plt.savefig('plotcircles_test.png')
else:
plt.savefig(save_path, dpi=200)
The image here is currently what I am getting, while I wish to have a legend of the colormap.
I found doing this will have the colour for the circles. Thanks ImportanceOfBeingErnest's comment for suggesting PatchCollection.
p = PatchCollection(rvs, cmap=cm.jet, alpha=0.4)
p.set_array(labels)
ax.add_collection(p)
fig.colorbar(p, ax=ax)

Matplotlib axis with two scales shared origin

I need two overlay two datasets with different Y-axis scales in Matplotlib. The data contains both positive and negative values. I want the two axes to share one origin, but Matplotlib does not align the two scales by default.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax1.bar(range(6), (2, -2, 1, 0, 0, 0))
ax2.plot(range(6), (0, 2, 8, -2, 0, 0))
plt.show()
I suppose it is possible to perform some computation with .get_ylim() and .set_ylim() two align the two scales. Is there an easier solution?
use the align_yaxis() function:
import numpy as np
import matplotlib.pyplot as plt
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax1.bar(range(6), (2, -2, 1, 0, 0, 0))
ax2.plot(range(6), (0, 2, 8, -2, 0, 0))
align_yaxis(ax1, 0, ax2, 0)
plt.show()
In order to ensure that the y-bounds are maintained (so no data points are shifted off the plot), and to balance adjustment of both y-axes, I made some additions to #HYRY's answer:
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
adjust_yaxis(ax2,(y1-y2)/2,v2)
adjust_yaxis(ax1,(y2-y1)/2,v1)
def adjust_yaxis(ax,ydif,v):
"""shift axis ax by ydiff, maintaining point v at the same location"""
inv = ax.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
miny, maxy = ax.get_ylim()
miny, maxy = miny - v, maxy - v
if -miny>maxy or (-miny==maxy and dy > 0):
nminy = miny
nmaxy = miny*(maxy+dy)/(miny+dy)
else:
nmaxy = maxy
nminy = maxy*(miny+dy)/(maxy+dy)
ax.set_ylim(nminy+v, nmaxy+v)
#drevicko's answer fails for me when plotting the following two sequences of points:
l1 = [0.03, -0.6, 1, 0.05]
l2 = [0.8, 0.9, 1, 1.1]
fig, ax1 = plt.subplots()
ax1.plot(l1)
ax2 = ax1.twinx()
ax2.plot(l2, color='r')
align_yaxis(ax1, 0, ax2, 0)
... so here's my version:
def align_yaxis(ax1, ax2):
"""Align zeros of the two axes, zooming them out by same ratio"""
axes = (ax1, ax2)
extrema = [ax.get_ylim() for ax in axes]
tops = [extr[1] / (extr[1] - extr[0]) for extr in extrema]
# Ensure that plots (intervals) are ordered bottom to top:
if tops[0] > tops[1]:
axes, extrema, tops = [list(reversed(l)) for l in (axes, extrema, tops)]
# How much would the plot overflow if we kept current zoom levels?
tot_span = tops[1] + 1 - tops[0]
b_new_t = extrema[0][0] + tot_span * (extrema[0][1] - extrema[0][0])
t_new_b = extrema[1][1] - tot_span * (extrema[1][1] - extrema[1][0])
axes[0].set_ylim(extrema[0][0], b_new_t)
axes[1].set_ylim(t_new_b, extrema[1][1])
There are in principle infinite different possibilities to align the zeros (or other values, which the other provided solutions accept): wherever you place zero on the y axis, you can zoom each of the two series so that it fits. We just pick the position such that, after the transformation, the two cover a vertical interval of same height.
Or in other terms, we minimize them of a same factor compared to the non-aligned plot.
(This does not mean that 0 is at half of the plot: this will happen e.g. if one plot is all negative and the other all positive.)
Numpy version:
def align_yaxis_np(ax1, ax2):
"""Align zeros of the two axes, zooming them out by same ratio"""
axes = np.array([ax1, ax2])
extrema = np.array([ax.get_ylim() for ax in axes])
tops = extrema[:,1] / (extrema[:,1] - extrema[:,0])
# Ensure that plots (intervals) are ordered bottom to top:
if tops[0] > tops[1]:
axes, extrema, tops = [a[::-1] for a in (axes, extrema, tops)]
# How much would the plot overflow if we kept current zoom levels?
tot_span = tops[1] + 1 - tops[0]
extrema[0,1] = extrema[0,0] + tot_span * (extrema[0,1] - extrema[0,0])
extrema[1,0] = extrema[1,1] + tot_span * (extrema[1,0] - extrema[1,1])
[axes[i].set_ylim(*extrema[i]) for i in range(2)]
The other answers here seem overly complicated and don't necessarily work for all the scenarios (e.g. ax1 is all negative and ax2 is all positive). There are 2 easy methods that always work:
Always put 0 in the middle of the graph for both y axes
A bit fancy and somewhat preserves the positive-to-negative ratio, see below
def align_yaxis(ax1, ax2):
y_lims = numpy.array([ax.get_ylim() for ax in [ax1, ax2]])
# force 0 to appear on both axes, comment if don't need
y_lims[:, 0] = y_lims[:, 0].clip(None, 0)
y_lims[:, 1] = y_lims[:, 1].clip(0, None)
# normalize both axes
y_mags = (y_lims[:,1] - y_lims[:,0]).reshape(len(y_lims),1)
y_lims_normalized = y_lims / y_mags
# find combined range
y_new_lims_normalized = numpy.array([numpy.min(y_lims_normalized), numpy.max(y_lims_normalized)])
# denormalize combined range to get new axes
new_lim1, new_lim2 = y_new_lims_normalized * y_mags
ax1.set_ylim(new_lim1)
ax2.set_ylim(new_lim2)
I've cooked up a solution starting from the above that will align any number of axes:
def align_yaxis_np(axes):
"""Align zeros of the two axes, zooming them out by same ratio"""
axes = np.array(axes)
extrema = np.array([ax.get_ylim() for ax in axes])
# reset for divide by zero issues
for i in range(len(extrema)):
if np.isclose(extrema[i, 0], 0.0):
extrema[i, 0] = -1
if np.isclose(extrema[i, 1], 0.0):
extrema[i, 1] = 1
# upper and lower limits
lowers = extrema[:, 0]
uppers = extrema[:, 1]
# if all pos or all neg, don't scale
all_positive = False
all_negative = False
if lowers.min() > 0.0:
all_positive = True
if uppers.max() < 0.0:
all_negative = True
if all_negative or all_positive:
# don't scale
return
# pick "most centered" axis
res = abs(uppers+lowers)
min_index = np.argmin(res)
# scale positive or negative part
multiplier1 = abs(uppers[min_index]/lowers[min_index])
multiplier2 = abs(lowers[min_index]/uppers[min_index])
for i in range(len(extrema)):
# scale positive or negative part based on which induces valid
if i != min_index:
lower_change = extrema[i, 1] * -1*multiplier2
upper_change = extrema[i, 0] * -1*multiplier1
if upper_change < extrema[i, 1]:
extrema[i, 0] = lower_change
else:
extrema[i, 1] = upper_change
# bump by 10% for a margin
extrema[i, 0] *= 1.1
extrema[i, 1] *= 1.1
# set axes limits
[axes[i].set_ylim(*extrema[i]) for i in range(len(extrema))]
example on 4 random series (you can see the discrete ranges on the 4 separate sets of y axis labels):
#Tim's solution adapted to work for more than two axes:
import numpy as np
def align_yaxis(axes):
y_lims = np.array([ax.get_ylim() for ax in axes])
# force 0 to appear on all axes, comment if don't need
y_lims[:, 0] = y_lims[:, 0].clip(None, 0)
y_lims[:, 1] = y_lims[:, 1].clip(0, None)
# normalize all axes
y_mags = (y_lims[:,1] - y_lims[:,0]).reshape(len(y_lims),1)
y_lims_normalized = y_lims / y_mags
# find combined range
y_new_lims_normalized = np.array([np.min(y_lims_normalized), np.max(y_lims_normalized)])
# denormalize combined range to get new axes
new_lims = y_new_lims_normalized * y_mags
for i, ax in enumerate(axes):
ax.set_ylim(new_lims[i])
I needed to align two subplots but not at their zeros. And other solutions didn't quite work for me.
The main code of my program looks like this. The subplots are not aligned. Further I only change align_yaxis function and keep all other code the same.
import matplotlib.pyplot as plt
def align_yaxis(ax1, v1, ax2, v2):
return 0
x = range(10)
y1 = [3.2, 1.3, -0.3, 0.4, 2.3, -0.9, 0.2, 0.1, 1.3, -3.4]
y2, s = [], 100
for i in y1:
s *= 1 + i/100
y2.append(s)
fig = plt.figure()
ax1 = fig.add_subplot()
ax2 = ax1.twinx()
ax1.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
ax1.bar(x, y1, color='tab:blue')
ax2.plot(x, y2, color='tab:red')
fig.tight_layout()
align_yaxis(ax1, 0, ax2, 100)
plt.show()
Picture of not aligned subplots
Using #HYRY's solution I get aligned subplots, but the second subplot is out of the figure. You can't see it.
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
Picture without second subplot
Using #drevicko's solution I also get aligned plot. But now the first subplot is out of the picture and first Y axis is quite weird.
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
adjust_yaxis(ax2,(y1-y2)/2,v2)
adjust_yaxis(ax1,(y2-y1)/2,v1)
def adjust_yaxis(ax,ydif,v):
"""shift axis ax by ydiff, maintaining point v at the same location"""
inv = ax.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
miny, maxy = ax.get_ylim()
miny, maxy = miny - v, maxy - v
if -miny>maxy or (-miny==maxy and dy > 0):
nminy = miny
nmaxy = miny*(maxy+dy)/(miny+dy)
else:
nmaxy = maxy
nminy = maxy*(miny+dy)/(maxy+dy)
ax.set_ylim(nminy+v, nmaxy+v)
Picture without firstsubplot
So I've tuned #drevicko's solution a little and got what I wanted.
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
adjust_yaxis(ax1,(y2 - y1)/2,v1)
adjust_yaxis(ax2,(y1 - y2)/2,v2)
def adjust_yaxis(ax,ydif,v):
"""shift axis ax by ydiff, maintaining point v at the same location"""
inv = ax.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
miny, maxy = ax.get_ylim()
nminy = miny - v + dy - abs(dy)
nmaxy = maxy - v + dy + abs(dy)
ax.set_ylim(nminy+v, nmaxy+v)
Subplots as I've expected them to look
This might not be what you are looking for but this helped me get whole numbers to line up on two different vertical axis:
ax1.set_ylim(0,4000)
ax2.set_ylim(0,120)
ax2.set_yticks(np.linspace(ax2.get_yticks()[0], ax2.get_yticks()[-1], len(ax1.get_yticks())))