Set legend symbol opacity with matplotlib? - python

I'm working on a plot with translucent 'x' markers (20% alpha). How do I make the marker appear at 100% opacity in the legend?
import matplotlib.pyplot as plt
plt.plot_date( x = xaxis, y = yaxis, marker = 'x', color=[1, 0, 0, .2], label='Data Series' )
plt.legend(loc=3, mode="expand", numpoints=1, scatterpoints=1 )

UPDATED:
There is an easier way! First, assign your legend to a variable when you create it:
leg = plt.legend()
Then:
for lh in leg.legendHandles:
lh.set_alpha(1)
OR if the above doesn't work (you may be using an older version of matplotlib):
for lh in leg.legendHandles:
lh._legmarker.set_alpha(1)
to make your markers opaque for a plt.plot or a plt.scatter, respectively.
Note that using simply lh.set_alpha(1) on a plt.plot will make the lines in your legend opaque rather than the markers. You should be able to adapt these two possibilities for the other plot types.
Sources:
Synthesized from some good advice by DrV about marker sizes. Update was inspired by useful comment from Owen.

Following up on cosmosis's answer, to make the "fake" lines for the legend invisible on the plot, you can use NaNs, and they will still work for generating legend entries:
import numpy as np
import matplotlib.pyplot as plt
# Plot data with alpha=0.2
plt.plot((0,1), (0,1), marker = 'x', color=[1, 0, 0, .2])
# Plot non-displayed NaN line for legend, leave alpha at default of 1.0
legend_line_1 = plt.plot( np.NaN, np.NaN, marker = 'x', color=[1, 0, 0], label='Data Series' )
plt.legend()

Other answers here give good practical solutions by either changing the alpha value in the legend after creation, or changing the alpha of the line after legend creation.
A solution to achieve a different opacity in the legend without manipulating anything afterwards would be the following. It uses a handler_map and an updating function.
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(43)
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerPathCollection, HandlerLine2D
plt.plot(np.linspace(0,1,8), np.random.rand(8), marker="o", markersize=12, label="A line", alpha=0.2)
plt.scatter(np.random.rand(8),np.random.rand(8), s=144,
c="red", marker=r"$\clubsuit$", label="A scatter", alpha=0.2)
def update(handle, orig):
handle.update_from(orig)
handle.set_alpha(1)
plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func= update),
plt.Line2D : HandlerLine2D(update_func = update)})
plt.show()

If you want to have something specific in your legend, it's easier to define objects that you place in the legend with appropriate text. For example:
import matplotlib.pyplot as plt
import pylab
plt.plot_date( x = xaxis, y = yaxis, marker = 'x', color=[1, 0, 0, .2], label='Data Series' )
line1 = pylab.Line2D(range(1),range(1),color='white',marker='x',markersize=10, markerfacecolor="red",alpha=1.0)
line2 = pylab.Line2D(range(10),range(10),marker="_",linewidth=3.0,color="dodgerblue",alpha=1.0)
plt.legend((line1,line2),('Text','Other Text'),numpoints=1,loc=1)
Here, line1 defines a short, white line (so essentially invisible) with the marker 'x' in red and full opacity. As an example, line2 gives you a longer blue line with no markers visible. By creating this "lines," you are able to more easily control their properties within the legend.

It looks like matplotlib draws the plot lines after it copies the alpha level to the legend. That means that you can create the plot lines with the alpha level that you want in the legend, create the legend to copy that alpha level, then change the alpha level on the plot lines.
Here's a complete example:
import matplotlib.pyplot as plt
x = (0, 1, 2)
y = (0, 2, 1)
line, = plt.plot(x, y, 'ro', label='label') # Default alpha is 1.0.
plt.legend() # Copy alpha to legend.
line.set_alpha(0.2) # Change alpha for data points.
plt.show()
That plot looks like this when I run it with matplotlib 2.2.3 on Python 2.7.15:

