Showing legend for only one subplot using matplotlib - python

I'm facing a problem in showing the legend in the correct format using matplotlib.
EDIT: I have 4 subplots in a figure in 2 by 2 format and I want legend only on the first subplot which has two lines plotted on it. The legend that I got using the code attached below contained endless entries and extended vertically throughout the figure. When I use the same code using linspace to generate fake data the legend works absolutely fine.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import os
#------------------set default directory, import data and create column output vectors---------------------------#
path="C:/Users/Pacman/Data files"
os.chdir(path)
data =np.genfromtxt('vrp.txt')
x=np.array([data[:,][:,0]])
y1=np.array([data[:,][:,6]])
y2=np.array([data[:,][:,7]])
y3=np.array([data[:,][:,9]])
y4=np.array([data[:,][:,11]])
y5=np.array([data[:,][:,10]])
nrows=2
ncols=2
tick_l=6 #length of ticks
fs_axis=16 #font size of axis labels
plt.rcParams['axes.linewidth'] = 2 #Sets global line width of all the axis
plt.rcParams['xtick.labelsize']=14 #Sets global font size for x-axis labels
plt.rcParams['ytick.labelsize']=14 #Sets global font size for y-axis labels
plt.subplot(nrows, ncols, 1)
ax=plt.subplot(nrows, ncols, 1)
l1=plt.plot(x, y2, 'yo',label='Flow rate-fan')
l2=plt.plot(x,y3,'ro',label='Flow rate-discharge')
plt.title('(a)')
plt.ylabel('Flow rate ($m^3 s^{-1}$)',fontsize=fs_axis)
plt.xlabel('Rupture Position (ft)',fontsize=fs_axis)
# This part is not working
plt.legend(loc='upper right', fontsize='x-large')
#Same code for rest of the subplots
I tried to implement a fix suggested in the following link, however, could not make it work:
how do I make a single legend for many subplots with matplotlib?
Any help in this regard will be highly appreciated.

If I understand correctly, you need to tell plt.legend what to put as legends... at this point it is being loaded empty. What you get must be from another source. I have quickly the following, and of course when I run fig.legend as you do I get nothing.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.4, 0.7])
ax2 = fig.add_axes([0.55, 0.1, 0.4, 0.7])
x = np.arange(0.0, 2.0, 0.02)
y1 = np.sin(2*np.pi*x)
y2 = np.exp(-x)
l1, l2 = ax1.plot(x, y1, 'rs-', x, y2, 'go')
y3 = np.sin(4*np.pi*x)
y4 = np.exp(-2*x)
l3, l4 = ax2.plot(x, y3, 'yd-', x, y4, 'k^')
fig.legend(loc='upper right', fontsize='x-large')
#fig.legend((l1, l2), ('Line 1', 'Line 2'), 'upper left')
#fig.legend((l3, l4), ('Line 3', 'Line 4'), 'upper right')
plt.show()
I'd suggest doing one by one, and then applying for all.

It is useful to work with the axes directly (ax in your case) when when working with subplots. So if you set up two plots in a figure and only wish to have a legend in your second plot:
t = np.linspace(0, 10, 100)
plt.figure()
ax1 = plt.subplot(2, 1, 1)
ax1.plot(t, t * t)
ax2 = plt.subplot(2, 1, 2)
ax2.plot(t, t * t * t)
ax2.legend('Cubic Function')
Note that when creating the legend, I am doing so on ax2 as opposed to plt. If you wish to create a second legend for the first subplot, you can do so in the same way but on ax1.

Related

How to increase plottable space above a subplot in matplotlib?

