Circle patch as matplotlib legend marker [duplicate] - python

I'm using the following code to create a custom matplotlib legend.
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
colors = ["g", "w"]
texts = ["Green Data Description", "RedData Description"]
patches = [ mpatches.Patch(color=colors[i], label="{:s}".format(texts[i]) ) for i in range(len(texts)) ]
plt.legend(handles=patches, bbox_to_anchor=(0.5, 0.5), loc='center', ncol=2 )
The resulted legend is as follows:
1 - The white symbol in the legend is not shown because that the default legend background is white as well. How can I set the legend background to other color ?
2 - How to change the rectangular symbols in the legend into circular shape ?

Try this:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as mpatches
from matplotlib.legend_handler import HandlerPatch
colors = ["g", "w"]
texts = ["Green Data Description", "RedData Description"]
class HandlerEllipse(HandlerPatch):
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize, trans):
center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
p = mpatches.Ellipse(xy=center, width=width + xdescent,
height=height + ydescent)
self.update_prop(p, orig_handle, legend)
p.set_transform(trans)
return [p]
c = [ mpatches.Circle((0.5, 0.5), 1, facecolor=colors[i], linewidth=3) for i in range(len(texts))]
plt.legend(c,texts,bbox_to_anchor=(0.5, 0.5), loc='center', ncol=2, handler_map={mpatches.Circle: HandlerEllipse()}).get_frame().set_facecolor('#00FFCC')
plt.show()
output:
Update:
To circle, set width equals to height, in mpatches.Ellipse
Remove the outer black line, set edgecolor="none" in mpatches.Circle
code:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as mpatches
from matplotlib.legend_handler import HandlerPatch
colors = ["g", "w"]
texts = ["Green Data Description", "RedData Description"]
class HandlerEllipse(HandlerPatch):
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize, trans):
center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
p = mpatches.Ellipse(xy=center, width=height + xdescent,
height=height + ydescent)
self.update_prop(p, orig_handle, legend)
p.set_transform(trans)
return [p]
c = [ mpatches.Circle((0.5, 0.5), radius = 0.25, facecolor=colors[i], edgecolor="none" ) for i in range(len(texts))]
plt.legend(c,texts,bbox_to_anchor=(0.5, 0.5), loc='center', ncol=2, handler_map={mpatches.Circle: HandlerEllipse()}).get_frame().set_facecolor('#00FFCC')
plt.show()
New Picture:

Setting the legend's background color can be done using the facecolor argument to plt.legend(),
plt.legend(facecolor="plum")
To obtain a circular shaped legend handle, you may use a standard plot with a circular marker as proxy artist,
plt.plot([],[], marker="o", ms=10, ls="")
Complete example:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
colors = ["g", "w"]
texts = ["Green Data Description", "RedData Description"]
patches = [ plt.plot([],[], marker="o", ms=10, ls="", mec=None, color=colors[i],
label="{:s}".format(texts[i]) )[0] for i in range(len(texts)) ]
plt.legend(handles=patches, bbox_to_anchor=(0.5, 0.5),
loc='center', ncol=2, facecolor="plum", numpoints=1 )
plt.show()
(Note that mec and numpoints arguments are only required for older versions of matplotlib)
For more complicated shapes in the legend, you may use a custom handler map, see the legend guide or e.g. this answer as an example

As the other answers did not work for me, I am adding an answer that is super simple and straight forward:
import matplotlib.pyplot as plt
handles = []
for x in colors:
handles.append(plt.Line2D([], [], color=x, marker="o", linewidth=0))
You can adjust marker size and whatever else you want (maybe a star etc) and the linewidth removes the line to leave you with only the marker. Works perfectly and is super simple!
Even simpler:
import matplotlib.pyplot as plt
handles = [(plt.Line2D([], [], color=x, marker="o", linewidth=0)) for x in colors]

Related

Matplotlib, plot a vector of numbers as a rectangle filled with numbers

