interactive legend with twinx - matplotlib - matplotlib

i want to create a plot with two y-axis and interactive legend. I made a minimal "working" example based on: https://matplotlib.org/3.1.1/gallery/event_handling/legend_picking.html
import matplotlib.pyplot as plt
import numpy as np
t = np.arange(0.0, 5, 0.01)
y1 = 2*np.sin(2*np.pi*t)
y2 = 4*np.sin(2*np.pi*2*t)+1
fig, ax = plt.subplots()
ax.set_title('Click on legend line to toggle line on/off')
line1, = ax.plot(t, y1, lw=2, label='1 HZ')
ax2 = ax.twinx()
line2, = ax2.plot(t, y2, lw=2, label='2 HZ')
lines_twinx=[line1,line2]
lbl = [l.get_label() for l in lines_twinx]
leg=ax.legend(lines_twinx, lbl, loc="upper left",fontsize='xx-small', shadow=True)
leg.get_frame().set_alpha(0.4)
lined = dict()
for legline, origline in zip(leg.get_lines(), lines_twinx):
legline.set_picker(5) # 5 pts tolerance
lined[legline] = origline
def onpick(event):
# on the pick event, find the orig line corresponding to the
# legend proxy line, and toggle the visibility
legline = event.artist
origline = lined[legline]
vis = not origline.get_visible()
origline.set_visible(vis)
# Change the alpha on the line in the legend so we can see what lines
# have been toggled
if vis:
legline.set_alpha(1.0)
else:
legline.set_alpha(0.2)
fig.canvas.draw()
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
plot-figure
Somehow the legend is not clickable. Anybody knows what to do?

Using fig.legend() instead of ax.legend() helped me
leg=fig.legend(lines_twinx, lbl, loc="upper left",fontsize='xx-small', shadow=True)
view here

Related

How to change font parameters in Z axis?

I'm trying to personalize some of my graphs so I'd like to change the font of all axis. I wrote the code below for that. The problem is that I can't change the font of the z axis. Somebody have a recommendation?
Thanks in advance.
import matplotlib.pyplot as plt
from matplotlib import cm
font = {'size' : 12}
cm=1/2.54
size_x = 10*cm
size_y = 8*cm
min_limx = 4
max_limx = 10
min_limy = 0
max_limy = 50
min_limz = 0
max_limz = 8
#%%
fig, ax = plt.subplots(figsize=(size_x, size_y), subplot_kw={"projection": "3d"})
X, Y = np.meshgrid(xnew, ynew, indexing='ij')
Z1 = m_fill
Z2 = m_nofill
surf = ax.plot_surface(X, Y, Z1, cmap=cm.viridis, linewidth=0, antialiased=False)
surf = ax.plot_surface(X, Y, Z2, color='w', linewidth=0, antialiased=False)
ax.set_xlabel('$\lambda$', **font)
ax.set_ylabel('$\chi$', **font)
ax.set_zlabel('$\it{m_{0}, кг}$', **font)
lin_x = np.arange(min_limx, max_limx, step=2)
lin_y = np.arange(min_limy, max_limy, step=15)
plt.xticks(lin_x, **font)
plt.yticks(lin_y, **font)
#plt.zticks(lin_z, **font) # AttributeError: module 'matplotlib.pyplot' has no attribute 'zticks'
ax.set_zlim(0, 8, )
#plt.gca().set_aspect('equal', adjustable='box')
ax.view_init(45, -45)
#plt.tight_layout()
plt.grid()
plt.show()
fig.savefig('p0_V_m0.png', format='png', dpi=300)
Ps: This is how the figure looks like right now.
The fontsize of the tick labels is set by the labelsize parameter:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
font = {'size': 20}
ax.tick_params('z', labelsize=font['size'])
(To set the label size for all three axes you can use ax.tick_params(labelsize=20).)

Not getting legend in figure/plot