I am currently making a plot on matplotlib, which looks like below.
The code for which is:
fig, ax1 = plt.subplots(figsize=(20,5))
ax2 = ax1.twinx()
# plt.subplots_adjust(top=1.4)
ax2.fill_between(dryhydro_df['Time'],dryhydro_df['Flow [m³/s]'],0,facecolor='lightgrey')
ax2.set_ylim([0,10])
AB = ax2.fill_between(dryhydro_df['Time'],[12]*len(dryhydro_df['Time']),9.25,facecolor=colors[0],alpha=0.5,clip_on=False)
ab = ax2.scatter(presence_df['Datetime'][presence_df['AB']==True],[9.5]*sum(presence_df['AB']==True),marker='X',color='black')
# tidal heights
ax1.plot(tide_df['Time'],tide_df['Tide'],color='dimgrey')
I want the blue shaded region and black scatter to be above the plot. I can move the elements above the plot by using clip_on=False but I think I need to extend the space above the plot to do visualise it. Is there a way to do this? Mock-up of what I need is below:
You can use clip_on=False to draw outside the main plot. To position the elements, an xaxis transform helps. That way, x-values can be used in the x direction, while the y-direction uses "axes coordinates". ax.transAxes() uses "axes coordinates" for both directions.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
dates = pd.date_range('2018-07-01', '2018-07-31', freq='H')
xs = dates.to_numpy().astype(float)
ys = np.sin(xs * .091) * (np.sin(xs * .023) ** 2 + 1)
fig, ax1 = plt.subplots(figsize=(20, 5))
ax1.plot(dates, ys)
ax1.scatter(np.random.choice(dates, 10), np.repeat(1.05, 10), s=20, marker='*', transform=ax1.get_xaxis_transform(),
clip_on=False)
ax1.plot([0, 1], [1.05, 1.05], color='steelblue', lw=20, alpha=0.2, transform=ax1.transAxes, clip_on=False)
plt.tight_layout() # fit labels etc. nicely
plt.subplots_adjust(top=0.9) # make room for the additional elements
plt.show()

Pyplot subplots with equal absolute scale but different limits

I'm not sure my wording is correct, but what I am trying to do is create a figure of two subplots, where the two plots have different limits, but their size is such that the physical scale (as in, y-distance per centimeter of figure height) is the same. To clarify, lets say subplot 1 shows data from -3 to 3 and subplot 2 shows data from -1 to 1. I want to have them below one another in such a way that the height of subplot2 (excluding ticks, just everything inside the frame) is exactly one third of subplot 1.
My attempt was as follows:
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0,2, 101)
y1 = 3*np.cos(x*np.pi)
y2 = np.cos(x*np.pi)
fig = plt.figure(figsize=(4, 6))
gs = gridspec.GridSpec(8, 1)
ax1 = plt.subplot(gs[0:6,0])
ax1.plot(x, y1, c='orange')
ax1.set_ylim(-3, 3)
ax1.set_xticks([], [])
ax2 = plt.subplot(gs[6:,0])
ax2.plot(x, y2, c='green')
ax2.set_ylim(-1,1)
ax2.set_xticks([0, 1, 2])
ax2.set_xticklabels([r'0', r'0.5', r'1'])
ax2.set_xlabel(r'$n_g$ (2e)')
plt.tight_layout()
fig.text(-0.025, 0.5, 'Frequency (GHz)', ha='center', va='center', rotation='vertical', size=18)
which produces the figure below, but as you can see (although you have to look closely) the range -1 to 1 in the second subplot is compressed (takes up less height) than the range -1 to 1 in subplot 1. I'm guessing this is because of the space between the two subplots.
Note that I'm using gridspec because I plan on adding another column of subplots with interesting aspect ratio's and its own labels and limits. I didn't know how to add a global ylabel in a more elegant way, if someone was wondering.
You can set the height_ratios of the gridspec to match the range of the limits.
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0,2, 101)
y1 = 3*np.cos(x*np.pi)
y2 = np.cos(x*np.pi)
ylim1 = -3,3
ylim2 = -1,1
fig = plt.figure(figsize=(4, 6), constrained_layout=True)
gs = gridspec.GridSpec(2, 1, height_ratios=[np.diff(ylim1)[0],
np.diff(ylim2)[0]], figure=fig)
ax1 = plt.subplot(gs[0,0])
ax1.plot(x, y1, c='orange')
ax1.set_ylim(ylim1)
ax1.set_xticks([], [])
ax2 = plt.subplot(gs[1,0])
ax2.plot(x, y2, c='green')
ax2.set_ylim(ylim2)
ax2.set_xticks([0, 1, 2])
ax2.set_xticklabels([r'0', r'0.5', r'1'])
ax2.set_xlabel(r'$n_g$ (2e)')
plt.show()

Z-order across axes when using matplotlib's twinx [duplicate]