So let's say I have a vector of numbers.
np.random.randn(5).round(2).tolist()
[2.05, -1.57, 1.07, 1.37, 0.32]
I want a draw a rectangle that shows this elements as numbers in a rectangle.
Something like this:
Is there an easy way to do this in matplotlib?
A bit convoluted but you could take advantage of seaborn.heatmap, creating a white colormap:
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
data = np.random.randn(5).round(2).tolist()
linewidth = 2
ax = sns.heatmap([data], annot=True, cmap=LinearSegmentedColormap.from_list('', ['w', 'w'], N=1),
linewidths=linewidth, linecolor='black', square=True,
cbar=False, xticklabels=False, yticklabels=False)
plt.tight_layout()
plt.show()
In this case, the external lines won't be as thick as the internal ones. If needed, this can be fixed with:
ax.axhline(y=0, color='black', lw=linewidth*2)
ax.axhline(y=1, color='black', lw=linewidth*2)
ax.axvline(x=0, color='black', lw=linewidth*2)
ax.axvline(x=len(data), color='black', lw=linewidth*2)
Edit: avoid these lines and add clip_on=False to sns.heatmap (thanks/credit #JohanC)
Output:
We can add rectangles , and annotate them in a for loop.
from matplotlib import pyplot as plt
import numpy as np
# Our numbers
nums = np.random.randn(5).round(2).tolist()
# rectangle_size
rectangle_size = 2
# We want rectangles look squared, you can change if you want
plt.rcParams["figure.figsize"] = [rectangle_size * len(nums), rectangle_size]
plt.rcParams["figure.autolayout"] = True
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(len(nums)):
# We are adding rectangles
# You can change colors as you wish
plt.broken_barh([(rectangle_size * i, rectangle_size)], (0, rectangle_size), facecolors='white', edgecolor='black'
,linewidth = 1)
# We are calculating where to annotate numbers
cy = rectangle_size / 2.0
cx = rectangle_size * i + cy
# Annotation You can change color,font, etc ..
ax.annotate(str(nums[i]), (cx, cy), color='black', weight='bold', fontsize=20, ha='center', va='center')
# For squared look
plt.xlim([0, rectangle_size*len(nums)])
plt.ylim([0, rectangle_size])
# We dont want to show ticks
plt.axis('off')
plt.show()
One way using the Rectangle patch is:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
x = np.random.randn(5).round(2).tolist()
fig, ax = plt.subplots(figsize=(9, 2)) # make figure
dx = 0.15 # edge size of box
buf = dx / 10 # buffer around edges
# set x and y limits
ax.set_xlim([0 - buf, len(x) * dx + buf])
ax.set_ylim([0 - buf, dx + buf])
# set axes as equal and turn off axis lines
ax.set_aspect("equal")
ax.axis("off")
# draw plot
for i in range(len(x)):
# create rectangle with linewidth=4
rect = Rectangle((dx * i, 0), dx, dx, facecolor="none", edgecolor="black", lw=4)
ax.add_patch(rect)
# get text position
x0, y0 = dx * i + dx / 2, dx / 2
# add text
ax.text(
x0, y0, f"{x[i]}", color="black", ha="center", va="center", fontsize=28, fontweight="bold"
)
fig.tight_layout()
fig.show()
which gives:

Legend for Lines with Different Endpoint Markers

I need to create a legend for a line segment that has a different marker at the bottom and the top of the line. I am able to create a legend with 1 of the marker symbols repeated but not the two different markers on each end.
Here is a reproducible example.
from matplotlib import pyplot as plt
import numpy as np
#Create some data
x = np.arange(0,11)
y1 = np.sqrt(x/2.)
y2 = x
plt.figure(figsize=(8,8))
ax = plt.subplot(111)
#Plot the lines
for i,x_ in zip(range(11),x):
ax.plot([x_,x_],[y1[i],y2[i]],c='k')
#Plot the end points
ax.scatter(x,y1,marker='s',c='r',s=100,zorder=10)
ax.scatter(x,y2,marker='o',c='r',s=100,zorder=10)
ax.plot([],[],c='k',marker='o',mfc='r',label='Test Range') #Create a single line for the label
ax.legend(loc=2,numpoints=2,prop={'size':16}) # How can I add a label with different symbols the line segments?
plt.show()
The end product should have a legend with a symbol showing a line connecting a circle and a square.
I'm afraid you have to combine different patches of mpatches, I'm not sure whether there is a better solution
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as mpatches
from matplotlib.legend_handler import HandlerPatch
from matplotlib.legend_handler import HandlerLine2D
class HandlerCircle(HandlerPatch):
def create_artists(self,legend,orig_handle,
xdescent,ydescent,width,height,fontsize,trans):
center = 0.5 * width, 0.5 * height
p = mpatches.Circle(xy=center,radius=width*0.3)
self.update_prop(p,orig_handle,legend)
p.set_transform(trans)
return [p]
class HandlerRectangle(HandlerPatch):
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize, trans):
center = 0,height/2-width*0.5/2
width,height = width*0.5,width*0.5
p = mpatches.Rectangle(xy=center,width=width,height=width)
self.update_prop(p, orig_handle, legend)
p.set_transform(trans)
return [p]
fig,ax = plt.subplots(figsize=(12,8))
texts = ['','','Test Range']
line, = ax.plot([],[],c='k')
c = [mpatches.Circle((0.,0.,),facecolor='r',linewidth=.5),
line,
mpatches.Rectangle((0.,0.),5,5,facecolor='r',linewidth=.5)]
ax.legend(c,texts,bbox_to_anchor=(.25,.95),loc='center',ncol=3,prop={'size':20},
columnspacing=-1,handletextpad=.6,
handler_map={mpatches.Circle: HandlerCircle(),
line: HandlerLine2D(numpoints=0,),
mpatches.Rectangle: HandlerRectangle()}).get_frame().set_facecolor('w')
plt.show()
running this script, you will get
If you use a different figure size or a different legend size, the settings in my script above may not be optimal. In that case, you can adjust the following parameters:
The centers and the sizes of Circle and Rectangle
columnspacing and handletextpad in ax.legend(...)