I've found that the .set_alpha() function works on many legend objects, but unfortunately, many legend objects have several pieces (such as the output of errorbar()) and the .set_alpha() call will only affect one of them.
One can use .get_legend_handles_labels() and then loop through parts of the handles and .set_alpha(), but unfortunately, copy.deepcopy() does not seem to work on the list of handles, so the plot itself will be affected. The best workaround I could find was to save the original alphas, .set_alpha() to what I wanted, create the legend, then reset the plot alphas back to their original values. It would be much cleaner if I could deepcopy handles (I wouldn't have to save alpha values or reset them), but I could not do this in python2.7 (maybe this depends on what objects are in the legend).
f,ax=plt.subplots(1)
ax.plot( ... )
def legend_alpha(ax,newalpha=1.0):
#sets alpha of legends to some value
#this would be easier if deepcopy worked on handles, but it doesn't
handles,labels=ax.get_legend_handles_labels()
alphass=[None]*len(handles) #make a list to hold lists of saved alpha values
for k,handle in enumerate(handles): #loop through the legend entries
alphas=[None]*len(handle) #make a list to hold the alphas of the pieces of this legend entry
for i,h in enumerate(handle): #loop through the pieces of this legend entry (there could be a line and a marker, for example)
try: #if handle was a simple list of parts, then this will work
alphas[i]=h.get_alpha()
h.set_alpha(newalpha)
except: #if handle was a list of parts which themselves were made up of smaller subcomponents, then we must go one level deeper still.
#this was needed for the output of errorbar() and may not be needed for simpler plot objects
alph=[None]*len(h)
for j,hh in enumerate(h):
alph[j]=hh.get_alpha() #read the alpha values of the sub-components of the piece of this legend entry
hh.set_alpha(newalpha)
alphas[i]=alph #save the list of alpha values for the subcomponents of this piece of this legend entry
alphass[k]=alphas #save the list of alpha values for the pieces of this legend entry
leg=ax.legend(handles,labels) #create the legend while handles has updated alpha values
for k,handle in enumerate(handles): #loop through legend items to restore origina alphas on the plot
for i,h in enumerate(handle): #loop through pieces of this legend item to restore alpha values on the plot
try:
h.set_alpha(alphass[k][i])
except:
for j,hh in enumerate(h): #loop through sub-components of this piece of this legend item to restore alpha values
hh.set_alpha(alphass[k][i][j])
return leg
leg=legend_alpha(ax)
leg.draggable()

In my case, set_alpha(1) also modified the edgecolors, which I didn't want: I had "invisible" edges, and setting alpha to opaque made them visible in the legend. The following snippet (OOP) changes the opacity of the face without changing the border color:
leg = ax.legend()
for lh in leg.legendHandles:
fc_arr = lh.get_fc().copy()
fc_arr[:, -1] = 1 # set opacity here
lh.set_fc(fc_arr)
Note the call to .copy(), if we don't do this it will modify the opacity for the whole plot. Calling copy means we are only modifying the facecolor inside the legend box.
Alternatively, you can add this function to your library:
def opaque_legend(ax):
"""
Calls legend, and sets all the legend colors opacity to 100%.
Returns the legend handle.
"""
leg = ax.legend()
for lh in leg.legendHandles:
fc_arr = lh.get_fc().copy()
fc_arr[:, -1] = 1
lh.set_fc(fc_arr)
return leg
And then simply replace leg = ax.legend() with leg = opaque_legend(ax). Hope this helps!
Andres

Instead of messing up with the opacity of the legend, I found another way. Firstly, I create a plot line with the style I want the legend to be. Then I change the plot line style, and, miraculously, the legend style remains intact. MWE:
plt.plot(x, y, 'ro', label='label')
for lh in plt.gca().get_legend_handles_labels():
lh[0].set_alpha(new_alpha)
I'd like to explain, why it works, but I can't. Neither I'm sure that it works for all backends.
And yes, I know that the question is old. As it still appears in Google, I'll find it later and help my future self.

Related

Matplotlib, 'Figure' object has no attribute 'figlegend' [duplicate]

I am plotting the same type of information, but for different countries, with multiple subplots with Matplotlib. That is, I have nine plots on a 3x3 grid, all with the same for lines (of course, different values per line).
However, I have not figured out how to put a single legend (since all nine subplots have the same lines) on the figure just once.
How do I do that?
There is also a nice function get_legend_handles_labels() you can call on the last axis (if you iterate over them) that would collect everything you need from label= arguments:
handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')
figlegend may be what you're looking for: matplotlib.pyplot.figlegend
An example is at Figure legend demo.
Another example:
plt.figlegend(lines, labels, loc = 'lower center', ncol=5, labelspacing=0.)
Or:
fig.legend(lines, labels, loc = (0.5, 0), ncol=5)
TL;DR
lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]
fig.legend(lines, labels)
I have noticed that none of the other answers displays an image with a single legend referencing many curves in different subplots, so I have to show you one... to make you curious...
Now, if I've teased you enough, here it is the code
from numpy import linspace
import matplotlib.pyplot as plt
# each Axes has a brand new prop_cycle, so to have differently
# colored curves in different Axes, we need our own prop_cycle
# Note: we CALL the axes.prop_cycle to get an itertoools.cycle
color_cycle = plt.rcParams['axes.prop_cycle']()
# I need some curves to plot
x = linspace(0, 1, 51)
functs = [x*(1-x), x**2*(1-x),
0.25-x*(1-x), 0.25-x**2*(1-x)]
labels = ['$x-x²$', '$x²-x³$',
'$\\frac{1}{4} - (x-x²)$', '$\\frac{1}{4} - (x²-x³)$']
# the plot,
fig, (a1,a2) = plt.subplots(2)
for ax, f, l, cc in zip((a1,a1,a2,a2), functs, labels, color_cycle):
ax.plot(x, f, label=l, **cc)
ax.set_aspect(2) # superfluos, but nice
# So far, nothing special except the managed prop_cycle. Now the trick:
lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]
# Finally, the legend (that maybe you'll customize differently)
fig.legend(lines, labels, loc='upper center', ncol=4)
plt.show()
If you want to stick with the official Matplotlib API, this is
perfect, otherwise see note no.1 below (there is a private
method...)
The two lines
lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]
deserve an explanation, see note 2 below.
I tried the method proposed by the most up-voted and accepted answer,
# fig.legend(lines, labels, loc='upper center', ncol=4)
fig.legend(*a2.get_legend_handles_labels(),
loc='upper center', ncol=4)
and this is what I've got
Note 1
If you don't mind using a private method of the matplotlib.legend module ... it's really much much much easier
from matplotlib.legend import _get_legend_handles_labels
...
fig.legend(*_get_legend_handles_and_labels(fig.axes), ...)
Note 2
I have encapsulated the two tricky lines in a function, just four lines of code, but heavily commented
def fig_legend(fig, **kwdargs):
# Generate a sequence of tuples, each contains
# - a list of handles (lohand) and
# - a list of labels (lolbl)
tuples_lohand_lolbl = (ax.get_legend_handles_labels() for ax in fig.axes)
# E.g., a figure with two axes, ax0 with two curves, ax1 with one curve
# yields: ([ax0h0, ax0h1], [ax0l0, ax0l1]) and ([ax1h0], [ax1l0])
# The legend needs a list of handles and a list of labels,
# so our first step is to transpose our data,
# generating two tuples of lists of homogeneous stuff(tolohs), i.e.,
# we yield ([ax0h0, ax0h1], [ax1h0]) and ([ax0l0, ax0l1], [ax1l0])
tolohs = zip(*tuples_lohand_lolbl)
# Finally, we need to concatenate the individual lists in the two
# lists of lists: [ax0h0, ax0h1, ax1h0] and [ax0l0, ax0l1, ax1l0]
# a possible solution is to sum the sublists - we use unpacking
handles, labels = (sum(list_of_lists, []) for list_of_lists in tolohs)
# Call fig.legend with the keyword arguments, return the legend object
return fig.legend(handles, labels, **kwdargs)
I recognize that sum(list_of_lists, []) is a really inefficient method to flatten a list of lists, but ① I love its compactness, ② usually is a few curves in a few subplots and ③ Matplotlib and efficiency? ;-)
For the automatic positioning of a single legend in a figure with many axes, like those obtained with subplots(), the following solution works really well:
plt.legend(lines, labels, loc = 'lower center', bbox_to_anchor = (0, -0.1, 1, 1),
bbox_transform = plt.gcf().transFigure)
With bbox_to_anchor and bbox_transform=plt.gcf().transFigure, you are defining a new bounding box of the size of your figureto be a reference for loc. Using (0, -0.1, 1, 1) moves this bounding box slightly downwards to prevent the legend to be placed over other artists.
OBS: Use this solution after you use fig.set_size_inches() and before you use fig.tight_layout()
You just have to ask for the legend once, outside of your loop.
For example, in this case I have 4 subplots, with the same lines, and a single legend.
from matplotlib.pyplot import *
ficheiros = ['120318.nc', '120319.nc', '120320.nc', '120321.nc']
fig = figure()
fig.suptitle('concentration profile analysis')
for a in range(len(ficheiros)):
# dados is here defined
level = dados.variables['level'][:]
ax = fig.add_subplot(2,2,a+1)
xticks(range(8), ['0h','3h','6h','9h','12h','15h','18h','21h'])
ax.set_xlabel('time (hours)')
ax.set_ylabel('CONC ($\mu g. m^{-3}$)')
for index in range(len(level)):
conc = dados.variables['CONC'][4:12,index] * 1e9
ax.plot(conc,label=str(level[index])+'m')
dados.close()
ax.legend(bbox_to_anchor=(1.05, 0), loc='lower left', borderaxespad=0.)
# it will place the legend on the outer right-hand side of the last axes
show()
If you are using subplots with bar charts, with a different colour for each bar, it may be faster to create the artefacts yourself using mpatches.
Say you have four bars with different colours as r, m, c, and k, you can set the legend as follows:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
labels = ['Red Bar', 'Magenta Bar', 'Cyan Bar', 'Black Bar']
#####################################
# Insert code for the subplots here #
#####################################
# Now, create an artist for each color
red_patch = mpatches.Patch(facecolor='r', edgecolor='#000000') # This will create a red bar with black borders, you can leave out edgecolor if you do not want the borders
black_patch = mpatches.Patch(facecolor='k', edgecolor='#000000')
magenta_patch = mpatches.Patch(facecolor='m', edgecolor='#000000')
cyan_patch = mpatches.Patch(facecolor='c', edgecolor='#000000')
fig.legend(handles = [red_patch, magenta_patch, cyan_patch, black_patch], labels=labels,
loc="center right",
borderaxespad=0.1)
plt.subplots_adjust(right=0.85) # Adjust the subplot to the right for the legend
To build on top of gboffi's and Ben Usman's answer:
In a situation where one has different lines in different subplots with the same color and label, one can do something along the lines of:
labels_handles = {
label: handle for ax in fig.axes for handle, label in zip(*ax.get_legend_handles_labels())
}
fig.legend(
labels_handles.values(),
labels_handles.keys(),
loc = "upper center",
bbox_to_anchor = (0.5, 0),
bbox_transform = plt.gcf().transFigure,
)
Using Matplotlib 2.2.2, this can be achieved using the gridspec feature.
In the example below, the aim is to have four subplots arranged in a 2x2 fashion with the legend shown at the bottom. A 'faux' axis is created at the bottom to place the legend in a fixed spot. The 'faux' axis is then turned off so only the legend shows. Result:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# Gridspec demo
fig = plt.figure()
fig.set_size_inches(8, 9)
fig.set_dpi(100)
rows = 17 # The larger the number here, the smaller the spacing around the legend
start1 = 0
end1 = int((rows-1)/2)
start2 = end1
end2 = int(rows-1)
gspec = gridspec.GridSpec(ncols=4, nrows=rows)
axes = []
axes.append(fig.add_subplot(gspec[start1:end1, 0:2]))
axes.append(fig.add_subplot(gspec[start2:end2, 0:2]))
axes.append(fig.add_subplot(gspec[start1:end1, 2:4]))
axes.append(fig.add_subplot(gspec[start2:end2, 2:4]))
axes.append(fig.add_subplot(gspec[end2, 0:4]))
line, = axes[0].plot([0, 1], [0, 1], 'b') # Add some data
axes[-1].legend((line,), ('Test',), loc='center') # Create legend on bottommost axis
axes[-1].set_axis_off() # Don't show the bottom-most axis
fig.tight_layout()
plt.show()
This answer is a complement to user707650's answer on the legend position.
My first try on user707650's solution failed due to overlaps of the legend and the subplot's title.
In fact, the overlaps are caused by fig.tight_layout(), which changes the subplots' layout without considering the figure legend. However, fig.tight_layout() is necessary.
In order to avoid the overlaps, we can tell fig.tight_layout() to leave spaces for the figure's legend by fig.tight_layout(rect=(0,0,1,0.9)).
Description of tight_layout() parameters.
All of the previous answers are way over my head, at this state of my coding journey, so I just added another Matplotlib aspect called patches:
import matplotlib.patches as mpatches
first_leg = mpatches.Patch(color='red', label='1st plot')
second_leg = mpatches.Patch(color='blue', label='2nd plot')
thrid_leg = mpatches.Patch(color='green', label='3rd plot')
plt.legend(handles=[first_leg ,second_leg ,thrid_leg ])
The patches aspect put all the data i needed on my final plot (it was a line plot that combined three different line plots all in the same cell in Jupyter Notebook).
Result
(I changed the names form what I named my own legend.)