In pyplot, you can change the order of different graphs using the zorder option or by changing the order of the plot() commands. However, when you add an alternative axis via ax2 = twinx(), the new axis will always overlay the old axis (as described in the documentation).
Is it possible to change the order of the axis to move the alternative (twinned) y-axis to background?
In the example below, I would like to display the blue line on top of the histogram:
import numpy as np
import matplotlib.pyplot as plt
import random
# Data
x = np.arange(-3.0, 3.01, 0.1)
y = np.power(x,2)
y2 = 1/np.sqrt(2*np.pi) * np.exp(-y/2)
data = [random.gauss(0.0, 1.0) for i in range(1000)]
# Plot figure
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax2.hist(data, bins=40, normed=True, color='g',zorder=0)
ax2.plot(x, y2, color='r', linewidth=2, zorder=2)
ax1.plot(x, y, color='b', linewidth=2, zorder=5)
ax1.set_ylabel("Parabola")
ax2.set_ylabel("Normal distribution")
ax1.yaxis.label.set_color('b')
ax2.yaxis.label.set_color('r')
plt.show()
Edit: For some reason, I am unable to upload the image generated by this code. I will try again later.
You can set the zorder of an axes, ax.set_zorder(). One would then need to remove the background of that axes, such that the axes below is still visible.
ax2 = ax1.twinx()
ax1.set_zorder(10)
ax1.patch.set_visible(False)

Merge matplotlib subplots with shared x-axis

I have two graphs to where both have the same x-axis, but with different y-axis scalings.
The plot with regular axes is the data with a trend line depicting a decay while the y semi-log scaling depicts the accuracy of the fit.
fig1 = plt.figure(figsize=(15,6))
ax1 = fig1.add_subplot(111)
# Plot of the decay model
ax1.plot(FreqTime1,DecayCount1, '.', color='mediumaquamarine')
# Plot of the optimized fit
ax1.plot(x1, y1M, '-k', label='Fitting Function: $f(t) = %.3f e^{%.3f\t} \
%+.3f$' % (aR1,kR1,bR1))
ax1.set_xlabel('Time (sec)')
ax1.set_ylabel('Count')
ax1.set_title('Run 1 of Cesium-137 Decay')
# Allows me to change scales
# ax1.set_yscale('log')
ax1.legend(bbox_to_anchor=(1.0, 1.0), prop={'size':15}, fancybox=True, shadow=True)
Now, i'm trying to figure out to implement both close together like the examples supplied by this link
http://matplotlib.org/examples/pylab_examples/subplots_demo.html
In particular, this one
When looking at the code for the example, i'm a bit confused on how to implant 3 things:
1) Scaling the axes differently
2) Keeping the figure size the same for the exponential decay graph but having a the line graph have a smaller y size and same x size.
For example:
3) Keeping the label of the function to appear in just only the decay graph.
Any help would be most appreciated.
Look at the code and comments in it:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import gridspec
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
fig = plt.figure()
# set height ratios for subplots
gs = gridspec.GridSpec(2, 1, height_ratios=[2, 1])
# the first subplot
ax0 = plt.subplot(gs[0])
# log scale for axis Y of the first subplot
ax0.set_yscale("log")
line0, = ax0.plot(x, y, color='r')
# the second subplot
# shared axis X
ax1 = plt.subplot(gs[1], sharex = ax0)
line1, = ax1.plot(x, y, color='b', linestyle='--')
plt.setp(ax0.get_xticklabels(), visible=False)
# remove last tick label for the second subplot
yticks = ax1.yaxis.get_major_ticks()
yticks[-1].label1.set_visible(False)
# put legend on first subplot
ax0.legend((line0, line1), ('red line', 'blue line'), loc='lower left')
# remove vertical gap between subplots
plt.subplots_adjust(hspace=.0)
plt.show()
Here is my solution:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
fig, (ax1,ax2) = plt.subplots(nrows=2, sharex=True, subplot_kw=dict(frameon=False)) # frameon=False removes frames
plt.subplots_adjust(hspace=.0)
ax1.grid()
ax2.grid()
ax1.plot(x, y, color='r')
ax2.plot(x, y, color='b', linestyle='--')
One more option is seaborn.FacetGrid but this requires Seaborn and Pandas libraries.
Here are some adaptions to show how the code could work to add a combined legend when plotting a pandas dataframe. ax=ax0 can be used to plot on a given ax and ax0.get_legend_handles_labels() gets the information for the legend.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
dates = pd.date_range('20210101', periods=100, freq='D')
df0 = pd.DataFrame({'x': np.random.normal(0.1, 1, 100).cumsum(),
'y': np.random.normal(0.3, 1, 100).cumsum()}, index=dates)
df1 = pd.DataFrame({'z': np.random.normal(0.2, 1, 100).cumsum()}, index=dates)
fig, (ax0, ax1) = plt.subplots(nrows=2, sharex=True, gridspec_kw={'height_ratios': [2, 1], 'hspace': 0})
df0.plot(ax=ax0, color=['dodgerblue', 'crimson'], legend=False)
df1.plot(ax=ax1, color='limegreen', legend=False)
# put legend on first subplot
handles0, labels0 = ax0.get_legend_handles_labels()
handles1, labels1 = ax1.get_legend_handles_labels()
ax0.legend(handles=handles0 + handles1, labels=labels0 + labels1)
# remove last tick label for the second subplot
yticks = ax1.get_yticklabels()
yticks[-1].set_visible(False)
plt.tight_layout()
plt.show()