How to have patterns for the color bar in Biokit/corrplot

I wonder if it is possible with corrplot (from the biokit package) to have a colobar with patterns.
In this example below, for the colobar, I would like to have 5 bubbles with differents sizes associated to the matrix's values ([-1, -0.5, 0, +0.5, +1]).
Ideas ?
thanks a lot
import numpy as np
import pandas as pd
from biokit.viz import corrplot
from matplotlib import pyplot as plt
import string
letters = string.ascii_uppercase[0:15]
df = pd.DataFrame(dict(( (k, np.random.random(10)+ord(k)-65) for k in letters)))
df = df.corr()
c = corrplot.Corrplot(df)
c.plot(colorbar=True, upper='circle', rotation=60, cmap='Oranges', fontsize=12)
plt.tight_layout()
plt.show()
Adding filled shapes to a colorbar seems not to be part of the standard interface. However, a legend could serve your goals. Some shapes are supported directly by the legend, others need a special handler, as described in this post.
It seems first some circles need to be created, for which color etc. can be set.
To tell the handler which shape exactly is meant, I'm misusing the label parameter.
From the source of biokit's corrplot, we learn that the ellipse is rotated either + or -45°, and that it is scaled by the absolute value of the correlation.
The following code puts everything together. The colorbar is drawn as a reference, but can be left out once everything is checked. The legend is positioned outside the main plot via bbox_to_anchor=(x, y). These coordinates are in axes coordinates. The ideal location depends on the size of the other elements, so some experimentation could be useful. I didn't draw the corrplot itself, as I don't have it installed, but you can replace the dummy scatter plot with it.
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse, Circle
import matplotlib as mpl
class HandlerEllipse(mpl.legend_handler.HandlerPatch):
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize, trans):
d = float(orig_handle.get_label())
center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
radius = (height + ydescent) * 1.8
p = Ellipse(xy=center, width=radius, height=radius * (1 - abs(d)), angle=45 if d > 0 else -45)
self.update_prop(p, orig_handle, legend)
p.set_transform(trans)
return [p]
#values = [1, 0.5, 0, -0.5, -1]
values = [1, 0.75, 0.5, 0.25, 0, -0.25, -0.5, -0.75, -1]
cmap = plt.cm.Oranges # or plt.cm.PiYG
norm = mpl.colors.Normalize(vmin=-1, vmax=1)
fig, ax = plt.subplots()
plt.scatter([0,1], [0,1], c=[-1,1], cmap=cmap, norm=norm) # a dummy plot as a stand-in for the corrplot
char = plt.colorbar(ticks=values)
shapes = [Circle((0.5, 0.5), 1, facecolor=cmap(norm(d)), edgecolor='k', alpha=1, zorder=2, linewidth=1, label=d)
for d in values]
plt.legend(handles=shapes, labels=values, title='Correlation', framealpha=1,
bbox_to_anchor=(1.25, 1), loc='upper left',
handler_map={Circle: HandlerEllipse()})
plt.tight_layout() # make sure legend and colorbar fit nicely in the plot
plt.show()

Matplotlib customize the legend to show squares instead of rectangles

