colors in legend do not match graph colors
Question:
I am trying to make customized legends showing only some of the lines plotted. However, the color in the legend does not always match the color of the plotted line. In the example legend 1 and legend 2 are fine, but legend 3 should show blue and green, not blue and orange. See attached image. How can I fix this?
Here is a mwe which I used to generate the image, but note that in the real program the plot function is not directly accessible and loaded from a different file. Also, the plot function should not be modified as it is used in other programs as well.
import matplotlib.pyplot as plt
import numpy as np
def my_plot_function(x,y,ax):
#do complicated stuff here, manipulating y for example
ax.plot(x,y)
x = np.linspace(0,1,100)
fig = plt.figure()
ax = fig.add_subplot(111)
my_plot_function(x, x, ax)
my_plot_function(x, x**2, ax)
my_plot_function(x, x**3, ax)
my_plot_function(x, x**4, ax)
lines = ax.get_lines()
print(lines[0])
print(lines[1])
print(lines[2])
print(lines[3])
fig.legend(lines, labels=range(4), loc=1, title="legend 1")
fig.legend([lines[0],lines[1]], labels=["0","1"], loc=2, title="legend 2")
fig.legend([lines[0],lines[2]], labels=["0","2"], loc=3, title="legend 3")
plt.show()
EDIT:
To clarify what I was asking for: Plotting needs to be done by my_plot_function
which is defined in a separate file and may not be modified. Thus I cannot pass it additional keywords such as label
.
Answers:
When I run your code, I get a warning from legend.py
:
UserWarning: You have mixed positional and keyword arguments, some input may be discarded.
warnings.warn("You have mixed positional and keyword "...
This is probably the reason why it’s not working. You need to add the handles
in your legend calls. I.e.
fig.legend(handles=[lines[0],lines[1]],...
By the way, it’s a good practice to assign a label when the plot is created.
ax.plot(x, y, label='some label')
Then you can call
handles, labels = ax.get_legend_handles_labels()
and use them in the legend like so
fig.legend(handles=(handles[0],handles[2]), labels=(labels[0],labels[2]),...)
Use handles
keyword argument:
fig.legend(handles=lines, labels=range(4), loc=1, title="legend 1")
fig.legend(handles=[lines[0],lines[1]], labels=["0","1"], loc=2, title="legend 2")
fig.legend(handles=[lines[0],lines[2]], labels=["0","2"], loc=3, title="legend 3")
When looking at the docs of figure.legend we can see that labels has to be used with handles (and the other way). You must put both handles and labels or none of them.
This works
fig.legend(lines, range(4), loc=1, title="legend 1")
fig.legend((lines[0],lines[1]), ("0","1"), loc=2, title="legend 2")
fig.legend((lines[0],lines[2]), ("0","2"), loc=3, title="legend 3")
and this works
fig.legend(handles=lines, labels=range(4), loc=1, title="legend 1")
fig.legend(handles=(lines[0],lines[1]), labels=("0","1"), loc=2, title="legend 2")
fig.legend(handles=(lines[0],lines[2]), labels=("0","2"), loc=3, title="legend 3")
I would approach the problem slightly differently. In cases like yours, I would always recommend to pass the legend entry while plotting using the label
keyword. Then, to use selected legends, you can use get_legend_handles_labels()
and then pass the required items to fig.legend()
. You do not have to specify the handles
and labels
arguments. But if you specify one (like you did for labels
), you should also specify the other or else you get a warning
import matplotlib.pyplot as plt
import numpy as np
def plot(x,y,ax):
ax.plot(x,y)
x = np.linspace(0,1,100)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, x, label='0')
ax.plot(x, x**2, label='1')
ax.plot(x, x**3, label='2')
ax.plot(x, x**4, label='3')
ax.legend(loc=1, title='legend 1')
h, l = ax.get_legend_handles_labels()
fig.legend([h[0],h[1]], [l[0], l[1]], loc=2, title="legend 2")
fig.legend([h[0],h[2]], [l[0], l[2]], loc=3, title="legend 3")
plt.show()
An alternative approach answering @gboffi comment below is following where you don’t use the global legend to extract the values.
x = np.linspace(0,1,100)
fig = plt.figure()
ax = fig.add_subplot(111)
l0, = ax.plot(x, x, label='0')
l1, = ax.plot(x, x**2, label='1')
l2, = ax.plot(x, x**3, label='2')
l3, = ax.plot(x, x**4, label='3')
fig.legend([l0, l1], [l0.get_label(), l1.get_label()], loc=2, title="legend 2")
fig.legend([l0, l2], [l0.get_label(), l2.get_label()], loc=3, title="legend 3")
plt.show()
I am trying to make customized legends showing only some of the lines plotted. However, the color in the legend does not always match the color of the plotted line. In the example legend 1 and legend 2 are fine, but legend 3 should show blue and green, not blue and orange. See attached image. How can I fix this?
Here is a mwe which I used to generate the image, but note that in the real program the plot function is not directly accessible and loaded from a different file. Also, the plot function should not be modified as it is used in other programs as well.
import matplotlib.pyplot as plt
import numpy as np
def my_plot_function(x,y,ax):
#do complicated stuff here, manipulating y for example
ax.plot(x,y)
x = np.linspace(0,1,100)
fig = plt.figure()
ax = fig.add_subplot(111)
my_plot_function(x, x, ax)
my_plot_function(x, x**2, ax)
my_plot_function(x, x**3, ax)
my_plot_function(x, x**4, ax)
lines = ax.get_lines()
print(lines[0])
print(lines[1])
print(lines[2])
print(lines[3])
fig.legend(lines, labels=range(4), loc=1, title="legend 1")
fig.legend([lines[0],lines[1]], labels=["0","1"], loc=2, title="legend 2")
fig.legend([lines[0],lines[2]], labels=["0","2"], loc=3, title="legend 3")
plt.show()
EDIT:
To clarify what I was asking for: Plotting needs to be done by my_plot_function
which is defined in a separate file and may not be modified. Thus I cannot pass it additional keywords such as label
.
When I run your code, I get a warning from legend.py
:
UserWarning: You have mixed positional and keyword arguments, some input may be discarded.
warnings.warn("You have mixed positional and keyword "...
This is probably the reason why it’s not working. You need to add the handles
in your legend calls. I.e.
fig.legend(handles=[lines[0],lines[1]],...
By the way, it’s a good practice to assign a label when the plot is created.
ax.plot(x, y, label='some label')
Then you can call
handles, labels = ax.get_legend_handles_labels()
and use them in the legend like so
fig.legend(handles=(handles[0],handles[2]), labels=(labels[0],labels[2]),...)
Use handles
keyword argument:
fig.legend(handles=lines, labels=range(4), loc=1, title="legend 1")
fig.legend(handles=[lines[0],lines[1]], labels=["0","1"], loc=2, title="legend 2")
fig.legend(handles=[lines[0],lines[2]], labels=["0","2"], loc=3, title="legend 3")
When looking at the docs of figure.legend we can see that labels has to be used with handles (and the other way). You must put both handles and labels or none of them.
This works
fig.legend(lines, range(4), loc=1, title="legend 1")
fig.legend((lines[0],lines[1]), ("0","1"), loc=2, title="legend 2")
fig.legend((lines[0],lines[2]), ("0","2"), loc=3, title="legend 3")
and this works
fig.legend(handles=lines, labels=range(4), loc=1, title="legend 1")
fig.legend(handles=(lines[0],lines[1]), labels=("0","1"), loc=2, title="legend 2")
fig.legend(handles=(lines[0],lines[2]), labels=("0","2"), loc=3, title="legend 3")
I would approach the problem slightly differently. In cases like yours, I would always recommend to pass the legend entry while plotting using the label
keyword. Then, to use selected legends, you can use get_legend_handles_labels()
and then pass the required items to fig.legend()
. You do not have to specify the handles
and labels
arguments. But if you specify one (like you did for labels
), you should also specify the other or else you get a warning
import matplotlib.pyplot as plt
import numpy as np
def plot(x,y,ax):
ax.plot(x,y)
x = np.linspace(0,1,100)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, x, label='0')
ax.plot(x, x**2, label='1')
ax.plot(x, x**3, label='2')
ax.plot(x, x**4, label='3')
ax.legend(loc=1, title='legend 1')
h, l = ax.get_legend_handles_labels()
fig.legend([h[0],h[1]], [l[0], l[1]], loc=2, title="legend 2")
fig.legend([h[0],h[2]], [l[0], l[2]], loc=3, title="legend 3")
plt.show()
An alternative approach answering @gboffi comment below is following where you don’t use the global legend to extract the values.
x = np.linspace(0,1,100)
fig = plt.figure()
ax = fig.add_subplot(111)
l0, = ax.plot(x, x, label='0')
l1, = ax.plot(x, x**2, label='1')
l2, = ax.plot(x, x**3, label='2')
l3, = ax.plot(x, x**4, label='3')
fig.legend([l0, l1], [l0.get_label(), l1.get_label()], loc=2, title="legend 2")
fig.legend([l0, l2], [l0.get_label(), l2.get_label()], loc=3, title="legend 3")
plt.show()