I am sharing Y-axis in two subplots, with the following codes but both shared plots are missing legends in them.
projectDir = r'/media/DATA/banikr_D_drive/model/2021-04-28-01-18-15_5_fold_114sub'
logPath = os.path.join(projectDir, '2021-04-28-01-18-15_fold_1_Mixed_loss_dice.bin')
with open(logPath, 'rb') as pfile:
h = pickle.load(pfile)
print(h.keys())
fig, ax = plt.subplots(2, figsize=(20, 20), dpi=100)
ax[0].plot(h['dice_sub_train'], color='tab:cyan', linewidth=2.0, label="Train")
ax[0].plot(smooth_curve(h['dice_sub_train']), color='tab:purple')
ax[0].set_xlabel('Epoch/iterations', fontsize=20)
ax[0].set_ylabel('Dice Score', fontsize=20)
ax[0].legend(loc='lower right', fontsize=20)#, frameon=False)
ax1 = ax[0].twiny()
ax1.plot(h['dice_sub_valid'], color='tab:orange', linewidth=2.0, alpha=0.9, label="Validation" )
ax1.plot(smooth_curve(h['dice_sub_valid']), color='tab:red')
# , bbox_to_anchor = (0.816, 0.85)
ax[1].plot(h['loss_sub_train'], color='tab:cyan', linewidth=2.0, label="Train")
ax[1].plot(smooth_curve(h['loss_sub_train']), color='tab:purple')
ax2 = ax[1].twiny()
ax2.plot(h['loss_sub_valid'], color='tab:orange', linewidth=2.0, label="Validation", alpha=0.6)
ax2.plot(smooth_curve(h['loss_sub_valid']), color='tab:red')
ax[1].set_xlabel('Epoch/iterations', fontsize=20)
ax[1].set_ylabel('loss(a.u.)', fontsize=20)
ax[1].legend(loc='upper right', fontsize=20)
# ,bbox_to_anchor = (0.8, 0.9)
plt.suptitle('Subject wise dice score and loss', fontsize=30)
plt.setp(ax[0].get_xticklabels(), fontsize=20, fontweight="normal", horizontalalignment="center") #fontweight="bold"
plt.setp(ax[0].get_yticklabels(), fontsize=20, fontweight='normal', horizontalalignment="right")
plt.setp(ax[1].get_xticklabels(), fontsize=20, fontweight="normal", horizontalalignment="center")
plt.setp(ax[1].get_yticklabels(), fontsize=20, fontweight="normal", horizontalalignment="right")
plt.show()
Any idea how to solve the issue?
[1]: https://i.stack.imgur.com/kg7PY.png
ax1 has a twin y-axis with ax[0], but they are two separate axes. That's why ax[0].legend() does not know about the Validation line of ax1.
To have Train and Validation on the same legend, plot empty lines on the main axes ax[0] and ax[1] with the desired color and label. This will generate dummy Validation entries on the main legend:
...
ax[0].plot([], [], color='tab:orange', label="Validation")
ax[0].legend(loc='lower right', fontsize=20)
...
ax[1].plot([], [], color='tab:orange', label="Validation")
ax[1].legend(loc='upper right', fontsize=20)
...

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

Legend picking - Enable picking on the legend to toggle the original line on and off

