How to keep legends inside plot? [duplicate] - python

This question already has answers here:
Secondary axis with twinx(): how to add to legend?
(11 answers)
Closed 3 years ago.
When I use matplotlib to draw some figures, the legends are always outside the plot. How to keep the legends inside plot ? You can see the result here
I have tried that bbox_to_anchor can work. But it's not convenient due to I don't want to modify the positions every time drawing a new figure.
The code is just an example to reproduce my problem.
import matplotlib.pyplot as plt
import numpy as np
time_step = np.arange(0, 200.01, 40).tolist()
drag3 = [1, 1, 1, 1, 1, 1]
lift3 = [1.5, 1, 1, 1, 1, 0.2]
second_drag3 = [1.2, 1.2, 1.2, 1.3, 1.2, 0.5]
second_lift3 = [1.2, 1.2, 1.2, 1.3, 1.2, 0.8]
fig, ax1 = plt.subplots()
ax1.plot(time_step, drag3, label="40$C_D1$", color='blue', linestyle='-', linewidth=1.0)
ax1.plot(time_step, second_drag3, label="40$C_D2$", color='darkviolet', linestyle='-', linewidth=1.0)
ax2 = ax1.twinx()
ax2.plot(time_step, lift3, label="40$C_L1$", color='red', linestyle='-', linewidth=1.0)
ax2.plot(time_step, second_lift3, label="40$C_L2$", color='limegreen', linestyle='-', linewidth=1.0)
plt.tight_layout()
fig.legend(loc='lower right', ncol=2)
plt.show()
I want to keep the all legends inside the plot.
Thanks for any help !

You can add extra padding between the axes and the legend by adding the "borderaxespad" kwarg to your legend call:
import matplotlib.pyplot as plt
import numpy as np
time_step = np.arange(0, 200.01, 40).tolist()
drag3 = [1, 1, 1, 1, 1, 1]
lift3 = [1.5, 1, 1, 1, 1, 0.2]
second_drag3 = [1.2, 1.2, 1.2, 1.3, 1.2, 0.5]
second_lift3 = [1.2, 1.2, 1.2, 1.3, 1.2, 0.8]
fig, ax1 = plt.subplots()
ax1.plot(time_step, drag3, label="40$C_D1$", color='blue', linestyle='-', linewidth=1.0)
ax1.plot(time_step, second_drag3, label="40$C_D2$", color='darkviolet', linestyle='-', linewidth=1.0)
ax2 = ax1.twinx()
ax2.plot(time_step, lift3, label="40$C_L1$", color='red', linestyle='-', linewidth=1.0)
ax2.plot(time_step, second_lift3, label="40$C_L2$", color='limegreen', linestyle='-', linewidth=1.0)
plt.tight_layout()
fig.legend(loc='lower right', ncol=2, borderaxespad=3)
plt.show()
This adds extra space between the legend and the figure boundary so when it is in the lower right corner it will move up and to the left. If it is in the lower left corner it will move up and to the right.

Related

How to prevent spans from hiding bar charts?

