Issue
I am generating a plot with a collection of wedges using matplotlib.patches.Wedge(). The wedges are different colors and with different angles subtended. I would like to include Wedge artists in a legend, but the following MWE is resulting in Line artists in the legend instead of Wedge artists.
legend() docs show examples for Line2D and Patch artists. How can I include Wedge artists in my legend?
import matplotlib.pylab as plt
from matplotlib.patches import Wedge
from matplotlib import rc
plt.rcParams['axes.facecolor']='#EEEEEE'
plt.rcParams['savefig.facecolor']='#EEEEEE'
colors = ['#e41a1c','#377eb8','#4daf4a','#984ea3']
fig1 = plt.figure(figsize=(4, 3))
ax = fig1.gca().axes
radius = 0.5
legend_elements = [Wedge((0, 1), radius, 0, 90, width=radius, color=colors[0], label='category1'),
Wedge((0, 1), radius, 0, 180, width=radius, color=colors[1], label='category2'),
Wedge((0, 1), radius, 0, 270, width=radius, color=colors[2], label='category3'),
Wedge((0, 1), radius, 0, 360, width=radius, color=colors[3], label='category4')]
ax.legend(handles = legend_elements, loc='lower center',
bbox_to_anchor= (0.5, 1.01), ncol=4,
borderaxespad=0, frameon=False)
fig1.show()
Solution
You'll need to create your own Handler
that tells matplotlib
how to take a specific Artist
and transform it to appear in a legend. Often times things need to be resized or transformed in some manner to appear better on a legend.
To create a handler, all you need to do is create any Python object that defines a legend_artist
method and has the same call signature as below see docs for more info.
From there we simply create a dummy Wedge
object whose radius is smaller than the box it's being drawn in (hence min(width, height)
). We update the aesthetic properties on this new dummy wedge based on the inputted Artists
.
Finally we add the newly created/updated dummy Wedge
to the handlebox
to be drawn later.
With the Handler
out of the way, we now supply a handler_map
to the call of ax.legend
. This is essentially a dictionary that maps an Artist
(in this case Wedge
) to the Handler
we just created, essentially tying the artist to the instructions on how to represent it in a legend.
import matplotlib.pylab as plt
from matplotlib.patches import Wedge
from matplotlib import rc
plt.rcParams['axes.facecolor']='#EEEEEE'
plt.rcParams['savefig.facecolor']='#EEEEEE'
class WedgeHandler:
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
x0, y0 = handlebox.xdescent, handlebox.ydescent
width, height = handlebox.width, handlebox.height
handle = Wedge((0, 0), min(width, height), orig_handle.theta1, orig_handle.theta2)
handle.update_from(orig_handle)
handlebox.add_artist(handle)
return handle
colors = ['#e41a1c','#377eb8','#4daf4a','#984ea3']
fig1 = plt.figure(figsize=(4, 3))
ax = fig1.gca().axes
radius = 0.5
legend_elements = [Wedge((0, 1), radius, 0, 90, width=radius, color=colors[0], label='category1'),
Wedge((0, 1), radius, 0, 180, width=radius, color=colors[1], label='category2'),
Wedge((0, 1), radius, 0, 270, width=radius, color=colors[2], label='category3'),
Wedge((0, 1), radius, 0, 360, width=radius, color=colors[3], label='category4')]
ax.legend(handles = legend_elements, loc='lower center',
bbox_to_anchor= (0.5, 1.05), ncol=4,
borderaxespad=0, frameon=False,
handler_map={Wedge: WedgeHandler()}
)
plt.show()
update, you may run into some issues if you use Artist.update_from
as it copies over a few too many properties. Instead you can manually specify which properties you want copied using this as an example:
import matplotlib.pylab as plt
from matplotlib.patches import Wedge
from matplotlib import rc
class WedgeHandler:
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
x0, y0 = handlebox.xdescent, handlebox.ydescent
width, height = handlebox.width, handlebox.height
r = min(width, height)
handle = Wedge(
center=(x0 + width / 2, y0 + height / 2), # centers handle in handlebox
r=r, # ensures radius fits in handlebox
width=r * (orig_handle.width / orig_handle.r), # preserves original radius/width ratio
theta1=orig_handle.theta1, # copies the following parameters
theta2=orig_handle.theta2,
color=orig_handle.get_facecolor(),
transform=handlebox.get_transform(), # use handlebox coordinate system
)
handlebox.add_artist(handle)
return handle
plt.rcParams['axes.facecolor']='#EEEEEE'
plt.rcParams['savefig.facecolor']='#EEEEEE'
colors = ['#e41a1c','#377eb8','#4daf4a','#984ea3']
theta2 = [90, 180, 270, 360]
fig, ax = plt.subplots()
for i, (color, t2) in enumerate(zip(colors, theta2), start=1):
wedge = Wedge((i, .5), 0.25, theta1=0, theta2=t2, width=0.25, color=color, label=f'category{i}')
ax.add_artist(wedge)
ax.set_xlim(1-.25, i + .25)
legend = ax.legend(title='Default Handler', loc='upper left', bbox_to_anchor=(1.01, 1))
ax.add_artist(legend)
ax.legend(title='Custom Handler', loc='upper left', bbox_to_anchor=(1.01, 0.5), handler_map={Wedge: WedgeHandler()})
plt.show()
Answered By - Cameron Riddell
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.