I currently generate my legend with matplotlib this way:
if t==25:
l1,l2 = ax2.plot(x320,vTemp320,'or',x320,vAnaTemp320,'-r')
elif t==50:
l3,l4 = ax2.plot(x320,vTemp320,'ob',x320,vAnaTemp320,'-b')
else:
l5,l6 = ax2.plot(x320,vTemp320,'og',x320,vAnaTemp320,'-g')
plt.legend((l1,l2,l3,l4,l5,l6), ('t=25 Simulation', 't=25 Analytical','t=50 Simulation', 't=50 Analytical','t=500 Simulation', 't=500 Analytical'),
bbox_to_anchor=(-.25, 1), loc=2, borderaxespad=0.,prop={'size':12})
Which somehow works see 1. But I have duplicated information in my legend.
I would prefer to seperate the legend. So that I have different colored lines corresponding to the time t. And a normal line as my Analytical solution an dots for the results of my simulation.
Something like that
--(red line) t = 25
--(blue line) t = 50
--(green line) t = 500
o Simulaton
-- Analytical Solution
Does anyone now how I could achieve this with matplotlib?
You can chose the artists and labels to display in the legend as follows. You'll need to create custom artists for the elements in the legend that are not actually plotted.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0,10,31)
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
#Plot analytic solution
ax.plot(x,1*x**2, color='r', label="t = 25")
ax.plot(x,2*x**2, color='b', label="t = 50")
ax.plot(x,3*x**2, color='g', label="t = 500")
#Plot simulation
ax.plot(x,1*x**2, color='r', linestyle='', marker='o')
ax.plot(x,2*x**2, color='b', linestyle='', marker='o')
ax.plot(x,3*x**2, color='g', linestyle='', marker='o')
#Get artists and labels for legend and chose which ones to display
handles, labels = ax.get_legend_handles_labels()
display = (0,1,2)
#Create custom artists
simArtist = plt.Line2D((0,1),(0,0), color='k', marker='o', linestyle='')
anyArtist = plt.Line2D((0,1),(0,0), color='k')
#Create legend from custom artist/label lists
ax.legend([handle for i,handle in enumerate(handles) if i in display]+[simArtist,anyArtist],
[label for i,label in enumerate(labels) if i in display]+['Simulation', 'Analytic'])
plt.show()
Related
I want to create a 3D scatter plot with legends for the sizes and the colors. However, the legend for the colors only shows the first color in the list.
import matplotlib.pyplot as plt
import matplotlib.colors
# Visualizing 5-D mix data using bubble charts
# leveraging the concepts of hue, size and depth
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type', fontsize=14)
xs = [1,2,3,5,4]
ys = [6,7,3,5,4]
zs = [1,5,3,9,4]
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]
ss = [100,200,390,500,400]
colors = ['red','red','blue','yellow','yellow']
scatter = ax.scatter(xs, ys, zs, alpha=0.4, c=colors, s=ss)
ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
legend1 = ax.legend(*scatter.legend_elements()[0],
loc="upper right", title="Classes", labels=colors, bbox_to_anchor=(1.5, 1),prop={'size': 20})
ax.add_artist(legend1)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
legend2 = ax.legend(handles, labels, loc="upper right", title="Sizes", bbox_to_anchor=(1.5, 0.5), prop={'size': 20})
The issue might result from the fact that matplotlib only receives one series to plot and thus assumes that one legend entry suffices. If I make scatter plots of the red, blue and yellow series individually, then all three classes are displayed correctly in the legend (but it causes issues when plotting the legend with sizes).
It's perhaps not the most elegant solution, but the legend with classes can be created manually:
import matplotlib.pyplot as plt
import matplotlib.colors
from matplotlib.lines import Line2D
# Visualizing 5-D mix data using bubble charts
# leveraging the concepts of hue, size and depth
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type', fontsize=14)
xs = [1,2,3,5,4]
ys = [6,7,3,5,4]
zs = [1,5,3,9,4]
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]
ss = [100,200,390,500,400]
colors = ['red','red','blue','yellow','yellow']
scatter = ax.scatter(xs, ys, zs, alpha=0.4, c=colors, s=ss)
ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
# Create additional legend
UniqueColors = list(dict.fromkeys(colors))
Legend2Add = []
for color in UniqueColors:
Legend2Add.append( Line2D([0], [0], marker='o', color='w', label=color,
markerfacecolor=color, markersize=15, alpha=0.4) )
# Produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
legend1 = ax.legend(handles,
loc="upper right", title="Classes", handles=Legend2Add, bbox_to_anchor=(1.5, 1),prop={'size': 20})
ax.add_artist(legend1)
legend2 = ax.legend(handles, labels, loc="upper right", title="Sizes", bbox_to_anchor=(1.5, 0.5), prop={'size': 20})
plt.show()
I would like to have an increasing spacing between legend items instead of a single value (labelspacing). The latter only accepts an int value type, but I want a variable spacing between legend items. Also, I want the markerfacecolor to follow the colormap used when creating the scatter plot.
N = 45
x, y = np.random.rand(2, N)
s = np.random.randint(10, 1000, size=N)
fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c=s, s=s)
cbar = fig.colorbar(scatter,
ax=ax,
label='Size',
fraction=0.1,
pad=0.04)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
for hd in handles:
hd.set_markeredgewidth(2)
hd.set_markeredgecolor("red")
hd.set_markerfacecolor('blue')
legend2 = ax.legend(
handles[::2], labels[::2], loc="upper right", title="Sizes", labelspacing=1.2
)
plt.show()
I searched StackOverflow and tried some possible methods but without success. Could someone guide how I can achieve the desired output?
I managed to set markerfacecolor as the colormap. But I am still struggling with the variable labelspacing!.
Any help!
N = 45
x, y = np.random.rand(2, N)
s = np.random.randint(10, 1000, size=N)
fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c=s, s=s)
cbar = fig.colorbar(scatter,
ax=ax,
label='Size',
fraction=0.1,
pad=0.04)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
leg_colrs = [color.get_markerfacecolor() for color in scatter.legend_elements()[0]]
for hd, color in zip(handles, leg_colrs):
hd.set_markeredgewidth(2)
hd.set_markeredgecolor("red")
hd.set_markerfacecolor(color)
legend2 = ax.legend(
handles[::2], labels[::2], loc="upper right", title="Sizes", labelspacing=1.2
)
plt.show()
I have a plot with two types of lines: solid and dashed. Both solid and dashed lines represent the same quantity for different conditions and they have the same color.
Now, I'd like to add in an ax.text() the marker of the solid line to say that the solid line represents the quantity in that condition and another ax.text() with the marker of the dashed line to indicate the other condition. Somehow I mean something like this:
ax.text(0.9, 0.5, solid_marker + 'condition 1')
ax.text(0.9, 0.4, dashed_marker + 'condition 2')
Well, something like in this picture:
Example of what I want to do:
Does anybody knows how to do it? It is possible to use the symbols of the markers in random text in the plot?
Thanks!
Your legend's spec is not standard. You need to create it manually. Here is a runnable code that can produce what you need.
#import matplotlib.patches as mpatches
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
# Need to create legends manually
# First legend
#red_patch = mpatches.Patch(color='red', label='The red data')
black_line = mlines.Line2D([], [], color='black', linewidth=1.5, label=r'$Z_n$')
green_line = mlines.Line2D([], [], color='green', linewidth=1.5, label=r'$Z_p$')
red_line = mlines.Line2D([], [], color='red', linewidth=1.5, label=r'$Z_\pi$')
# Second legend
line_solid = mlines.Line2D([], [], color='black', linestyle='-', linewidth=1.5, label=r'$n_\beta = n_o$')
line_dashed = mlines.Line2D([], [], color='black', linestyle='--', linewidth=1.5, label=r'$n_\beta = n_o/2$')
first_legend = plt.legend(handles=[black_line, green_line, red_line], loc=1)
ax = plt.gca().add_artist(first_legend)
second_legend = plt.legend(handles=[line_solid, line_dashed], loc='best', \
bbox_to_anchor=(0.5, 0.20, 0.5, 0.5)) #best upper-right
second_legend.set_frame_on(False)
plt.show()
The output plot:
Thanks to #swatchai. I modified his answer to fit my code. So that I got:
import numpy as np
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
# my fig and my axes
fig, ax1 = plt.subplots(figsize=(6, 6))
# my plots
ax1.plot(temp, zn, color=cycle[0], label=r'$z_n$')
ax1.plot(temp, zp, color=cycle[1], label=r'$z_p$')
ax1.plot(temp, zpi, color=cycle[2], label=r'$z_\pi$')
# the other plots
ax1.plot(temp, zn2, color=cycle[0], linestyle='--')
ax1.plot(temp, zp2, color=cycle[1], linestyle='--')
ax1.plot(temp, zpi2, color=cycle[2], linestyle='--')
# Second legend 'imaginary' lines
line_solid = mlines.Line2D([], [], color='black', linestyle='-', \
linewidth=1.5, label=r'$n_b = n_0$')
line_dashed = mlines.Line2D([], [], color='black', linestyle='--', \
linewidth=1.5, label=r'$n_b = n_0/2$')
# original legend
leg1 = ax1.legend()
# set second legend (will remove first one)
leg2 = ax1.legend(handles=[line_solid, line_dashed], loc='best', \
bbox_to_anchor=(0.5, 0.20, 0.5, 0.6))
leg2.set_frame_on(False) # remove legend frame
# manually add the first legend back
ax1.add_artist(leg1)
The output (notice that the above code is not runnable and it seem I cannot embed pictures yet):
result
I actually wanted to avoid having to go through this step of creating new imaginary lines to assign a legend to them. I would have liked to know if it is possible to use the markers in a text too. But well, this at least solves my problem.
Thanks!
I am plotting a few dozen of subplots with matplotlib. At the bottom of the figure, between the last row of plots and the legend, an empty area appears. The empty area grows larger when I add more subplots. Any idea how to get rid of this empty space?
Here's the working code:
import textwrap
import matplotlib.pyplot as plt
from collections import OrderedDict
rlen = 31 # number of plots
figsize = (11, 3) # matrix of subplots
fig = plt.figure(figsize=(figsize[1]*4, figsize[0]*4))
plots = []
for f_ind in range(rlen):
plots.append(fig.add_subplot(figsize[0], figsize[1], f_ind))
fig.subplots_adjust(wspace=0.5, hspace=0.5)
for ax in plots:
atitle = 'Aaa bbb ccc ' * 10
ax.set_title('\n'.join(textwrap.wrap(atitle, 45)), fontsize=10)
ax.plot(range(10), range(10), 'o', color='red', label='LABEL_1')
revlist = list(reversed(range(10)))
ax.plot(revlist, range(10), 'o', color='blue', label='LABEL_2')
ax.set_xlabel('Train set size', fontsize=9)
ax.set_ylabel('Accuracy (%)', fontsize=9)
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
lgd = fig.legend(by_label.values(), by_label.keys(), loc='lower center', ncol=4)
fig.savefig('ZZZ.png', dpi=fig.dpi, bbox_extra_artists=(lgd,), bbox_inches='tight')
You can also view the example result here. Thanks!
You can use the bottom keyword for subplots_adjust to set the point to which the subplots should be drawn with respect to the figure.
So for example:
fig.subplots_adjust(wspace=0.5, hspace=0.5, bottom=0)
The legend will then be close to the 'lowest' subplots. Here is a crop from the bottom of the resulting image:
Besides bottom, there are also left, right and top keywords.
I am currently plotting the same data but visualizing it differently in two subplots (see figure):
Code snippet used for producing the above figure:
# Figure
plt.figure(figsize=(14,8), dpi=72)
plt.gcf().suptitle(r'Difference between TI and $\lambda$D', size=16)
# Subplot 1
ax1 = plt.subplot2grid((1,3),(0,0),colspan=2)
# Plot scattered data in first subplot
plt.scatter(LE_x, LE_y, s=40, lw=0, color='gold', marker='o', label=r'$\lambda$D')
plt.scatter(MD_x, MD_y, s=40, lw=0, color='blue', marker='^', label=r'TI')
# Subplot 2
ax2 = plt.subplot2grid((1,3),(0,2))
plt.barh(vpos1, LE_hist, height=4, color='gold', label=r'$\lambda$D')
plt.barh(vpos2, MD_hist, height=4, color='blue', label=r'TI')
# Legend
legend = plt.legend()
Is there any way to make the legend show both the scatter dots and the bars? Would this also go per dummy as described here? Could somebody then please post a minimal working example for this, since I'm not able to wrap my head around this.
This worked for me, you essentially capture the patch handles for each graph plotted and manually create a legend at the end.
import pylab as plt
import numpy as NP
plt.figure(figsize=(14,8), dpi=72)
plt.gcf().suptitle(r'Difference between TI and $\lambda$D', size=16)
# Subplot 1
ax1 = plt.subplot2grid((1,3),(0,0),colspan=2)
N = 100
LE_x = NP.random.rand(N)
LE_y = NP.random.rand(N)
MD_x = NP.random.rand(N)
MD_y = NP.random.rand(N)
# Plot scattered data in first subplot
s1 = plt.scatter(LE_x, LE_y, s=40, lw=0, color='gold', marker='o', label=r'$\lambda$D')
s2 = plt.scatter(MD_x, MD_y, s=40, lw=0, color='blue', marker='^', label=r'TI')
data = NP.random.randn(1000)
LE_hist, bins2 = NP.histogram(data, 50)
data = NP.random.randn(1000)
MD_hist, bins2 = NP.histogram(data, 50)
# Subplot 2
ax2 = plt.subplot2grid((1,3),(0,2))
vpos1 = NP.arange(0, len(LE_hist))
vpos2 = NP.arange(0, len(MD_hist)) + 0.5
h1 = plt.barh(vpos1, LE_hist, height=0.5, color='gold', label=r'$\lambda$D')
h2 = plt.barh(vpos2, MD_hist, height=0.5, color='blue', label=r'TI')
# Legend
#legend = plt.legend()
lgd = plt.legend((s1, s2, h1, h2), (r'$\lambda$D', r'TI', r'$\lambda$D', r'TI'), loc='upper center')
plt.show()