I try to reproduce following example, but however I fail.
So what I would like to achieve is following: I have mutiple dataframes and these should be plotted individually - For this I already created a UI. I would like to click on the items on within the legend to switch on or off the line. To achieve this I used the following example
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 0.2, 0.1)
y1 = 2*np.sin(2*np.pi*t)
y2 = 4*np.sin(2*np.pi*2*t)
fig, ax = plt.subplots()
ax.set_title('Click on legend line to toggle line on/off')
line1, = ax.plot(t, y1, lw=2, label='1 HZ')
line2, = ax.plot(t, y2, lw=2, label='2 HZ')
leg = ax.legend(loc='upper left', fancybox=True, shadow=True)
leg.get_frame().set_alpha(0.4)
# we will set up a dict mapping legend line to orig line, and enable
# picking on the legend line
lines = [line1, line2]
lined = dict()
for legline, origline in zip(leg.get_lines(), lines):
legline.set_picker(5) # 5 pts tolerance
lined[legline] = origline
def onpick(event):
# on the pick event, find the orig line corresponding to the
# legend proxy line, and toggle the visibility
legline = event.artist
origline = lined[legline]
vis = not origline.get_visible()
origline.set_visible(vis)
# Change the alpha on the line in the legend so we can see what lines
# have been toggled
if vis:
legline.set_alpha(1.0)
else:
legline.set_alpha(0.2)
fig.canvas.draw()
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
If copy paste this to have two figures (I did this to check my error - since I had an error producing multiple plots) it gives me two figures, but I am not able to click on the legends of both figures, only on the last, here my code
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 0.2, 0.1)
y1 = 2*np.sin(2*np.pi*t)
y2 = 4*np.sin(2*np.pi*2*t)
fig, ax = plt.subplots()
ax.set_title('Click on legend line to toggle line on/off')
Line1, = ax.plot(t, y1, lw=2, label='1 HZ')
Line2, = ax.plot(t, y2, lw=2, label='2 HZ')
Leg = ax.legend(loc='upper left', fancybox=True, shadow=True)
Leg.get_frame().set_alpha(0.4)
# we will set up a dict mapping legend line to orig line, and enable
# picking on the legend line
Lines = [Line1, Line2]
Lined = dict()
for Legline, Origline in zip(Leg.get_lines(), Lines):
Legline.set_picker(5) # 5 pts tolerance
Lined[Legline] = Origline
def onpick(Event):
# on the pick event, find the orig line corresponding to the
# legend proxy line, and toggle the visibility
Legline = Event.artist
Origline = Lined[Legline]
vis = not Origline.get_visible()
Origline.set_visible(vis)
# Change the alpha on the line in the legend so we can see what lines
# have been toggled
if vis:
Legline.set_alpha(1.0)
else:
Legline.set_alpha(0.2)
fig.canvas.draw()
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
t = np.arange(0.0, 0.2, 0.1)
y1 = 2*np.sin(2*np.pi*t)
y2 = 4*np.sin(2*np.pi*2*t)
fig, ax = plt.subplots()
ax.set_title('Click on legend line to toggle line on/off')
line1, = ax.plot(t, y1, lw=2, label='1 HZ')
line2, = ax.plot(t, y2, lw=2, label='2 HZ')
leg = ax.legend(loc='upper left', fancybox=True, shadow=True)
leg.get_frame().set_alpha(0.4)
# we will set up a dict mapping legend line to orig line, and enable
# picking on the legend line
lines = [line1, line2]
lined = dict()
for legline, origline in zip(leg.get_lines(), lines):
legline.set_picker(5) # 5 pts tolerance
lined[legline] = origline
def onpick2(event):
# on the pick event, find the orig line corresponding to the
# legend proxy line, and toggle the visibility
legline = event.artist
origline = lined[legline]
vis = not origline.get_visible()
origline.set_visible(vis)
# Change the alpha on the line in the legend so we can see what lines
# have been toggled
if vis:
legline.set_alpha(1.0)
else:
legline.set_alpha(0.2)
fig.canvas.draw()
fig.canvas.mpl_connect('pick_event', onpick2)
plt.show()
So basically I just copy pasted it. But it's not working the way I expected it. Anyone any recommendation?
You are using some variables that have the same name in the first and second figures. The event handling for the first figure is trying to access variables that have been overwritten in creating the second figure, and that's where things fail.
If you make sure you use unique variable names, everything works:
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 0.2, 0.1)
y1 = 2 * np.sin(2 * np.pi * t)
y2 = 4 * np.sin(2 * np.pi * 2 * t)
fig1, ax1 = plt.subplots()
ax1.set_title('Click on legend line to toggle line on/off')
Line11, = ax1.plot(t, y1, lw=2, label='1 HZ')
Line12, = ax1.plot(t, y2, lw=2, label='2 HZ')
Leg1 = ax1.legend(loc='upper left', fancybox=True, shadow=True)
Leg1.get_frame().set_alpha(0.4)
# we will set up a dict mapping legend line to orig line, and enable
# picking on the legend line
Lines1 = [Line11, Line12]
Lined1 = dict()
for Legline, Origline in zip(Leg1.get_lines(), Lines1):
Legline.set_picker(5) # 5 pts tolerance
Lined1[Legline] = Origline
def onpick(Event):
# on the pick event, find the orig line corresponding to the
# legend proxy line, and toggle the visibility
Legline = Event.artist
Origline = Lined1[Legline]
vis = not Origline.get_visible()
Origline.set_visible(vis)
# Change the alpha on the line in the legend so we can see what lines
# have been toggled
if vis:
Legline.set_alpha(1.0)
else:
Legline.set_alpha(0.2)
fig1.canvas.draw()
fig1.canvas.mpl_connect('pick_event', onpick)
t = np.arange(0.0, 0.2, 0.1)
y1 = 2 * np.sin(2 * np.pi * t)
y2 = 4 * np.sin(2 * np.pi * 2 * t)
fig2, ax2 = plt.subplots()
ax2.set_title('Click on legend line to toggle line on/off')
line21, = ax2.plot(t, y1, lw=2, label='1 HZ')
line22, = ax2.plot(t, y2, lw=2, label='2 HZ')
leg2 = ax2.legend(loc='upper left', fancybox=True, shadow=True)
leg2.get_frame().set_alpha(0.4)
# we will set up a dict mapping legend line to orig line, and enable
# picking on the legend line
lines2 = [line21, line22]
lined2 = dict()
for legline, origline in zip(leg2.get_lines(), lines2):
legline.set_picker(5) # 5 pts tolerance
lined2[legline] = origline
def onpick2(event):
# on the pick event, find the orig line corresponding to the
# legend proxy line, and toggle the visibility
legline = event.artist
origline = lined2[legline]
vis = not origline.get_visible()
origline.set_visible(vis)
# Change the alpha on the line in the legend so we can see what lines
# have been toggled
if vis:
legline.set_alpha(1.0)
else:
legline.set_alpha(0.2)
fig2.canvas.draw()
fig2.canvas.mpl_connect('pick_event', onpick2)
plt.show()

