5

I am creating a grid by plotting several curves using one plot call as:

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()

x = np.array([[0,1], [0,1], [0,1]])
y = np.array([[0,0], [1,1], [2,2]])

ax.plot([0,1],[0,2], label='foo', color='b')

ax.plot(x.T, y.T, label='bar', color='k')

ax.legend()

plt.show()

The resulting legend has as many 'bar' entries as there are curves (see below). I wish that have only one legend entry per plot call (in this case only one time 'bar').

I want this such that I can have other plot commands (e.g. the one plotting the 'foo' curve) whose curves are automatically included in the legend if they have a label. I specifically want to avoid hand-selecting the handles when constructing the legend, but rather use matplotlib's feature to deal with this by yes/no including a label when plotting. How can I achieve this?

enter image description here

Tom de Geus
  • 5,625
  • 2
  • 33
  • 77

3 Answers3

8

Here is one possible solution: You may use the fact that underscores do not produce legend entries. So setting all but the first label to "_" suppresses those to appear in the legend.

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()

x = np.array([[0,1], [0,1], [0,1]])
y = np.array([[0,0], [1,1], [2,2]])

ax.plot([0,1],[0,2], label='foo', color='b')

lines = ax.plot(x.T, y.T, label='bar', color='k')
plt.setp(lines[1:], label="_")
ax.legend()

plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
4

Following is one way using the already existing legend handles and labels. You first get the three handles, labels and then just show the first one. This way additionally gives you a control not only on the order of putting handles but also what to show on the plot.

ax.plot(x.T, y.T,  label='bar', color='k')
handles, labels = ax.get_legend_handles_labels()
ax.legend([handles[0]], [labels[0]], loc='best')

enter image description here

Alternative approach where the legends will only be taken from a particular plot (set of lines) -- ax1 in this case

ax1 = ax.plot(x.T, y.T,  label='bar', color='k')
plt.legend(handles=[ax1[0]], loc='best')

Extending it to you problem with two figures

ax1 = ax.plot([0,1],[0,2], label='foo', color='b')
ax2 = ax.plot(x.T, y.T,  label='bar', color='k')
plt.legend(handles=[ax1[0], ax2[1]], loc='best')

Another alternative using for loops as suggested by @SpghttCd

for i in range(len(x)):
    ax.plot(x[i], y[i], label=('' if i==0 else '_') + 'bar', color='k')

ax.legend()

enter image description here

Sheldore
  • 37,862
  • 7
  • 57
  • 71
  • Thanks. A disadvantage I see is that this would break that `legend` automatically gets the handles from other plot commands that were given a label. Right? – Tom de Geus Sep 25 '18 at 12:07
  • I didn't understand your point. Could you perhaps rephrase? If you have other curves plotted on the same axes `ax`, this would then not work. But for your current problem, it looks straightforward. – Sheldore Sep 25 '18 at 12:10
  • I added a second approach where the other plot commands won't be considered for legends because now you take them **only** from a particular plot, `ax1` in this case – Sheldore Sep 25 '18 at 12:15
  • I see, sorry for being not fully clear. I have rephrased the question to make more clear that, and why, I wish to avoid carrying handles. – Tom de Geus Sep 25 '18 at 12:33
  • You can use `label=None` in the second plot command but then you will at some point of time add 'bar' back again to the existing legend. I don't think there is any direct way to just say that include only one legend in the `ax.plot()` command – Sheldore Sep 25 '18 at 12:40
  • I just added an extended work-around solution for your foo and bar cases. Maybe it helps you – Sheldore Sep 25 '18 at 12:47
  • I'm not too deep in your conversation here, but it might be worth noting, that you can surpress labels showing up in the legend by adding an underline as first character of the label (e.g. label='_foo'). That means you could progammatically add those underlines to labels e.g. perhaps in a loop depending on the counter (let's say `i`), so that only the first iteration will create a label for the legend: `ax.plot(x.T, y.T, label=('' if i==0 else '_') + 'bar', color='k')` – SpghttCd Sep 25 '18 at 13:23
  • @SpghttCd: You are right, I thought of this loop based solution first but I wasn't sure if the OP wanted for loops. Let me put your solution as an edit in mine with proper acknowledgement. Thanks for the suggestion. – Sheldore Sep 25 '18 at 13:26
  • What would be the most need is use a counter internal to `plot` and provide a `lambda` function to act on it in this way. With the loop as it is given one can wonder whether it is not equally easy to suppress the `label` option altogether based on the counter (a matter of style of course). @SpghttCd – Tom de Geus Sep 25 '18 at 13:59
  • @TomdeGeus: You can use two plot commands in the for loop using an if statement. `If i==0: plt.plot(.... label='bar') else: plt.plot(....)`. But it's a matter of taste. I really like SpghttCd's solution. After all it's a matter of taste. – Sheldore Sep 25 '18 at 14:06
0

Maybe not quite elegant, but the easiest and most straightforward way is to make a second plot using a single pair of elements where you prescribe the 'label' you want!

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()

x = np.array([[0,1], [0,1], [0,1]])
y = np.array([[0,0], [1,1], [2,2]])

ax.plot([0,1],[0,2], label='foo', color='b')

ax.plot(x.T, y.T, color='k')
ax.plot(x[0].T, y[0].T, label='bar', color='k')


ax.legend()

plt.show()

enter image description here