Here is my attempt to change the legend of a barplot from rectangle to square:
import matplotlib.patches as patches
rect1 = patches.Rectangle((0,0),1,1,facecolor='#FF605E')
rect2 = patches.Rectangle((0,0),1,1,facecolor='#64B2DF')
plt.legend((rect1, rect2), ('2016', '2015'))
But when I plot this, I still see rectangles instead of squares:
Any suggestions on how can I do this?
I tried both solutions provided by #ImportanceOfBeingErnest and #furas, here are the results:
#ImportanceOfBeingErnest's solution is the easiest to do:
plt.rcParams['legend.handlelength'] = 1
plt.rcParams['legend.handleheight'] = 1.125
Here is the result:
My final code looks like this:
plt.legend((df.columns[1], df.columns[0]), handlelength=1, handleheight=1) # the df.columns = the legend text
#furas's solution produces this, I don't know why the texts are further away from the rectangles, but I am sure the gap can be changed somehow:
Matplotlib provides the rcParams
legend.handlelength : 2. # the length of the legend lines in fraction of fontsize
legend.handleheight : 0.7 # the height of the legend handle in fraction of fontsize
You can set those within the call to plt.legend()
plt.legend(handlelength=1, handleheight=1)
or using the rcParams at the beginning of your script
import matplotlib
matplotlib.rcParams['legend.handlelength'] = 1
matplotlib.rcParams['legend.handleheight'] = 1
Unfortunately providing equal handlelength=1, handleheight=1 will not give a perfect rectange. It seems handlelength=1, handleheight=1.125 will do the job, but this may depend on the font being used.
An alternative, if you want to use proxy artists may be to use the square markers from the plot/scatter methods.
bar1 = plt.plot([], marker="s", markersize=15, linestyle="", label="2015")
and supply it to the legend, legend(handles=[bar1]). Using this approach needs to have set matplotlib.rcParams['legend.numpoints'] = 1, otherwise two markers would appear in the legend.
Here is a full example of both methods
import matplotlib.pyplot as plt
plt.rcParams['legend.handlelength'] = 1
plt.rcParams['legend.handleheight'] = 1.125
plt.rcParams['legend.numpoints'] = 1
fig, ax = plt.subplots(ncols=2, figsize=(5,2.5))
# Method 1: Set the handlesizes already in the rcParams
ax[0].set_title("Setting handlesize")
ax[0].bar([0,2], [6,3], width=0.7, color="#a30e73", label="2015", align="center")
ax[0].bar([1,3], [3,2], width=0.7, color="#0943a8", label="2016", align="center" )
ax[0].legend()
# Method 2: use proxy markers. (Needs legend.numpoints to be 1)
ax[1].set_title("Proxy markers")
ax[1].bar([0,2], [6,3], width=0.7, color="#a30e73", align="center" )
ax[1].bar([1,3], [3,2], width=0.7, color="#0943a8", align="center" )
b1, =ax[1].plot([], marker="s", markersize=15, linestyle="", color="#a30e73", label="2015")
b2, =ax[1].plot([], marker="s", markersize=15, linestyle="", color="#0943a8", label="2016")
ax[1].legend(handles=[b1, b2])
[a.set_xticks([0,1,2,3]) for a in ax]
plt.show()
producing
It seems they change it long time ago - and now some elements can't be used directly in legend.
Now it needs handler:
Implementing a custom legend handler
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.legend_handler import HandlerPatch
# --- handlers ---
class HandlerRect(HandlerPatch):
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height,
fontsize, trans):
x = width//2
y = 0
w = h = 10
# create
p = patches.Rectangle(xy=(x, y), width=w, height=h)
# update with data from oryginal object
self.update_prop(p, orig_handle, legend)
# move xy to legend
p.set_transform(trans)
return [p]
class HandlerCircle(HandlerPatch):
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height,
fontsize, trans):
r = 5
x = r + width//2
y = height//2
# create
p = patches.Circle(xy=(x, y), radius=r)
# update with data from oryginal object
self.update_prop(p, orig_handle, legend)
# move xy to legend
p.set_transform(trans)
return [p]
# --- main ---
rect = patches.Rectangle((0,0), 1, 1, facecolor='#FF605E')
circ = patches.Circle((0,0), 1, facecolor='#64B2DF')
plt.legend((rect, circ), ('2016', '2015'),
handler_map={
patches.Rectangle: HandlerRect(),
patches.Circle: HandlerCircle(),
})
plt.show()
Legend reserves place for rectangle and this method doesn't change it so there is so many empty space.

Python Matplotlib Multi-color Legend Entry

