0

So basically I'm trying to plot the lap time of 2 F1 drivers and I'd like to show when the Safety Car is out. By the way, I can't show in the same time a legend where I can see, the drivers' name and the Safety Car deployed all at once. I have the same issue to change to marker on the lap where the driver is pitting to change tires.

Here are the lists which are used in the code :

lap=[1, 2, 3, 4, 5, 6, 7, 8, 9]
info=['SC', 'SC', 'SC', None, None, None, None, None, None]
time_lec=[145.323, 141.062, 142.84, 145.489, 136.873, 99.585, 94.848, 92.511, 91.679]
time_nor=[142.471, 139.843, 147.079, 141.984, 130.516, 104.428, 98.389, 94.898, 93.029]
info_lec=[None, None, None, None, None, 'P', None, None, None]
info_nor=[None, None, None, None, 'P', None, None, None, None]

Here is the code to display the drivers legend:

import matplotlib.pyplot as plt


for i in range(0,len(info)):
    if info[i]=='SC':
        plt.axvspan(i+1,i+2,facecolor='yellow',alpha=0.5) #Display when Safety Car is deployed
    if info_lec[i]=='P': #Remove time lost during pitstop
        time_lec[i]=time_lec[i]-25
    if info_nor[i]=='P':
        time_nor[i]=time_nor[i]-25

plt.plot(lap,time_lec,'.:r',label='Leclerc',linewidth=1.2)
plt.plot(lap,time_nor,'.:b',label='Norris',linewidth=1.2)

for i in range(0,len(info)): #Displaying the lap where the driver is pitting (to change tires)
    if info_lec[i]=='P':
        plt.plot(lap[i],time_lec[i],'xr')
    if info_nor[i]=='P':
        plt.plot(lap[i],time_nor[i],'xb')

plt.legend()
plt.xlabel('Lap')
plt.ylabel('Time (sec)')
plt.show()

And here is the code to display the safety car legend:

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
for i in range(0,len(info)):
    if info[i]=='SC':
        plt.axvspan(i+1,i+2,facecolor='yellow',alpha=0.5) #Display when Safety Car is deployed
    if info_lec[i]=='P': #Remove time lost during pitstop
        time_lec[i]=time_lec[i]-25
    if info_nor[i]=='P':
        time_nor[i]=time_nor[i]-25


plt.plot(lap,time_lec,'.:r',label='Leclerc',linewidth=1.2)
plt.plot(lap,time_nor,'.:b',label='Norris',linewidth=1.2)

for i in range(0,len(info)): #Displaying the lap where the driver is pitting (to change tires)
    if info_lec[i]=='P':
        plt.plot(lap[i],time_lec[i],'xr')
    if info_nor[i]=='P':
        plt.plot(lap[i],time_nor[i],'xb')

sc_patch = mpatches.Patch(color='yellow', alpha=0.5, label='Safety car')
plt.legend(handles=[sc_patch])

plt.xlabel('Lap')
plt.ylabel('Time (sec)')
plt.show()

Note: If I'm adding plt.legend() just before plt.show(), we will not see the safety car legend

And here are the two legends I can get even though I would like to have everything at once : We can notice the the marker changed for the pit stop (lap 28) but is not shown in any legend boxes

Driver legend Safety car legend

The objective is to have the 2 legends as one : One legend where there are Leclerc, Norris, Safety Car, Red Flag