I'm plotting a bar graphic and horizontal spans with this code:
fig = plt.figure('Graphic', figsize=(20,15), dpi=400)
ax1 = fig.add_axes([0.1, 0.1, 0.85, 0.75])
data.plot('DATE',["PP"],kind='bar',color='black', fontsize = 15.0,ax=ax1,alpha=1)
data.plot('DATE',['PP'],kind='line',marker='*',style=['--'],linewidth=1,color='gray', ms=5,ax=ax1)
ax1.axhspan(0, 1, facecolor='lightyellow', alpha=1)
ax1.axhspan(1, 1.5, facecolor='yellow', alpha=1)
ax1.axhspan(1.5, 2, facecolor='lime', alpha=1)
ax1.axhspan(2, 3.5, facecolor='green', alpha=1)
ax1.axhspan(0, -1, facecolor='bisque', alpha=1)
ax1.axhspan(-1, -1.5, facecolor='orange', alpha=1)
ax1.axhspan(-1.5, -2, facecolor='pink', alpha=1)
ax1.axhspan(-2, -3.5, facecolor='red', alpha=1)
The issue is that spans are hiding the Bar graphic. I would like to be able to visualize the spans with the bar graphs. Both with alpha=1. I don't want to reduce the alpha values.
Is this possible?
Thanks in advance.
I am displaying the image with axhspans with alpha=1 covering the bar charts.
I noticed two things that needed to change.
When you use pandas line and bar plots with X-axis being dates, there was/is a bug. Refer to this link. The workaround used here is what was there. Using matplotlib plot instead of pandas helped resolve this.
Refer to zorder. You can specify the order of the various components (line plot, bar, spans) to tell it what will come on top of what. Higher the zorder, the higher the plot will be. I have used 1 for the spans zorder, 2 for the bar plot zorder and 2 for line plot.
Updated code is below. See if this helps.
fig = plt.figure('Graphic', figsize=(20,15), dpi=400)
ax1 = fig.add_axes([0.1, 0.1, 0.85, 0.75])
data.plot('DATE',["PP"],kind='bar',color='black', fontsize = 15.0,ax=ax1,alpha=1, zorder=2) ## Added zorder
# Changed to matplotlib, increased linewidth to 3 so you can see it and zorder=3
ax1.plot(data[['PP']], marker='*',ls='--',linewidth=3,color='gray', ms=5, zorder=3)
## All zorder = 0
ax1.axhspan(0, 1, facecolor='lightyellow', alpha=1, zorder=1)
ax1.axhspan(1, 1.5, facecolor='yellow', alpha=1, zorder=1)
ax1.axhspan(1.5, 2, facecolor='lime', alpha=1, zorder=1)
ax1.axhspan(2, 3.5, facecolor='green', alpha=1, zorder=1)
ax1.axhspan(0, -1, facecolor='bisque', alpha=1, zorder=1)
ax1.axhspan(-1, -1.5, facecolor='orange', alpha=1, zorder=1)
ax1.axhspan(-1.5, -2, facecolor='pink', alpha=1, zorder=1)
ax1.axhspan(-2, -3.5, facecolor='red', alpha=1, zorder=1)
Plot
The order of display in the pandas plot is not adjustable, so I guess we have to deal with it in matplotlib. ax is set up with a line chart and horizontal fill, and a bar chart is added as a second axis. Then I get the order of the line chart, add 1 to the value of the line chart, and set the display order to the bar chart. Since no data was provided, stock price data was used as a sample.
import yfinance as yf
import pandas as pd
data = yf.download("AAPL", start="2022-06-01", end="2022-09-01")
data.index = pd.to_datetime(data.index)
import matplotlib.pyplot as plt
fig = plt.figure('Graphic', figsize=(10,7.5), dpi=100)
ax1 = fig.add_axes([0.1, 0.1, 0.85, 0.75])
ax1.plot(data.index, data['Close'], marker='*', linestyle='--', linewidth=1, color='gray', ms=5)
ax1.axhspan(170, 180, facecolor='lightyellow', alpha=1)
ax1.axhspan(160, 170, facecolor='yellow', alpha=1)
ax1.axhspan(150, 160, facecolor='lime', alpha=1)
ax1.axhspan(145, 150, facecolor='green', alpha=1)
ax1.axhspan(140, 145, facecolor='bisque', alpha=1)
ax1.axhspan(135, 140, facecolor='orange', alpha=1)
ax1.axhspan(130, 135, facecolor='pink', alpha=1)
ax1.axhspan(120, 130, facecolor='red', alpha=1)
ax2 = ax1.twinx()
ax2.bar(x=data.index, height=data['Volume'], color='black')
ax2.set_zorder(ax1.get_zorder()+1)
ax2.set_frame_on(False)
ax1.set_ylim(120, 180)
plt.show()

How to preserve axis aspect ratio with tight_layout

I have a plot with both a colorbar and a legend. I want to place the legend outside of the plot to the right of the colorbar. To accomplish this, I use bbox_to_anchor argument, but this causes the legend to get cut off:
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import norm
_, ax = plt.subplots()
extent = np.r_[0, 1, 0, 1]
space = np.linspace(0, 1)
probs = np.array([[norm.cdf(x + y) for x in space] for y in space])
colormap = ax.imshow(probs, aspect="auto", origin="lower", extent=extent, alpha=0.5)
colorbar = plt.colorbar(colormap, ax=ax)
colorbar.set_label(f"Probability")
ax.scatter(
[0.2, 0.4, 0.6], [0.8, 0.6, 0.4], color="r", label="Labeled Points",
)
plt.legend(loc="center left", bbox_to_anchor=(1.3, 0.5))
plt.title
plt.show()
Plot with legend cut off
To fix the legend, I insert a call to plt.tight_layout() before plt.show(), but this causes the aspect ratio to get distorted:
Plot with distorted aspect ratio
How can I show the entire legend and preserve the aspect ratio of the axes?
You can manage the ratio between axis height and width with matplotlib.axes.Axes.set_aspect. Since you want them to be equal:
ax.set_aspect(1)
Then you can use matplotlib.pyplot.tight_layout to fit the legend within the figure.
If you want to adjust margins too, you can use matplotlib.pyplot.subplots_adjust.
Complete Code
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import norm
_, ax = plt.subplots()
extent = np.r_[0, 1, 0, 1]
space = np.linspace(0, 1)
probs = np.array([[norm.cdf(x + y) for x in space] for y in space])
colormap = ax.imshow(probs, aspect="auto", origin="lower", extent=extent, alpha=0.5)
colorbar = plt.colorbar(colormap, ax=ax)
colorbar.set_label(f"Probability")
ax.scatter([0.2, 0.4, 0.6], [0.8, 0.6, 0.4], color="r", label="Labeled Points",)
plt.legend(loc="center left", bbox_to_anchor=(1.3, 0.5))
ax.set_aspect(1)
plt.tight_layout()
plt.subplots_adjust(left = 0.1)
plt.show()