Combine two matplotlib markers into one in legend

To have a custom marker, I made two scatter plots with same data points but different markers. Thus by plotting one marker on top of the other I get the look of a new custom marker. Now I want to use it in legend. Is there a way I can use two markers one on top of the other in legend and show them as a single marker.
Edit:
The question is not regarding how to share the same label for two different markers, but how to plot one marker on top of other in the legend
Using a tuple of markers answers the question;
from numpy.random import randn
m=np.random.uniform(size=10)
x=np.arange(0,10,1)
y=x**2
fig, ax = plt.subplots(1,1)
blue_dot = ax.scatter(x[:5],y[:5], s=m*100, color='b')
red_dot = ax.scatter(x[5:],y[5:], s=200*m, color='r')
black_cross = ax.scatter(x[5:],y[5:], s=400*m, marker='+', color='k')
lgnd = ax.legend([blue_dot, (red_dot, black_cross)], ["Blue Circle", "Red Circle and Black Cross"])
Now I want to change the size of the markers in the legend so that all the markers of equal size. For that, I have tried adding this to above code.
lgnd.legendHandles[0]._sizes = [200]
lgnd.legendHandles[1]._sizes = [200] # this is affecting the size of red_dot only
How do I change the size of black_cross as well in the legend?

Limiting the size of legend in MatPlotLib in python, then allowing scrolling within the legend