Alistair Lucet
  • 125
  • 1
  • 13
  • Can you make this a minimum working example? – Mister Mak Apr 22 '21 at 16:44
  • I added the values for the different lists – Alistair Lucet Apr 22 '21 at 17:28
  • Are you asking how to modify or add to a legend? Your [mre] should include some example data. We shouldn't have to go to an offsite resource or try to get a file off your computer in order to reproduce your problem/issue. – wwii Apr 22 '21 at 17:28
  • `NameError: name 'nan' is not defined` - please fix. – wwii Apr 22 '21 at 17:30
  • Does [Add item to existing Matplotlib legend](https://stackoverflow.com/questions/55896058/add-item-to-existing-matplotlib-legend) answer your question? Or [Manually add legend Items Python matplotlib](https://stackoverflow.com/questions/39500265/manually-add-legend-items-python-matplotlib)? – wwii Apr 22 '21 at 17:36
  • I've replaced nan by None which is OK for my code I haven't been able to make neither of them work – Alistair Lucet Apr 22 '21 at 17:44
  • 1
    Still too much fluff in the example for me. – Mister Mak Apr 22 '21 at 17:48
  • I've tried to make it more readable by putting less value and trying to split the different parts of the code – Alistair Lucet Apr 22 '21 at 18:17

3 Answers3

1

To create a custom legend you need to get the plot objects and use them when defining your legend, as explicited on matplotlib's documentation:

  1. Explicitly defining the elements in the legend

For full control of which artists have a legend entry, it is possible to pass an iterable of legend artists followed by an iterable of legend labels respectively:

ax.legend([line1, line2, line3], ['label1', 'label2', 'label3'])

Also, you may add the labels when creating the vertical bands.

    import matplotlib.pyplot as plt
    
    atl=25 # Average time lost during pitstop
    RedFlag_color='#C33A33'
    SafetyCar_color='#F9D849'
    
    Ferrari_color='#CA2A1D'
    McLaren_color='#F29C38'
    
    
    lap=[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]
    info=['SC', 'SC', 'SC', 'SC', 'SC', None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, 'RF', 'SC', None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None]
    time_lec=[145.323, 141.062, 142.84, 145.489, 136.873, 99.585, 94.848, 92.511, 91.679, 90.706, 90.268, 89.44, 89.089, 88.806, 89.571, 88.137, 87.54, 88.092, 88.526, 87.624, 87.05, 87.21, 86.79, 88.31, 88.054, 87.537, 92.467, 88.853, 84.319, 84.395, 111.855, 131.844, None, 85.394, 81.509, 80.807, 80.439, 80.43, 79.512, 79.561, 79.446, 79.399, 79.237, 79.219, 79.37, 78.938, 79.145, 79.025, 78.818, 78.784, 79.094, 78.739, 79.31, 80.016, 78.97, 78.622, 78.676, 78.689, 78.379, 78.575, 78.461, 78.624]
    time_nor=[142.471, 139.843, 147.079, 141.984, 130.516, 104.428, 98.389, 94.898, 93.029, 92.971, 91.482, 91.918, 89.719, 90.113, 88.964, 87.946, 87.101, 87.049, 87.419, 89.988, 88.538, 88.908, 88.592, 89.157, 89.051, 88.315, 92.204, 87.737, 84.987, 83.211, 128.26, 116.233, None, 83.618, 81.8, 80.748, 80.578, 79.946, 79.745, 79.386, 79.591, 79.484, 79.352, 79.388, 79.205, 78.979, 78.984, 79.025, 79.074, 78.838, 78.95, 78.787, 79.053, 78.717, 78.756, 78.653, 78.951, 78.659, 79.342, 78.557, 78.571, 78.259]
    info_lec=[None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, 'P', None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None]
    info_nor=[None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, 'P', None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None]
    
    
    
    for i in range(0,len(info)):
        if info[i]=='SC':
            plot_SafetyCar = plt.axvspan(
                i+2,i+3,facecolor=SafetyCar_color,alpha=0.5,
                label='Safety Car') #Display when Safety Car is deployed
        if info[i]=='RF':
            plot_RedFlag = plt.axvspan(
                i+2,i+3,facecolor=RedFlag_color,label='Red Flag')
        if info_lec[i]=='P':
            time_lec[i]=time_lec[i]-atl
        if info_nor[i]=='P':
            time_nor[i]=time_nor[i]-atl
    
    
    plot_Leclerc, = plt.plot(
        lap,time_lec,'.:',label='Leclerc',color=Ferrari_color,linewidth=1.2)
    plot_McLaren, = plt.plot(
        lap,time_nor,'.:',label='Norris',color=McLaren_color,linewidth=1.2)
    
    for i in range(0,len(info)): #Displaying the lap where the driver is pitting (to change tires)
        if info_lec[i]=='P':
            plt.plot(lap[i],time_lec[i],'x',color=Ferrari_color)
        if info_nor[i]=='P':
            plt.plot(lap[i],time_nor[i],'x',color=McLaren_color)
    
    
    plt.legend(handles=[plot_Leclerc,plot_McLaren,plot_SafetyCar,plot_RedFlag])
    plt.xlabel('Lap')
    plt.ylabel('Time (sec)')
    plt.show()

wagnifico
  • 632
  • 3
  • 13
1

Can't you just add a label to the following line in your first example?

plt.axvspan(i+1,i+2,facecolor='yellow',alpha=0.5, label='The label')

If it appears many time because of i, add a condition:

label='The label' if (i==0) else None

Mister Mak
  • 276
  • 1
  • 9
  • Yes, it works too, but I had to add something (shown just in the message after because it was not readable as a comment) As the 'SC' isn't necessarily the first value – Alistair Lucet Apr 22 '21 at 19:54
1

To reply to @Mister Mak solution, here is the code

t1=False
for i in range(0,len(info)):
    label1='First label' if (t1==False) else None
    if info[i]=='SC':
     plt.axvspan(i+2,i+3,facecolor=SafetyCar_color,alpha=0.5,label=lb1) 
        t1=True
Alistair Lucet
  • 125
  • 1
  • 13