Step wise area plot in matplotlib

I would like to have a step-wise area plot in matplotib with pandas. I adjusted the code for a step-wise line plot but I get an error message. Here is the current code:
import pandas as pd
from matplotlib import pyplot as plt
%matplotlib inline
columns = ['Conventional control', 'Optimized control']
power_values = [[0.73,1.28],
[0.21, 0.21],
[0.18, 0.18],
[0.16, 1.00],
[0.57, 0.76],
[1.63, 1.62],
[3.28, 2.77],
[3.92, 0.47],
[3.29, 0.51],
[2.01, 3.64],
[1.72, 4.45],
[2.2, 0.59],
[2.33, 4.34],
[2.01, 2.05],
[1.39, 1.68],
[2.06, 0.55],
[3.07, 0.61],
[4.07, 0.61],
[3.66, 0.59],
[2.67, 0.59] ,
[1.54, 1.65],
[1.37, 1.55],
[1.36, 0.95],
[1.1, 1.70],
[0,0]]
wind_data = pd.DataFrame(power_values, index=range(0, 25), columns=columns)
fig = plt.figure(linewidth=1, figsize=(9, 5))
ax = wind_data.plot.area(ax=plt.gca(), color =["saddlebrown", "limegreen"], stacked=False, drawstyle="steps-post" )
ax.set_facecolor("white")
ax.set_xlabel("Time of day", fontsize = 14, labelpad=8)
ax.set_ylabel("Electrical power in kW", fontsize = 14,labelpad=8)
ax.set_xlim(0, 24)
ax.set_ylim(0, 5)
plt.xticks(wind_data.index, labels=[f'{h:02d}:00' for h in wind_data.index], rotation=90)
plt.grid(axis='y', alpha=.4)
plt.tight_layout()
hours = list(range(25)) # [0, 1, 2, ... 22, 23, 24]
labels = [f'{h:02d}:00' for h in hours] # ["00:00", "01:00", ... "23:00", "24:00"]
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='center left', bbox_to_anchor=(0.15, 1.07), fontsize = 14, ncol=3)
plt.savefig('CS_Cost_PerTimeslot.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()
If I do not use the argument "drawstyle="steps-post" I get just an normal area plot. But I would like to have a step-wise area plot. When using this attribut (as with the line plot) I get the error message:"
AttributeError: 'PolyCollection' object has no property 'drawstyle'
". I'd be very happy if someone could help me on that. Maybe there is also another way how to tell matpoltlib not to linearly interpolate the lines between the data points.
I think the simplest way to solve your problem is to use the pyplot fill_between command directly. That way you get superb control over all the plotting elements you might want. Slightly less user friendly than the DataFrame.plot api, but still good.
Replace the line
ax = wind_data.plot.area(ax=plt.gca(), color =["saddlebrown", "limegreen"], stacked=False,drawstyle="steps-post")
with
ax=plt.gca()
for column,color in zip(wind_data.columns,['saddlebrown','limegreen']):
ax.fill_between(
x=wind_data.index,
y1=wind_data[column],
y2=0,
label=column,
color=color,
alpha=.5,
step='post',
linewidth=2,
)
and you're good.

How to change the font size of tick labels of a colorbar in Matplotlib?

I am creating a confusion matrix plot for my data. Next to the plot, I am placing a colorbar and want to change the font size of the colorbar tick labels. I search on the internet for a while but could not figure out how I can change the font size of the ticks of my colorbar since I am creating the colorbar using imshow. This could be because creating the colorbar this way is not the usual way as done/suggested in most places on the web (e.g. here and here). So I need your help for this. Here's how I'm creating my plot and add the colorbar next to it:
data=np.array([[0.83, 0.6, 0.76],[0.59, 0.46, 0.52],[0.62, 0.58, 0.88]])
xTicksMajor, yTicksMajor = [0.5, 1.5, 2.5], [0.5, 1.5, 2.5]
xTicksMinor, yTicksMinor = [0, 1, 2], [0, 1, 2]
fig, ax = plt.subplots()
cmapProp = {'drawedges': True, 'boundaries': np.linspace(0, 1, 13, endpoint=True).round(2)}
m = ax.imshow(data, cmap=plt.cm.get_cmap('Oranges'))
m.set_clim(0, 1)
ax.figure.colorbar(m, ax=ax, **cmapProp)
ax.set_xticks(xTicksMajor)
ax.set_yticks(yTicksMajor)
ax.set_xticks(xTicksMinor, minor=True)
ax.set_yticks(yTicksMinor, minor=True)
ax.yaxis.grid(True, color='black', linestyle='-', linewidth=0.5)
ax.xaxis.grid(True, color='black', linestyle='-', linewidth=0.5)
thresh = data.max() / 1.4
for i, j in itertools.product(range(data.shape[0]), range(data.shape[1])):
ax.text(j, i, format(data[i, j], '.2f'),
horizontalalignment="center",
verticalalignment='center',
color="black" if data[i, j] > thresh else "dimgrey",
fontsize=26)
fig.savefig('temp.png', dpi=200)
plt.close()
I tried changing the font size of the ticks as follow:
cmapProp = {'drawedges': True, 'boundaries': np.linspace(0, 1, 13, endpoint=True).round(2), 'fontsize': 14}
But this gives me the following error:
TypeError: init() got an unexpected keyword argument 'fontsize'
I wonder, how can I change the font size of the tick labels next to the colorbar? Feel free to make suggestions like creating the colorbar in a different way so that it is easy to change the fontsize.
Also, the above code results in the plot show below:
How about this:
...
fig, ax = plt.subplots()
cmapProp = {'drawedges': True, 'boundaries': np.linspace(0, 1, 13, endpoint=True).round(2)}
m = ax.imshow(data, cmap=plt.cm.get_cmap('Oranges'))
m.set_clim(0, 1)
# And here try this:
cbar = ax.figure.colorbar(m, ax=ax, **cmapProp)
cbar.ax.tick_params(labelsize=25) # set your label size here
...
Out:
bold labels:
...
cbar = ax.figure.colorbar(m, ax=ax, **cmapProp)
cbar.ax.tick_params(labelsize=25)
for tick in cbar.ax.yaxis.get_major_ticks():
tick.label2.set_fontweight('bold')
...
Out:

How to make trapezoid and parallelogram in python using matplotlib

I have tried the following to produce a regular polygon:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig2 = plt.figure()
ax2 = fig2.add_subplot(111, aspect='equal')
ax2.add_patch(
patches.RegularPolygon(
(0.5, 0.5),
3,
0.2,
fill=False # remove background
)
)
fig2.savefig('reg-polygon.png', dpi=90, bbox_inches='tight')
plt.show()
While this produces a triangle, I haven't found any way to produce a trapezoid and and a parallelogram.
Are there any commands to do this? Or can I transform the regular polygon into one of the other shapes?
You would need to use a matplotlib.patches.Polygon and define the corners yourself.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
# Parallelogram
x = [0.3,0.6,.7,.4]
y = [0.4,0.4,0.6,0.6]
ax.add_patch(patches.Polygon(xy=list(zip(x,y)), fill=False))
# Trapez
x = [0.3,0.6,.5,.4]
y = [0.7,0.7,0.9,0.9]
ax.add_patch(patches.Polygon(xy=list(zip(x,y)), fill=False))
plt.show()
For filled patches with size greater than 1 x 1
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
ax.set_xlim(0, 3)
ax.set_ylim(0, 3)
x = [0, 1.16, 2.74, 2, 0]
y = [0, 2.8, 2.8, 0, 0]
ax.add_patch(patches.Polygon(xy=list(zip(x,y)), fill=True))
x = [0.3,0.6,.5,.4]
y = [0.7,0.7,0.9,0.9]
ax.add_patch(patches.Polygon(xy=list(zip(x,y)), fill=True, color='magenta'))
One simple way to do it is creating a list of lists as the end points of the polygon( parallelogram/trapezoid) and plotting(or rather tracing) them.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig2 = plt.figure()
ax2 = fig2.add_subplot(111, aspect='equal')
points = [[0.2, 0.4], [0.4, 0.8], [0.8, 0.8], [0.6, 0.4], [0.2,0.4]] #the points to trace the edges.
polygon= plt.Polygon(points, fill=None, edgecolor='r')
ax2.add_patch(polygon)
fig2.savefig('reg-polygon.png', dpi=90, bbox_inches='tight')
plt.show()
Also, note that you should use Polygon instead of RegularPolygon.

Categories

Resources