matplotlib two legends out of plot

I'm facing problem with showing two legends outside of plot.
Showing multiple legends inside plot is easy - its described in matplotlib doc's with examples.
Even showing one legend outside of plot is rather easy as i found here on stackoverflow (ex. here).
But i cant find working example to show two legends outside of the plot.
Methods which work with one legend is not working in this case.
Here is an example.
First of all base code:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.lines import Line2D
from matplotlib.font_manager import FontProperties
fig1 = plt.figure(figsize=(17,5))
fontP = FontProperties()
fontP.set_size('small')
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.grid()
# stuff for legend
rec1 = patches.Rectangle(
(0.9, 0.25), # (x,y)
0.1, # width
0.1, # height
label='rectangle',
**{
'color': 'blue'
}
)
ax1.add_patch(rec1)
leg = plt.legend(handles=[rec1], bbox_to_anchor=(0.7, -0.1))
fig1.savefig('sample1.png', dpi=90, bbox_inches='tight')
But now i want to draw another legend at the right side of plot.
Here is the code:
...
ax1.add_patch(rec1)
l1 = plt.legend(prop=fontP, handles=[rec1], loc='center left',
box to_anchor=(1.0, 0.5))
plt.gca().add_artist(l1)
...
And the result:
As you can see, second legend is truncated.
My conclusion is that matplotlib ignores size and position of objects added with
plt.gca().add_artist(obj)
How can i fix this?
So far i found a solution but its very nasty:
Create three legends, two of them as additiontal (added by add_artist) and one as normal legend.
As far matplotlib respect position and size of normal legends, move it to the right down corner and hide it with code:
leg.get_frame().set_alpha(0)
Here are the results (without setting alpha for example purpose):
It behave exactly how i want it to but as you know its nasty.
Here is the final code:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.lines import Line2D
from matplotlib.font_manager import FontProperties
fig1 = plt.figure(figsize=(17,5))
fontP = FontProperties()
fontP.set_size('small')
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.grid()
# stuff for additional legends
rec1 = patches.Rectangle(
(0.9, 0.25), # (x,y)
0.1, # width
0.1, # height
label='rectangle',
**{
'color': 'blue'
}
)
ax1.add_patch(rec1)
# example additional legends
l1 = plt.legend(prop=fontP, handles=[rec1], loc='center left',
bbox_to_anchor=(1.0, 0.5))
l2 = plt.legend(prop=fontP, handles=[rec1], loc=3, bbox_to_anchor=(0.4,
-0.2))
# add legends
plt.gca().add_artist(l1)
plt.gca().add_artist(l2)
# add third legend
leg = plt.legend(handles=[], bbox_to_anchor=(1.3, -0.3))
leg.get_frame().set_alpha(0) # hide legend
fig1.savefig('sample3.png', dpi=90, bbox_inches='tight')
I can suggest the following solution:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
fig.set_size_inches((10,10))
gs1 = gridspec.GridSpec(1, 1)
ax1 = fig.add_subplot(gs1[0])
x = np.arange(0.0, 3.0, 0.02)
y1 = np.sin(2*np.pi*x)
y2 = np.exp(-x)
l1, l2 = ax1.plot(x, y1, 'rs-', x, y2, 'go')
y3 = np.sin(4*np.pi*x)
y4 = np.exp(-2*x)
l3, l4 = ax1.plot(x, y3, 'yd-', x, y4, 'k^')
fig.legend((l1, l2), ('Line 1', 'Line 2'), "right")
fig.legend((l3, l4), ('Line 3', 'Line 4'), "lower center")
gs1.tight_layout(fig, rect=[0, 0.1, 0.8, 0.5])
I used an example from matplotlib site and followed the documentation about tight layout http://matplotlib.org/users/tight_layout_guide.html.
The result is

Categories