I would like to limit the size of the legend in MatPlotLib to scale with a figure. After this, I would like to enable scrolling within the legend to see any cut off data. This is regarding two legends, each corresponding to a subplot. A picture is attached to show the exact setup:
In this image, you can see that the legends are overlapping each other, as well as being cut off by the bottom of the frame.
Here is the python code used to obtain the figure:
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
fig, axes = plt.subplots(2, figsize=(9,6))
x = [i for i in range(100)]
y_data = []
for i in range(1,15):
temp = []
for j in x:
temp.append(i * j)
y_data.append(temp)
for line in y_data:
axes[0].plot(x, line, '.')
axes[1].plot(x, line, '.')
axes[0].legend(x, bbox_to_anchor=(1.02, 0, 0.07, 1))
axes[1].legend(x, bbox_to_anchor=(1.02, 0, 0.07, 1))
plt.show()
I would like to modify this code so that the legend is smaller, and so that if there are a great amount of lines in the legend, the data that is not within the confines of the legend can be scrolled to.
I attempted to use the ideas here: Fix size of legend in matplotlib, but it did not seem to scale with two subplots. Setting the height and width in bbox_to_anchor also did not seem to constrain the legend - only move it.
How can I accomplish this?

matplotlib legend with alpha interaction [duplicate]

