I have this plot in which some areas between curves are being filled by definition. Is there any way to include them in legend? Especially where those filled areas are overlapped and as well as that a new and different color is being appeared.
Or there is possibility to define an arbitrary legend regardless of the curves' data?
Using fill_bettween to plot your data will automatically include the filled area in the legend.
To include the areas where the two datasets overlap, you can combine the legend handles from both dataset into a single legend handle.
As pointed out in the comments, you can also define any arbitrary legend handle with a proxy.
Finally, you can define exactly what handles and labels you want to appear in the legend, regardless of the data plotted in your graph.
See the MWE below that illustrates the points stated above:
import matplotlib.pyplot as plt
import numpy as np
plt.close('all')
# Gererate some datas:
x = np.random.rand(50)
y = np.arange(len(x))
# Plot data:
fig, ax = plt.subplots(figsize=(11, 4))
fillA = ax.fill_between(y, x-0.25, 0.5, color='darkolivegreen', alpha=0.65, lw=0)
fillB = ax.fill_between(y, x, 0.5, color='indianred', alpha=0.75, lw=0)
linec, = ax.plot(y, np.zeros(len(y))+0.5, color='blue', lw=1.5)
linea, = ax.plot(y, x, color='orange', lw=1.5)
lineb, = ax.plot(y, x-0.25, color='black', lw=1.5)
# Define an arbitrary legend handle with a proxy:
rec1 = plt.Rectangle((0, 0), 1, 1, fc='blue', lw=0, alpha=0.25)
# Generate the legend:
handles = [linea, lineb, linec, fillA, fillB, (fillA, fillB),
rec1, (fillA, fillB, rec1)]
labels = ['a', 'b', 'c', 'A', 'B', 'A+B', 'C', 'A+B+C']
ax.legend(handles, labels, loc=2, ncol=4)
ax.axis(ymin=-1, ymax=2)
plt.show()
Yes, you are absolutely right ian_itor, tacaswell and Jean-Sébastien, user defined legend seems to be the unique solution, in addition I made different linewidth for those area to be distinguishable from the curves, and playing with alpha got the right color.
handles, labels = ax.get_legend_handles_labels()
display = (0,1,2,3,4)
overlap_1 = plt.Line2D((0,1),(0,0), color='firebrick', linestyle='-',linewidth=15, alpha = 0.85)
overlap_2= plt.Line2D((0,1),(0,0), color='darkolivegreen',linestyle='-',linewidth=15, alpha = 0.65)
over_lo_3= plt.Line2D((0,1),(0,0), color='indianred',linestyle='-',linewidth=15, alpha = 0.75)
ax.legend([handle for i,handle in enumerate(handles) if i in display]+[overlap_1 , overlap_2 , overlap_3 ],
[label for i,label in enumerate(labels) if i in display]+['D','F','G'])
Related
I am working on plotting features' importance between two different perspectives as in this image features importance.
How can I change the size of feature fonts and image size to allow all feature names to fit into the image size since I have several features?
This is my code. I hope someone can help me. Thanks a lot.
# Libraries
import matplotlib.pyplot as plt
import pandas as pd
from math import pi
# Set data
df = pd.DataFrame({
'group': ['News','Response'],
'user/favourites_count': [.73,5.75],
'name_length_in _char': [.32,4.27],
'user/followers_count': [13.82 ,4],
'screen_name_length_in _char': [.64,3.31],
'user/friends_count': [.84, 2.14],
'user/description_length_in _char': [1.1, 1.83],
'user/geo_enabled': [.29,1.6],
'user/listed_count': [11.35,.95],
'reply_count': [9.43,1.44],
'reputation_score': [4.34,1.31],
'user_effect': [4.42,1.2],
'user_engagment': [2,.83],
'favorite_count': [2.24,.81],
'user/statuses_count': [2.51,.75],
'user/media_count': [7.18 ,0.056],
'user/verified': [8.73, 0.03],
'user_url_statues': [9.52, 3.4],
'user_describtion_statues': [1.1, 3.6],
})
# ------- PART 1: Create background
# number of variable
categories=list(df)[1:]
N = len(categories)
# What will be the angle of each axis in the plot? (we divide the plot / number of variable)
angles = [n / float(N) * 2 * pi for n in range(N)]
angles += angles[:1]
# Initialise the spider plot
ax = plt.subplot(111, polar=True)
plt.rc('xtick', labelsize=6)
plt.rc('ytick', labelsize=6)
# If you want the first axis to be on top:
ax.set_theta_offset(pi / 2)
ax.set_theta_direction(-1)
# Draw one axe per variable + add labels
plt.xticks(angles[:-1], categories)
# Draw ylabels
ax.set_rlabel_position(0)
plt.yticks([15,25,35], ["15","25","35"], color="grey", size=5)
plt.ylim(0,14)
# ------- PART 2: Add plots
# Plot each individual = each line of the data
# I don't make a loop, because plotting more than 3 groups makes the chart unreadable
# Ind1
values=df.loc[0].drop('group').values.flatten().tolist()
values += values[:1]
ax.plot(angles, values, linewidth=2, linestyle='solid', label="News")
ax.fill(angles, values, 'b', alpha=0.1)
# Ind2
values=df.loc[1].drop('group').values.flatten().tolist()
values += values[:1]
ax.plot(angles, values, linewidth=2, linestyle='solid', label="Response")
ax.fill(angles, values, 'r', alpha=0.1)
# Add legend
plt.legend(loc='upper right', bbox_to_anchor=(0.1, 0.1))
# Show the graph
plt.show()
How to make a scatter plot with random values of delta_x and delta_y positions; where each point has certain frequency value(color intensity changes depending upon intensity), i.e., a certain symbols.
Example plot: (from Alberdi, et al, 2013)
If I'm understanding you correctly, you're asking how to have scatter share a color scale but have different symbols for different groups, correct?
There are a few different ways to handle this.
The key is to call scatter multiple times (one for each different group), but pass in the same vmin, vmax, and cmap arguments.
As an complete (and arguably over-complex) example of reproducing the plot above:
import numpy as np
import matplotlib.pyplot as plt
# Generate data
freq_groups = [1.7, 2.3, 5.0, 8.4]
num = 50
x = np.random.normal(0, 0.5, num)
y = np.random.normal(0.2, 0.5, num)
year = 9 * np.random.random(num) + 1993.5
frequencies = np.random.choice(freq_groups, num)
symbols = ['o', '^', 's', 'd']
# Plot data
fig, ax = plt.subplots(figsize=(8, 9))
for freq, marker in zip(freq_groups, symbols):
mask = np.isclose(freq, frequencies)
scat = ax.scatter(x[mask], y[mask], c=year[mask], s=100, marker=marker,
cmap='jet_r', vmin=year.min(), vmax=year.max(),
label='{:0.1f} GHz'.format(freq), color='black')
ax.legend(loc='upper left', scatterpoints=1)
ax.set(xlabel='Relative RA (mas)', ylabel='Relative Dec (mas)')
ax.invert_xaxis()
cbar = fig.colorbar(scat, orientation='horizontal')
cbar.set_label('Epoch (year)')
cbar.formatter.useOffset = False
cbar.update_ticks()
fig.tight_layout()
plt.show()
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()
What I want is like this:
What I get is this:
So how to merge the markers into one label?
also for the lines, for the lines, of course, u can realize it by not assigning label to the second line while using the same linetype, but for the markers, you can not, since they are of different shapes.
Note that in recent versions of matplotlib you can achieve this using class matplotlib.legend_handler.HandlerTuple as illustrated in this answer and also in this guide:
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerTuple
fig, ax1 = plt.subplots(1, 1)
# First plot: two legend keys for a single entry
p2, = ax1.plot([3, 4], [2, 3], 'o', mfc="white", mec="k")
p1, = ax1.plot([1, 2], [5, 6], 's', mfc="gray", mec="gray")
# `plot` returns a list, but we want the handle - thus the comma on the left
p3, = ax1.plot([1, 5], [4, 4], "-k")
p4, = ax1.plot([2, 6], [3, 2], "-k")
# Assign two of the handles to the same legend entry by putting them in a tuple
# and using a generic handler map (which would be used for any additional
# tuples of handles like (p1, p3)).
l = ax1.legend([(p1, p2), p3], ['data', 'models'],
handler_map={tuple: HandlerTuple(ndivide=None)})
plt.savefig("demo.png")
I think it's best to use a full legend - otherwise, how will your readers know the difference between the two models, or the two datasets? I would do it this way:
But, if you really want to do it your way, you can use a custom legend as shown in this guide. You'll need to create your own class, like they do, that defines the legend_artist method, which then adds squares and circles as appropriate. Here is the plot generated and the code used to generate it:
#!/usr/bin/env python
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
# ==================================
# Define the form of the function
# ==================================
def model(x, A=190, k=1):
return A * np.exp(-k*x/50)
# ==================================
# How many data points are generated
# ==================================
num_samples = 15
# ==================================
# Create data for plots
# ==================================
x_model = np.linspace(0, 130, 200)
x_data1 = np.random.rand(num_samples) * 130
x_data1.sort()
x_data2 = np.random.rand(num_samples) * 130
x_data2.sort()
data1 = model(x_data1, k=1) * (1 + np.random.randn(num_samples) * 0.2)
data2 = model(x_data2, k=2) * (1 + np.random.randn(num_samples) * 0.15)
model1 = model(x_model, k=1)
model2 = model(x_model, k=2)
# ==================================
# Plot everything normally
# ==================================
fig = plt.figure()
ax = fig.add_subplot('111')
ax.plot(x_data1, data1, 'ok', markerfacecolor='none', label='Data (k=1)')
ax.plot(x_data2, data2, 'sk', markeredgecolor='0.5', markerfacecolor='0.5', label='Data (k=2)')
ax.plot(x_model, model1, '-k', label='Model (k=1)')
ax.plot(x_model, model2, '--k', label='Model (k=2)')
# ==================================
# Format plot
# ==================================
ax.set_xlabel('Distance from heated face($10^{-2}$ m)')
ax.set_ylabel('Temperature ($^\circ$C)')
ax.set_xlim((0, 130))
ax.set_title('Normal way to plot')
ax.legend()
fig.tight_layout()
plt.show()
# ==================================
# ==================================
# Do it again, but with custom
# legend
# ==================================
# ==================================
class AnyObject(object):
pass
class data_handler(object):
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
scale = fontsize / 22
x0, y0 = handlebox.xdescent, handlebox.ydescent
width, height = handlebox.width, handlebox.height
patch_sq = mpatches.Rectangle([x0, y0 + height/2 * (1 - scale) ], height * scale, height * scale, facecolor='0.5',
edgecolor='0.5', transform=handlebox.get_transform())
patch_circ = mpatches.Circle([x0 + width - height/2, y0 + height/2], height/2 * scale, facecolor='none',
edgecolor='black', transform=handlebox.get_transform())
handlebox.add_artist(patch_sq)
handlebox.add_artist(patch_circ)
return patch_sq
# ==================================
# Plot everything
# ==================================
fig = plt.figure()
ax = fig.add_subplot('111')
d1 = ax.plot(x_data1, data1, 'ok', markerfacecolor='none', label='Data (k=2)')
d2 = ax.plot(x_data2, data2, 'sk', markeredgecolor='0.5', markerfacecolor='0.5', label='Data (k=1)')
m1 = ax.plot(x_model, model1, '-k', label='Model (k=1)')
m2 = ax.plot(x_model, model2, '-k', label='Model (k=2)')
# ax.legend([d1], handler_map={ax.plot: data_handler()})
ax.legend([AnyObject(), m1[0]], ['Data', 'Model'], handler_map={AnyObject: data_handler()})
# ==================================
# Format plot
# ==================================
ax.set_xlabel('Distance from heated face($10^{-2}$ m)')
ax.set_ylabel('Temperature ($^\circ$C)')
ax.set_xlim((0, 130))
ax.set_title('Custom legend')
fig.tight_layout()
plt.show()
I also found this link very useful (code below), it's an easier way to handle this issue. It's basically using a list of legend handles to make one of the markers of the first handle invisible and overplot it with the marker of the second handle. This way, you have both markers next to each other with one label.
fig, ax = plt.subplots()
p1 = ax.scatter([0.1],[0.5],c='r',marker='s')
p2 = ax.scatter([0.3],[0.2],c='b',marker='o')
l = ax.legend([(p1,p2)],['points'],scatterpoints=2)
With the above code, a TupleHandler is used to create legend handles which
simply overplot two handles (there are red squares behind the blue
circles if you look carefylly. What you want to do is make the second
marker of first handle and the first marker of the second handle
invisible. Unfortunately, the TupleHandler is a rather recent addition
and you need a special function to get all the handles. Otherwise, you
can use the Legend.legendHandles attribute (it only show the first
handle for the TupleHandler).
def get_handle_lists(l):
"""returns a list of lists of handles.
"""
tree = l._legend_box.get_children()[1]
for column in tree.get_children():
for row in column.get_children():
yield row.get_children()[0].get_children()
handles_list = list(get_handle_lists(l))
handles = handles_list[0] # handles is a list of two PathCollection.
# The first one is for red squares, and the second
# is for blue circles.
handles[0].set_facecolors(["r", "none"]) # for the fist
# PathCollection, make the
# second marker invisible by
# setting their facecolor and
# edgecolor to "none."
handles[0].set_edgecolors(["k", "none"])
handles[1].set_facecolors(["none", "b"])
handles[1].set_edgecolors(["none", "k"])
fig
Here is a new solution that will plot any collection of markers with the same label. I have not figured out how to make it work with markers from a line plot, but you can probably do a scatter plot on top of a line plot if you need to.
from matplotlib import pyplot as plt
import matplotlib.collections as mcol
import matplotlib.transforms as mtransforms
import numpy as np
from matplotlib.legend_handler import HandlerPathCollection
from matplotlib import cm
class HandlerMultiPathCollection(HandlerPathCollection):
"""
Handler for PathCollections, which are used by scatter
"""
def create_collection(self, orig_handle, sizes, offsets, transOffset):
p = type(orig_handle)(orig_handle.get_paths(), sizes=sizes,
offsets=offsets,
transOffset=transOffset,
)
return p
fig, ax = plt.subplots()
#make some data to plot
x = np.arange(0, 100, 10)
models = [.05 * x, 8 * np.exp(- .1 * x), np.log(x + 1), .01 * x]
tests = [model + np.random.rand(len(model)) - .5 for model in models]
#make colors and markers
colors = cm.brg(np.linspace(0, 1, len(models)))
markers = ['o', 'D', '*', 's']
markersize = 50
plots = []
#plot points and lines
for i in xrange(len(models)):
line, = plt.plot(x, models[i], linestyle = 'dashed', color = 'black', label = 'Model')
plot = plt.scatter(x, tests[i], c = colors[i], s = markersize, marker = markers[i])
plots.append(plot)
#get attributes
paths = []
sizes = []
facecolors = []
edgecolors = []
for plot in plots:
paths.append(plot.get_paths()[0])
sizes.append(plot.get_sizes()[0])
edgecolors.append(plot.get_edgecolors()[0])
facecolors.append(plot.get_facecolors()[0])
#make proxy artist out of a collection of markers
PC = mcol.PathCollection(paths, sizes, transOffset = ax.transData, facecolors = colors, edgecolors = edgecolors)
PC.set_transform(mtransforms.IdentityTransform())
plt.legend([PC, line], ['Test', 'Model'], handler_map = {type(PC) : HandlerMultiPathCollection()}, scatterpoints = len(paths), scatteryoffsets = [.5], handlelength = len(paths))
plt.show()
I have a solution for you if you're willing to use all circles for markers and differentiate by color only. You can use a circle collection to represent the markers, and then have a legend label for the collection as a whole.
Example code:
import matplotlib.pyplot as plt
import matplotlib.collections as collections
from matplotlib import cm
import numpy as np
#make some data to plot
x = np.arange(0, 100, 10)
models = [.05 * x, 8 * np.exp(- .1 * x), np.log(x + 1), .01 * x]
tests = [model + np.random.rand(len(model)) - .5 for model in models]
#make colors
colors = cm.brg(np.linspace(0, 1, len(models)))
markersize = 50
#plot points and lines
for i in xrange(len(models)):
line, = plt.plot(x, models[i], linestyle = 'dashed', color = 'black', label = 'Model')
plt.scatter(x, tests[i], c = colors[i], s = markersize)
#create collection of circles corresponding to markers
circles = collections.CircleCollection([markersize] * len(models), facecolor = colors)
#make the legend -- scatterpoints needs to be the same as the number
#of markers so that all the markers show up in the legend
plt.legend([circles, line], ['Test', 'Model'], scatterpoints = len(models), scatteryoffsets = [.5], handlelength = len(models))
plt.show()
You can do this by plotting data without any label and then adding the label separately:
from matplotlib import pyplot as plt
from numpy import random
xs = range(10)
data = random.rand(10, 2)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
kwargs = {'color': 'r', 'linewidth': 2, 'linestyle': '--'}
ax.plot(xs, data, **kwargs)
ax.plot([], [], label='Model', **kwargs)
ax.legend()
plt.show()
I'm making a scatter plot which looks like this:
(MWE at bottom of question)
As can be seen in the image above the colors of the points in the legend are set to blue automatically by matplotlib. I need to set this points to some other color not present in the colormap (ie: black) so they won't generate confusion with the colors associated with said colormap.
I looked around but the matplotlib.legend module does not seem to accept a color keyword. Is there any way to do this?
Here's the MWE:
import matplotlib.pyplot as plt
import numpy as np
def rand_data():
return np.random.uniform(low=0., high=1., size=(100,))
# Generate data.
x, y, x2, x3 = [rand_data() for i in range(4)]
# This data defines the markes and labels used.
x1 = np.random.random_integers(7, 9, size=(100,))
# Order all lists so smaller points are on top.
order = np.argsort(-np.array(x2))
# Order x and y.
x_o, y_o = np.take(x, order), np.take(y, order)
# Order list related to markers and labels.
z1 = np.take(x1, order)
# Order list related to sizes.
z2 = np.take(x2, order)
# Order list related to colors.
z3 = np.take(x3, order)
plt.figure()
cm = plt.cm.get_cmap('RdYlBu')
# Scatter plot where each value in z1 has a different marker and label
# assigned.
mrk = {7: ('o', '7'), 8: ('s', '8'), 9: ('D', '9')}
for key, value in mrk.items():
s1 = (z1 == key)
plt.scatter(x_o[s1], y_o[s1], marker=value[0], label=value[1],
s=z2[s1] * 100., c=z3[s1], cmap=cm, lw=0.2)
# Plot colorbar
plt.colorbar()
# Plot legend.
plt.legend(loc="lower left", markerscale=0.7, scatterpoints=1, fontsize=10)
plt.show()
You can obtain the legend handles and change their colors individually:
ax = plt.gca()
leg = ax.get_legend()
leg.legendHandles[0].set_color('red')
leg.legendHandles[1].set_color('yellow')
Adding to the other answers – I've had trouble in the past changing color of legend markers with set_color. An alternate approach is to build the legend yourself:
import matplotlib.lines as mlines
eight = mlines.Line2D([], [], color='blue', marker='s', ls='', label='8')
nine = mlines.Line2D([], [], color='blue', marker='D', ls='', label='9')
# etc etc
plt.legend(handles=[eight, nine])
Building legends from scratch can sometimes save the hassle of dealing with the obscure internals of an already built legend. More information in Matplotlib docs here.
You can retrieve the label of each legend handle with lh.get_label() if you want to map colors to specific labels.
For my purposes it worked best to create a dict from legendHandles and change the colors like so:
ax = plt.gca()
leg = ax.get_legend()
hl_dict = {handle.get_label(): handle for handle in leg.legendHandles}
hl_dict['9'].set_color('red')
hl_dict['8'].set_color('yellow')
While I found that the solution with legendHandles[i].set_color did not work for errorbar, I managed to do the following workaround:
ax_legend = fig.add_subplot(g[3, 0])
ax_legend.axis('off')
handles_markers = []
markers_labels = []
for marker_name, marker_style in markers_style.items():
pts = plt.scatter([0], [0], marker=marker_style, c='black', label=marker_name)
handles_markers.append(pts)
markers_labels.append(marker_name)
pts.remove()
ax_legend.legend(handles_markers, markers_labels, loc='center', ncol=len(markers_labels), handlelength=1.5, handletextpad=.1)
See this GitHub issue as well.