I would like to make a legend entry in a matplotlib look something like this:
It has multiple colors for a given legend item. Code is shown below which outputs a red rectangle. I'm wondering what I need to do to overlay one color ontop of another? Or is there a better solution?
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
red_patch = mpatches.Patch(color='red', label='Foo')
plt.legend(handles=[red_patch])
plt.show()
The solution I am proposing is to combine two different proxy-artists for one entry legend, as described here: Combine two Pyplot patches for legend.
The strategy is then to set the fillstyle of the first square marker to left while the other one is set to right (see http://matplotlib.org/1.3.0/examples/pylab_examples/filledmarker_demo.html). Two different colours can then be attributed to each marker in order to produce the desired two-colour legend entry.
The code below show how this can be done. Note that the numpoints=1 argument in plt.legend is important in order to display only one marker for each entry.
import matplotlib.pyplot as plt
plt.close('all')
#---- Generate a Figure ----
fig = plt.figure(figsize=(4, 4))
ax = fig.add_axes([0.15, 0.15, 0.75, 0.75])
ax.axis([0, 1, 0, 1])
#---- Define First Legend Entry ----
m1, = ax.plot([], [], c='red' , marker='s', markersize=20,
fillstyle='left', linestyle='none')
m2, = ax.plot([], [], c='blue' , marker='s', markersize=20,
fillstyle='right', linestyle='none')
#---- Define Second Legend Entry ----
m3, = ax.plot([], [], c='cyan' , marker='s', markersize=20,
fillstyle='left', linestyle='none')
m4, = ax.plot([], [], c='magenta' , marker='s', markersize=20,
fillstyle='right', linestyle='none')
#---- Plot Legend ----
ax.legend(((m2, m1), (m3, m4)), ('Foo', 'Foo2'), numpoints=1, labelspacing=2,
loc='center', fontsize=16)
plt.show(block=False)
Which results in:
Disclaimer: This will only work for a two-colors legend entry. If more than two colours is desired, I cannot think of any other way to do this other than the approach described by #jwinterm (Python Matplotlib Multi-color Legend Entry)
Perhaps another hack to handle more than two patches. Make sure you order the handles/labels according to the number of columns:
from matplotlib.patches import Patch
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
pa1 = Patch(facecolor='red', edgecolor='black')
pa2 = Patch(facecolor='blue', edgecolor='black')
pa3 = Patch(facecolor='green', edgecolor='black')
#
pb1 = Patch(facecolor='pink', edgecolor='black')
pb2 = Patch(facecolor='orange', edgecolor='black')
pb3 = Patch(facecolor='purple', edgecolor='black')
ax.legend(handles=[pa1, pb1, pa2, pb2, pa3, pb3],
labels=['', '', '', '', 'First', 'Second'],
ncol=3, handletextpad=0.5, handlelength=1.0, columnspacing=-0.5,
loc='center', fontsize=16)
plt.show()
which results in:
I absolutely loved #raphael's answer.
Here is a version with circles. Furthermore, I've refactored and trimmed the code a bit to make it more modular.
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
class MulticolorCircles:
"""
For different shapes, override the ``get_patch`` method, and add the new
class to the handler map, e.g. via
ax_r.legend(ax_r_handles, ax_r_labels, handlelength=CONF.LEGEND_ICON_SIZE,
borderpad=1.2, labelspacing=1.2,
handler_map={MulticolorCircles: MulticolorHandler})
"""
def __init__(self, face_colors, edge_colors=None, face_alpha=1,
radius_factor=1):
"""
"""
assert 0 <= face_alpha <= 1, f"Invalid face_alpha: {face_alpha}"
assert radius_factor > 0, "radius_factor must be positive"
self.rad_factor = radius_factor
self.fc = [mcolors.colorConverter.to_rgba(fc, alpha=face_alpha)
for fc in face_colors]
self.ec = edge_colors
if edge_colors is None:
self.ec = ["none" for _ in self.fc]
self.N = len(self.fc)
def get_patch(self, width, height, idx, fc, ec):
"""
"""
w_chunk = width / self.N
radius = min(w_chunk / 2, height) * self.rad_factor
xy = (w_chunk * idx + radius, radius)
patch = plt.Circle(xy, radius, facecolor=fc, edgecolor=ec)
return patch
def __call__(self, width, height):
"""
"""
patches = []
for i, (fc, ec) in enumerate(zip(self.fc, self.ec)):
patch = self.get_patch(width, height, i, fc, ec)
patches.append(patch)
result = PatchCollection(patches, match_original=True)
#
return result
class MulticolorHandler:
"""
"""
#staticmethod
def legend_artist(legend, orig_handle, fontsize, handlebox):
"""
"""
width, height = handlebox.width, handlebox.height
patch = orig_handle(width, height)
handlebox.add_artist(patch)
return patch
Sample usage and image, note that some of the legend handles have radius_factor=0.5 because the true size would be too small.
ax_handles, ax_labels = ax.get_legend_handles_labels()
ax_labels.append(AUDIOSET_LABEL)
ax_handles.append(MulticolorCircles([AUDIOSET_COLOR],
face_alpha=LEGEND_SHADOW_ALPHA))
ax_labels.append(FRAUNHOFER_LABEL)
ax_handles.append(MulticolorCircles([FRAUNHOFER_COLOR],
face_alpha=LEGEND_SHADOW_ALPHA))
ax_labels.append(TRAIN_SOURCE_NORMAL_LABEL)
ax_handles.append(MulticolorCircles(SHADOW_COLORS["source"],
face_alpha=LEGEND_SHADOW_ALPHA))
ax_labels.append(TRAIN_TARGET_NORMAL_LABEL)
ax_handles.append(MulticolorCircles(SHADOW_COLORS["target"],
face_alpha=LEGEND_SHADOW_ALPHA))
ax_labels.append(TEST_SOURCE_ANOMALY_LABEL)
ax_handles.append(MulticolorCircles(DOT_COLORS["anomaly_source"],
radius_factor=LEGEND_DOT_RATIO))
ax_labels.append(TEST_TARGET_ANOMALY_LABEL)
ax_handles.append(MulticolorCircles(DOT_COLORS["anomaly_target"],
radius_factor=LEGEND_DOT_RATIO))
#
ax.legend(ax_handles, ax_labels, handlelength=LEGEND_ICON_SIZE,
borderpad=1.1, labelspacing=1.1,
handler_map={MulticolorCircles: MulticolorHandler})
There is in fact a proper way to do this by implementing a custom
legend handler as explained in the matplotlib-doc under "implementing a custom legend handler" (here):
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
# define an object that will be used by the legend
class MulticolorPatch(object):
def __init__(self, colors):
self.colors = colors
# define a handler for the MulticolorPatch object
class MulticolorPatchHandler(object):
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
width, height = handlebox.width, handlebox.height
patches = []
for i, c in enumerate(orig_handle.colors):
patches.append(plt.Rectangle([width/len(orig_handle.colors) * i - handlebox.xdescent,
-handlebox.ydescent],
width / len(orig_handle.colors),
height,
facecolor=c,
edgecolor='none'))
patch = PatchCollection(patches,match_original=True)
handlebox.add_artist(patch)
return patch
# ------ choose some colors
colors1 = ['g', 'b', 'c', 'm', 'y']
colors2 = ['k', 'r', 'k', 'r', 'k', 'r']
# ------ create a dummy-plot (just to show that it works)
f, ax = plt.subplots()
ax.plot([1,2,3,4,5], [1,4.5,2,5.5,3], c='g', lw=0.5, ls='--',
label='... just a line')
ax.scatter(range(len(colors1)), range(len(colors1)), c=colors1)
ax.scatter([range(len(colors2))], [.5]*len(colors2), c=colors2, s=50)
# ------ get the legend-entries that are already attached to the axis
h, l = ax.get_legend_handles_labels()
# ------ append the multicolor legend patches
h.append(MulticolorPatch(colors1))
l.append("a nice multicolor legend patch")
h.append(MulticolorPatch(colors2))
l.append("and another one")
# ------ create the legend
f.legend(h, l, loc='upper left',
handler_map={MulticolorPatch: MulticolorPatchHandler()},
bbox_to_anchor=(.125,.875))
Probably not exactly what you're looking for, but you can do it (very) manually by placing patches and text yourself on the plot. For instance:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
red_patch = mpatches.Patch(color='red', label='Foo')
plt.legend(handles=[red_patch])
r1 = mpatches.Rectangle((0.1, 0.1), 0.18, 0.1, fill=False)
r2 = mpatches.Rectangle((0.12, 0.12), 0.03, 0.06, fill=True, color='red')
r3 = mpatches.Rectangle((0.15, 0.12), 0.03, 0.06, fill=True, color='blue')
ax.add_patch(r1)
ax.add_patch(r2)
ax.add_patch(r3)
ax.annotate('Foo', (0.2, 0.13), fontsize='x-large')
plt.show()

Categories

Resources