Adjust legend to several graphs (Pyplot) - python

There are several questions in StackOverflow regarding the position of the legend in Python's matplotlib.pyplot. For a single graph, the problem can often be solved by tweaking parameters location and bbox_to_anchor. In the following example, the legend can be placed above the graph with bbox_to_anchor = (0.5,1.3).
industry_capm_df.plot.bar
ax = plt.axes()
ax.legend(bbox_to_anchor=(0.5,1.3))
Yet, for this other graph, the same parameters (0.5,1.3) result in a legend slightly out of alignment.
industry_raw_df.plot.bar
ax = plt.axes()
ax.legend(bbox_to_anchor=(0.5,1.3))
Since I got several graphs to plot, I would like legend alignment to be automatic, without having to tweak bbox_to_anchor every time. How could I solve this?

You should add loc='lower left' to specify that the coordinates in bbox_to_anchor refer to the lower left corner of the legend:
ax.legend(loc='lower left', bbox_to_anchor=(0, 1.03), borderaxespad=0)
borderaxespad is to disable padding outside the box, so that the left edge is perfectly aligned with the axis. Then we add a bit of padding (0.03) so that the bottom edge is slightly above the top of the chart.
Reference: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.legend.html

Related

Matplotlib cuts off legend placed outside axes, ignoring bbox_extra_artists parameter in savefig

This question is similar to this one asked 6 years ago. However, their solution is not quite solving my problem; unlike their question, I am dealing with multiple legends, some of which are getting cut off.
I'm trying to create a figure containing multiple legends that sit outside the axes of my graph. I'm following the matplotlib documentation's instructions for creating multiple legends, using add_artist to add all but the final legend to my axes. I then use the bbox_extra_artists parameter in my savefig call as described in the above question to include all my legend objects. As seen in this example output image, the wider legend still gets cut off on the right side.
The code used to generate this plot:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
for x in [0, 1, 2]:
ax.bar(x+0.5, x+1, width=1, color=['red', 'blue', 'green'][x])
handle1 = plt.Line2D((0,1), (0,0), color='purple')
lgd1 = ax.legend([handle1], ['label1 is very long'], bbox_to_anchor=(1, 1))
ax.add_artist(lgd1)
handle2 = plt.Line2D((0,1), (0,0), color='orange')
lgd2 = ax.legend([handle2], ['label2'], bbox_to_anchor=(1, 0.9))
plt.savefig('output.png', bbox_extra_artists=(lgd1,lgd2), bbox_inches='tight')
Notably, if I change the order in which the legends are added (adding the wider legend first), the problem goes away and both legends are visible as seen here:
handle2 = plt.Line2D((0,1), (0,0), color='orange')
lgd2 = ax.legend([handle2], ['label2'], bbox_to_anchor=(1, 0.9))
ax.add_artist(lgd2)
handle1 = plt.Line2D((0,1), (0,0), color='purple')
lgd1 = ax.legend([handle1], ['label1 is very long'], bbox_to_anchor=(1, 1))
plt.savefig('output.png', bbox_extra_artists=(lgd1,lgd2), bbox_inches='tight')
For my actual project (which has to handle a dynamic number of legends), I've made it figure out which legend is "longest" and always add that legend last to work around this problem. However, this feels messy and it doesn't allow for adding more legends on other sides of the figure (e.g., I cannot add an x-axis legend below the graph without it getting cut off, since only one legend can be added "last").
Is this an intractable bug in matplotlib, or is there a tidy solution I'm missing?

Python - Remove borders from charts and legend

I have the following plot:
dfA.plot.bar(stacked=True, color=[colorDict.get(x, '#333333') for x in
dfA.columns],figsize=(10,8))
plt.legend(loc='upper right', bbox_to_anchor=(1.4, 1))
Which displays this:
I want to remove all of the borders of the chart and legend i.e. the box around the chart (leaving the axis numbers like 2015 and 6000 etc)
All of the examples I find refer to spines and 'ax', however I have not built my chart using fig = plt.figure() etc.
Anyone know how to do it?
You can remove the border of the legend by using the argument frameon=False in the call to plt.legend().
If you only have one figure and axes active, then you can use plt.gca() to get the current axes. Alternatively df.plot.bar returns an axes object (which I would suggest using because plt.gca() might get confusing when working with multiple figures). Therefore you can set the visibility of the spines to False:
ax = dfA.plot.bar(stacked=True, color=[colorDict.get(x, '#333333') for x in
dfA.columns],figsize=(10,8))
plt.legend(loc='upper right', bbox_to_anchor=(1.4, 1), frameon=False)
for spine in ax.spines:
ax.spines[spine].set_visible(False)
# Color of the spines can also be set to none, suggested in the comments by ScoutEU
# ax.spines[spine].set_color("None")