Legend location in matplotlib subplot

I am trying to create subplots on (6X3) grid. I am having a problem with the location of legend. The legend is common to all subplot. The lgend is now overlapping with the y-axis label
I tried with removing the constrained_layout=True option. But this keeps a lot of white space between legend and subplots.
import numpy as np
import matplotlib.pyplot as plt
#plt.rcParams["font.family"] = "Times New Roman"
#plt.rcParams.update({'font.size': 12})
font = {'family' : 'Times New Roman',
'size' : 14}
plt.rc('font', **font)
t = np.linspace(0,10, num=200)
fig, axs = plt.subplots(6, 3, figsize=(12,16))#, constrained_layout=True)
i = 0 # i = 0 for x = 0.25; i = 3 for x = -0.25
j = 6 # j = 6 for x = 0.25; j = 9 for x = -0.25
#%%
solution = np.loadtxt(open("sequential_legs=01.csv", "rb"), delimiter=",", skiprows=0)
axs[0, 0].plot(t, solution[:,i],'r-')
axs[0, 0].plot(t, solution[:,i+1],'g-')
axs[0, 0].plot(t, solution[:,i+2],'b-')
axs[0, 0].plot(t, solution[:,j],'r--')
axs[0, 0].plot(t, solution[:,j+1],'g--')
axs[0, 0].plot(t, solution[:,j+2],'b--')
axs[0, 0].set_title('DNN-S (p = 1)', fontsize=14)
axs[0, 0].set_xlim([0, 10])
(repeated for each grid)
line_labels = ["$y_1$ (True)","$y_2$ (True)", "$y_3$ (True)", "$y_1$ (ML-Pred)","$y_2$ (ML-Pred)", "$y_3$ (ML-Pred)"]
plt.figlegend( line_labels, loc = 'lower center', borderaxespad=0.1, ncol=6, labelspacing=0., prop={'size': 13} ) #bbox_to_anchor=(0.5, 0.0), borderaxespad=0.1,
for ax in axs.flat:
ax.set(xlabel='Time', ylabel='Response')
for ax in axs.flat:
ax.label_outer()
fig.savefig('LSE_X=025.pdf', bbox_inches = 'tight')
constrained_layout does not take figure legends into account. The easiest option I currently see here is to use tight_layout to have the subplots distribute nicely in the figure, and after that, add some space for the legend manually.
import matplotlib.pyplot as plt
fig, axs = plt.subplots(6, 3, figsize=(12,8), sharex=True, sharey=True,
constrained_layout=False)
labels = [f"Label {i}" for i in range(1,5)]
for ax in axs.flat:
for i, lb in enumerate(labels):
ax.plot([1,2], [0,i+1], label=lb)
ax.set_xlabel("x label")
ax.label_outer()
fig.tight_layout()
fig.subplots_adjust(bottom=0.1) ## Need to play with this number.
fig.legend(labels=labels, loc="lower center", ncol=4)
plt.show()