I'm working on a plot with translucent 'x' markers (20% alpha). How do I make the marker appear at 100% opacity in the legend?
import matplotlib.pyplot as plt
plt.plot_date( x = xaxis, y = yaxis, marker = 'x', color=[1, 0, 0, .2], label='Data Series' )
plt.legend(loc=3, mode="expand", numpoints=1, scatterpoints=1 )
UPDATED:
There is an easier way! First, assign your legend to a variable when you create it:
leg = plt.legend()
Then:
for lh in leg.legendHandles:
lh.set_alpha(1)
OR if the above doesn't work (you may be using an older version of matplotlib):
for lh in leg.legendHandles:
lh._legmarker.set_alpha(1)
to make your markers opaque for a plt.plot or a plt.scatter, respectively.
Note that using simply lh.set_alpha(1) on a plt.plot will make the lines in your legend opaque rather than the markers. You should be able to adapt these two possibilities for the other plot types.
Sources:
Synthesized from some good advice by DrV about marker sizes. Update was inspired by useful comment from Owen.
Following up on cosmosis's answer, to make the "fake" lines for the legend invisible on the plot, you can use NaNs, and they will still work for generating legend entries:
import numpy as np
import matplotlib.pyplot as plt
# Plot data with alpha=0.2
plt.plot((0,1), (0,1), marker = 'x', color=[1, 0, 0, .2])
# Plot non-displayed NaN line for legend, leave alpha at default of 1.0
legend_line_1 = plt.plot( np.NaN, np.NaN, marker = 'x', color=[1, 0, 0], label='Data Series' )
plt.legend()
Other answers here give good practical solutions by either changing the alpha value in the legend after creation, or changing the alpha of the line after legend creation.
A solution to achieve a different opacity in the legend without manipulating anything afterwards would be the following. It uses a handler_map and an updating function.
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(43)
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerPathCollection, HandlerLine2D
plt.plot(np.linspace(0,1,8), np.random.rand(8), marker="o", markersize=12, label="A line", alpha=0.2)
plt.scatter(np.random.rand(8),np.random.rand(8), s=144,
c="red", marker=r"$\clubsuit$", label="A scatter", alpha=0.2)
def update(handle, orig):
handle.update_from(orig)
handle.set_alpha(1)
plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func= update),
plt.Line2D : HandlerLine2D(update_func = update)})
plt.show()
If you want to have something specific in your legend, it's easier to define objects that you place in the legend with appropriate text. For example:
import matplotlib.pyplot as plt
import pylab
plt.plot_date( x = xaxis, y = yaxis, marker = 'x', color=[1, 0, 0, .2], label='Data Series' )
line1 = pylab.Line2D(range(1),range(1),color='white',marker='x',markersize=10, markerfacecolor="red",alpha=1.0)
line2 = pylab.Line2D(range(10),range(10),marker="_",linewidth=3.0,color="dodgerblue",alpha=1.0)
plt.legend((line1,line2),('Text','Other Text'),numpoints=1,loc=1)
Here, line1 defines a short, white line (so essentially invisible) with the marker 'x' in red and full opacity. As an example, line2 gives you a longer blue line with no markers visible. By creating this "lines," you are able to more easily control their properties within the legend.
It looks like matplotlib draws the plot lines after it copies the alpha level to the legend. That means that you can create the plot lines with the alpha level that you want in the legend, create the legend to copy that alpha level, then change the alpha level on the plot lines.
Here's a complete example:
import matplotlib.pyplot as plt
x = (0, 1, 2)
y = (0, 2, 1)
line, = plt.plot(x, y, 'ro', label='label') # Default alpha is 1.0.
plt.legend() # Copy alpha to legend.
line.set_alpha(0.2) # Change alpha for data points.
plt.show()
That plot looks like this when I run it with matplotlib 2.2.3 on Python 2.7.15:
I've found that the .set_alpha() function works on many legend objects, but unfortunately, many legend objects have several pieces (such as the output of errorbar()) and the .set_alpha() call will only affect one of them.
One can use .get_legend_handles_labels() and then loop through parts of the handles and .set_alpha(), but unfortunately, copy.deepcopy() does not seem to work on the list of handles, so the plot itself will be affected. The best workaround I could find was to save the original alphas, .set_alpha() to what I wanted, create the legend, then reset the plot alphas back to their original values. It would be much cleaner if I could deepcopy handles (I wouldn't have to save alpha values or reset them), but I could not do this in python2.7 (maybe this depends on what objects are in the legend).
f,ax=plt.subplots(1)
ax.plot( ... )
def legend_alpha(ax,newalpha=1.0):
#sets alpha of legends to some value
#this would be easier if deepcopy worked on handles, but it doesn't
handles,labels=ax.get_legend_handles_labels()
alphass=[None]*len(handles) #make a list to hold lists of saved alpha values
for k,handle in enumerate(handles): #loop through the legend entries
alphas=[None]*len(handle) #make a list to hold the alphas of the pieces of this legend entry
for i,h in enumerate(handle): #loop through the pieces of this legend entry (there could be a line and a marker, for example)
try: #if handle was a simple list of parts, then this will work
alphas[i]=h.get_alpha()
h.set_alpha(newalpha)
except: #if handle was a list of parts which themselves were made up of smaller subcomponents, then we must go one level deeper still.
#this was needed for the output of errorbar() and may not be needed for simpler plot objects
alph=[None]*len(h)
for j,hh in enumerate(h):
alph[j]=hh.get_alpha() #read the alpha values of the sub-components of the piece of this legend entry
hh.set_alpha(newalpha)
alphas[i]=alph #save the list of alpha values for the subcomponents of this piece of this legend entry
alphass[k]=alphas #save the list of alpha values for the pieces of this legend entry
leg=ax.legend(handles,labels) #create the legend while handles has updated alpha values
for k,handle in enumerate(handles): #loop through legend items to restore origina alphas on the plot
for i,h in enumerate(handle): #loop through pieces of this legend item to restore alpha values on the plot
try:
h.set_alpha(alphass[k][i])
except:
for j,hh in enumerate(h): #loop through sub-components of this piece of this legend item to restore alpha values
hh.set_alpha(alphass[k][i][j])
return leg
leg=legend_alpha(ax)
leg.draggable()
In my case, set_alpha(1) also modified the edgecolors, which I didn't want: I had "invisible" edges, and setting alpha to opaque made them visible in the legend. The following snippet (OOP) changes the opacity of the face without changing the border color:
leg = ax.legend()
for lh in leg.legendHandles:
fc_arr = lh.get_fc().copy()
fc_arr[:, -1] = 1 # set opacity here
lh.set_fc(fc_arr)
Note the call to .copy(), if we don't do this it will modify the opacity for the whole plot. Calling copy means we are only modifying the facecolor inside the legend box.
Alternatively, you can add this function to your library:
def opaque_legend(ax):
"""
Calls legend, and sets all the legend colors opacity to 100%.
Returns the legend handle.
"""
leg = ax.legend()
for lh in leg.legendHandles:
fc_arr = lh.get_fc().copy()
fc_arr[:, -1] = 1
lh.set_fc(fc_arr)
return leg
And then simply replace leg = ax.legend() with leg = opaque_legend(ax). Hope this helps!
Andres
Instead of messing up with the opacity of the legend, I found another way. Firstly, I create a plot line with the style I want the legend to be. Then I change the plot line style, and, miraculously, the legend style remains intact. MWE:
plt.plot(x, y, 'ro', label='label')
for lh in plt.gca().get_legend_handles_labels():
lh[0].set_alpha(new_alpha)
I'd like to explain, why it works, but I can't. Neither I'm sure that it works for all backends.
And yes, I know that the question is old. As it still appears in Google, I'll find it later and help my future self.

automatically position text box in matplotlib

Is there a way of telling pyplot.text() a location like you can with pyplot.legend()?
Something like the legend argument would be excellent:
plt.legend(loc="upper left")
I am trying to label subplots with different axes using letters (e.g. "A","B"). I figure there's got to be a better way than manually estimating the position.
Thanks
Just use annotate and specify axis coordinates. For example, "upper left" would be:
plt.annotate('Something', xy=(0.05, 0.95), xycoords='axes fraction')
You could also get fancier and specify a constant offset in points:
plt.annotate('Something', xy=(0, 1), xytext=(12, -12), va='top'
xycoords='axes fraction', textcoords='offset points')
For more explanation see the examples here and the more detailed examples here.
I'm not sure if this was available when I originally posted the question but using the loc parameter can now actually be used. Below is an example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnchoredText
# make some data
x = np.arange(10)
y = x
# set up figure and axes
f, ax = plt.subplots(1,1)
# loc works the same as it does with figures (though best doesn't work)
# pad=5 will increase the size of padding between the border and text
# borderpad=5 will increase the distance between the border and the axes
# frameon=False will remove the box around the text
anchored_text = AnchoredText("Test", loc=2)
ax.plot(x,y)
ax.add_artist(anchored_text)
plt.show()
The question is quite old but as there is no general solution to the problem till now (2019) according to Add loc=best kwarg to pyplot.text(), I'm using legend() and the following workaround to obtain auto-placement for simple text boxes:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpl_patches
x = np.linspace(-1,1)
fig, ax = plt.subplots()
ax.plot(x, x*x)
# create a list with two empty handles (or more if needed)
handles = [mpl_patches.Rectangle((0, 0), 1, 1, fc="white", ec="white",
lw=0, alpha=0)] * 2
# create the corresponding number of labels (= the text you want to display)
labels = []
labels.append("pi = {0:.4g}".format(np.pi))
labels.append("root(2) = {0:.4g}".format(np.sqrt(2)))
# create the legend, supressing the blank space of the empty line symbol and the
# padding between symbol and label by setting handlelenght and handletextpad
ax.legend(handles, labels, loc='best', fontsize='small',
fancybox=True, framealpha=0.7,
handlelength=0, handletextpad=0)
plt.show()
The general idea is to create a legend with a blank line symbol and to remove the resulting empty space afterwards. How to adjust the size of matplotlib legend box? helped me with the legend formatting.

Categories

Resources