\mathrm mode for legend labels and legend props in matplotlib

So I am having a little alignment issue with my legend in matplotlib. Hopefully it is easily solvable with the right know-how. I have scoured the matplotlib website but I'm struggling to find the exact solution.
Essentially I have the following axis vertical spans with the following labels (note that I have used $\mathrm{}$ for these):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.axvspan(3851.1, 3951.1, color='gray', alpha=0.4, lw=1,label='$\mathrm{D}_{n}(4000)$')
ax.axvspan(4001.1, 4101.2, color='gray', alpha=0.4, lw=1)
ax.axvspan(4084.7, 4123.4, color='gray', alpha=0.8, lw=1,label='$\mathrm{H}\delta_{\mathrm{A}}$')
I also have the following legend label:
ax.legend(prop={'size':8}, loc=2)
Now the problem that I am seeing is this (for the image I have increased the prop size to 12 to show the issue but it scales down for size 8):
My issue is that the alignment is slightly off between the vspan regions and the math mode descriptive labels. Taking away the math mode solves the problem, but the labels do not contain the correct subscripts and greek lettering that I require. See here:
I was wondering if anyone knew of any alignment arguments for this niche scenario?

Cartopy subplot ticks & axes box line formatting

I'm using cartopy to plot several areas of very different sizes in different subplot arrangements (1x2, 3x4 etc.), which makes it quite difficult to find consistent layout parameters. One issue is that longitude tick labels are overlapping for small areas. Is there a way to rotate them? I'm creating the grid and ticks as follows:
gridlines = map.gridlines(crs=crs, draw_labels=True, linewidth=linewidth, color='black', alpha=1.0, linestyle=':', zorder=13)
The other issue is that by downscaling the Geoaxes in the subplot arrangement, the bounding box' line thickness appears very wide. Is there a way to set it explicitely? Here's the command I'm using to add each Geoaxes subplot:
map = fig.add_subplot(nrows, ncols, 1 + nth_col + (ncols * nth_row), projection=ccrs.Mercator())
Unfortunately, I don't think there is any control provided for either of these.
Regarding rotated ticks: With some care you can add axis ticks, and rotate those with usual "axes.set_ticklabels(... rotation=X)". But the gridline labels are not ticks, and you can't do this -- you can only control the position and formatting (via the exposed ticker and formatter objects).
Regarding the outline: again this does not appear to be the normal axes outline, and does not respond to the usual axes.set_frame_on() control.
I do find that "plt.gca().outline_path.set_linewidth" can be used. I guess this is useful but probably not a futureproof solution.

Preventing xticks from overlapping yticks

How can I prevent the labels of xticks from overlapping with the labels of yticks when using hist (or other plotting commands) in matplotlib?
There are several ways.
One is to use the tight_layout method of the figure you are drawing, which will automatically try to optimize the appareance of the labels.
fig, ax = subplots(1)
ax.plot(arange(10),rand(10))
fig.tight_layout()
An other way is to modify the rcParams values for the ticks formatting:
rcParams['xtick.major.pad'] = 6
This will draw the ticks a little farter from the axes. after modifying the rcparams (this of any other, you can find the complete list on your matplotlibrc configuration file), remember to set it back to deafult with the rcdefaults function.
A third way is to tamper with the axes locator_params telling it to not draw the label in the corner:
fig, ax = subplots(1)
ax.plot(arange(10),rand(10))
ax.locator_params(prune='lower',axis='both')
the axis keywords tell the locator on which axis it should work and the prune keyword tell it to remove the lowest value of the tick
Try increasing the padding between the ticks on the labels
import matplotlib
matplotlib.rcParams['xtick.major.pad'] = 8 # defaults are 4
matplotlib.rcParams['ytick.major.pad'] = 8
same goes for [x|y]tick.minor.pad.
Also, try setting: [x|y]tick.direction to 'out'. That gives you a little more room and helps makes the ticks a little more visible -- especially on histograms with dark bars.

Categories

Resources