1

I read through previous solutions but couldn't make any of them work. I want to have a global legend for individual subplots. The ax objects for this subplot are generated by a predefined function "get_plot" out of a predefined class "The_predefined_plotting_class" roughly like this:

My code

the function returns an ax object and each ax objects has multiple "plots"/ from multiple columns of the original "datafiles".

In one of the solutions I found on this site I read that I could use:

enter image description here

to make a global legend. Unfortunately I have no idea how to append the individual ax objects ( or the data therein) to handles to make this work. Each plots contains some identical column names and some that differ. If an entry/name exists in many subplots it should only be printed once.

Solution1

Solution2

Solution3

EDIT

I am really sorry that I had to use pictures, but whatever I did the website did not let me post my code even though it was shown properly in the preview window (the screenshots are from this window)

EDIT2

If I do it like this:

lines=[]
labels=[]
for idata, datafile in enumerate(datafiles):
              
    MYData = The_predefined_plotting_class.from_file(datafile) 
        
    axis[idata] = The_predefined_plotting_class.get_plot( *kwargs)
    h, l = axis[idata].get_legend_handles_labels()

    lines.append(h)
    labels.append(l)


LINES=[]
LABELS=[]
for i in range(0, nrows):
    LINES+=lines[i]
    LABELS+=labels[i]
plt.legend( LINES, LABELS, loc="upper left", bbox_to_anchor=[0, 1],ncol=3, shadow=True, title="Legend", fancybox=True)
plt.show()

Then it shows all Data. Some of the Data has the same lines and labels handler. I am now left with the problem to iterate through both lists and delete only one entry if in both lists the tuple (LINES[j];LABELS[j]) = (LINES[i];LABELS[i]) exist twice (and only then). Preferably the first entry:

EDIT3

labels =[]
lines = []
h=["Cat","Mouse","Dog","Cat","Cat","Kangaroo","Dog"]
l=["black","white","brown","white","black","yellow","brown"]


for handle, label in zip(h, l):
    if label not in labels : 
        
            lines.append(handle)
            labels.append(label)
            
print "The disired Output is :"            
print '["Cat","Mouse","Dog","Cat","Kangaroo"]'
print '["black","white","brown","white","yellow"]'

print "currently you get:"               
                
print lines
print labels  
          

EDIT4

I add a "minimum" working example which should contain all possible situations that occur in my real data.

lines=[]
labels=[]
legend_properties = {'weight':'bold','size':10}  
# Example data
x1 = np.linspace(0.0, 5.0)
x2 = np.linspace(0.0, 2.0)

a = np.cos(2 * np.pi * x1) * np.exp(-x1)
b = np.cos(2 * np.pi * x2)
c = np.cos(5 * np.pi * x1) * np.exp(-x1)
c2 = np.cos(5 * np.pi * x1**2) * np.exp(-x1)
d = np.cos(2 * np.pi * x2 )
d2 = np.cos(2 * np.pi * x2-1 )
e = x1*5
e2 = -x1*5
f = np.exp(x1)-e
f2 = (np.exp(x1)-e)/2

nrows = 4
# Plot

fig, axis = plt.subplots(nrows, sharex=True, sharey=False, figsize=(5, 8))
fig.subplots_adjust(hspace=0.0001)
fig.suptitle("Stacked Plots with global Legend which contains to little elements",fontsize=14,weight='bold')



axis[0].plot(x1, e, 'k--', label='Label1',color="green")
axis[0].plot(x1, e2, 'k--', label='Label2',color="blue")
axis[0].plot(x1, a, 'k--', label='Label3',color="yellow")
axis[1].plot(x1, c, 'k--', label='Label1',color="green")
axis[1].plot(x1, c2, 'k--', label='Label2',color="blue")
axis[1].plot(x1, a, 'k--', label='Label3',color="grey")
axis[2].plot(x2, d, '*', label='Label1',color="green")
axis[2].plot(x2, d2, 'D', label='Label2',color="green")
axis[3].plot(x1, f, 'H', label='Label1',color="green")
axis[3].plot(x1, f2, 'D', label='Label2',color="green")

for i in range(nrows):
    h, l = axis[i].get_legend_handles_labels()
    for handle, label in zip(h, l):
        if label not in labels:
            lines.append(handle)
            labels.append(label)
            
# only 3 Legend entries Label1, Label2 and Label3 are visible .. Differences in colors and markers are ignored
plt.legend(handles=lines, labels=labels,bbox_to_anchor=(0., nrows+.02, 1., .102), loc=3,ncol=3, prop=legend_properties,mode="expand", borderaxespad=0.,frameon=False,framealpha=0.0) 

plt.show()

EDIT5

this is the part form the script in question where the actual plots are generated. "columns" contains just the names of the actual data to be plotted.

    # add plots
    ic = 0
    for col in columns:
        if col == "envelope":
            ax.plot(self.data.index, self.data.envelope,
                    linewidth=LINEWIDTH_envelope, c=last_color, label="")
        elif col == "Exp":
            ax.plot(self.data.index, self.data.Exp, c=first_color, linestyle="",
                    label="Exp", marker="o", markersize=MARKERSIZE )
        else:
            color = used_colors[ic % len(used_colors)]
            if fill and "BG" in self.data.columns:
                ax.fill_between(self.data.index, self.data.BG,
                                self.data[col], label=col, alpha=ALPHA,
                                color=color)
            else:
                ax.plot(self.data.index, self.data[col], linewidth=LINEWIDTH,
                        c=color, label=col)
            ic += 1

EDIT6

I tried to find a solution based on the idea I presented here:

Iteration though lists

Unfortunately what works for two Lists containing strings does not work for the artist handles it seems.

import matplotlib.pyplot as plt
import numpy as np
LI=[]
lines=[]
labels=[]
legend_properties = {'weight':'bold','size':10}  
# Example data
x1 = np.linspace(0.0, 5.0)
x2 = np.linspace(0.0, 2.0)

a = np.cos(2 * np.pi * x1) * np.exp(-x1)
b = np.cos(2 * np.pi * x2)
c = np.cos(5 * np.pi * x1) * np.exp(-x1)
c2 = np.cos(5 * np.pi * x1**2) * np.exp(-x1)
d = np.cos(2 * np.pi * x2 )
d2 = np.cos(2 * np.pi * x2-1 )
e = x1*5
e2 = -x1*5
f = np.exp(x1)-e
f2 = (np.exp(x1)-e)/2

nrows = 4
# Plot

fig, axis = plt.subplots(nrows, sharex=True, sharey=False, figsize=(5, 8))
fig.subplots_adjust(hspace=0.0001)
#fig.suptitle("Stacked Plots with global Legend which contains to little elements",fontsize=14,weight='bold')



axis[0].plot(x1, e, 'k--', label='Label1',color="green")
axis[0].plot(x1, e2, 'k--', label='Label2',color="blue")
axis[0].plot(x1, a, 'k--', label='Label3',color="yellow")
axis[1].plot(x1, c, 'k--', label='Label1',color="green")
axis[1].plot(x1, c2, 'k--', label='Label2',color="blue")
axis[1].plot(x1, a, 'k--', label='Label3',color="grey")
axis[2].plot(x2, d, '*', label='Label1',color="green")
axis[2].plot(x2, d2, 'D', label='Label2',color="green")
axis[3].plot(x1, f, 'H', label='Label1',color="green")
axis[3].plot(x1, f2, 'D', label='Label2',color="green")

for i in range(nrows):
    print i
    h, l = axis[i].get_legend_handles_labels()
    for hl in zip(h,l):

        if hl not in LI:
            LI.append(hl)
            lines.append(LI[-1][0])
            labels.append(LI[-1][1])
            
print LI            


            
# only 3 Legend entries Label1 , Label2 and Label3 are visible .. Differences in colors and markers are ignored
plt.legend(handles=lines, labels=labels,bbox_to_anchor=(0., nrows+.02, 1., .102), loc=3,ncol=3, prop=legend_properties,mode="expand", borderaxespad=0.,frameon=False,framealpha=0.0) 

plt.show()

I think the problem is that only the string for the Memory address is compared in

if hl not in LI:

not the actual content of "h"?

solution based on the explanation of ImportanceOfBeingErnest gave in a related post Link7:

import matplotlib.pyplot as plt
import numpy as np
import matplotlib.mlab as mlab
import math
import matplotlib.collections

def is_inlist(handle, handles):
    for h in handles:
        if isinstance(handle, matplotlib.collections.PolyCollection) and isinstance(h, matplotlib.collections.PolyCollection): 
            if np.all(h.get_facecolor() == handle.get_facecolor()) and \
                np.all(h.get_linestyle() == handle.get_linestyle()) and \
                np.all(h.get_alpha() == handle.get_alpha()):
                return True
        if isinstance(handle, matplotlib.lines.Line2D) and isinstance(h, matplotlib.lines.Line2D):
            if h.get_color() == handle.get_color() and \
                h.get_linestyle() == handle.get_linestyle() and \
                h.get_marker() == handle.get_marker():
                return True        
                
                
    return False


lines=[]
labels=[]
legend_properties = {'weight':'bold','size':10}  
# Example data


mu = 0
mu2 = 5
variance = 1
variance2 = 2
sigma = math.sqrt(variance)
sigma2 = math.sqrt(variance2)
x = np.linspace(mu-3*variance,mu+3*variance, 100)
x2 = np.linspace(mu2-3*variance2,mu2+3*variance2, 100)

nrows = 4
# Plot

fig, axis = plt.subplots(nrows, sharex=True, sharey=False, figsize=(5, 8))
fig.subplots_adjust(hspace=0.0001)
#fig.suptitle("Stacked Plots with global Legend which contains to little elements",fontsize=14,weight='bold')


axis[0].fill_between(x+6,0,mlab.normpdf(x, mu, sigma), color='green',alpha=0.5,label="PEAK1", interpolate=True)
axis[0].fill_between(x+4,0,mlab.normpdf(x, mu, sigma), color='orange',alpha=0.5,label="PEAK2", interpolate=True)
axis[0].fill_between(x+3,0,mlab.normpdf(x, mu, sigma), color='blue',alpha=0.5,label="PEAK3", interpolate=True)
axis[0].fill_between(x+7,0,mlab.normpdf(x, mu, sigma), color='red',alpha=0.5,label="PEAK4", interpolate=True)
axis[0].plot(x2,2.5*mlab.normpdf(x2, mu2, sigma2),color='black',linestyle="",label="Exp", marker="o", markersize=4)

axis[1].fill_between(x+6,0,mlab.normpdf(x, mu, sigma), color='green',alpha=0.5,label="PEAK1", interpolate=True)
axis[1].fill_between(x+4,0,mlab.normpdf(x, mu, sigma), color='purple',alpha=0.5,label="PEAK2", interpolate=True)
axis[1].fill_between(x+3,0,mlab.normpdf(x, mu, sigma), color='blue',alpha=0.5,label="PEAK3", interpolate=True)
axis[1].fill_between(x+7,0,mlab.normpdf(x, mu, sigma), color='red',alpha=0.5,label="PEAK4", interpolate=True)
axis[1].fill_between(x+6.5,0,mlab.normpdf(x, mu, sigma), color='yellow',alpha=0.5,label="PEAK5", interpolate=True)
axis[1].plot(x2,2.5*mlab.normpdf(x2, mu2, sigma2),color='black',linestyle="",label="Exp", marker="o", markersize=4)

axis[2].fill_between(x+6,0,mlab.normpdf(x, mu, sigma), color='green',alpha=0.5,label="PEAK1", interpolate=True)
axis[2].fill_between(x+4,0,mlab.normpdf(x, mu, sigma), color='orange',alpha=0.5,label="PEAK2", interpolate=True)
axis[2].fill_between(x+3,0,mlab.normpdf(x, mu, sigma), color='#73d216',alpha=0.5,label="PEAK3", interpolate=True)
axis[2].fill_between(x+7,0,mlab.normpdf(x, mu, sigma), color='red',alpha=0.5,label="PEAK4", interpolate=True)
axis[2].plot(x2,2.5*mlab.normpdf(x2, mu2, sigma2),color='black',linestyle="",label="Exp", marker="o", markersize=4)


axis[3].fill_between(x+6,0,mlab.normpdf(x, mu, sigma), color='green',alpha=0.5,label="PEAK1", interpolate=True)
axis[3].fill_between(x+4,0,mlab.normpdf(x, mu, sigma), color='purple',alpha=0.5,label="PEAK2", interpolate=True)
axis[3].fill_between(x+3,0,mlab.normpdf(x, mu, sigma), color='blue',alpha=0.5,label="PEAK3", interpolate=True)
axis[3].fill_between(x+7,0,mlab.normpdf(x, mu, sigma), color='red',alpha=0.5,label="PEAK4", interpolate=True)
axis[3].fill_between(x+6.5,0,mlab.normpdf(x, mu, sigma), color='#73d216',alpha=0.5,label="PEAK5", interpolate=True)
axis[3].fill_between(x+5.5,0,mlab.normpdf(x, mu, sigma), color='violet',alpha=0.5,label="PEAK6", interpolate=True)
axis[3].plot(x2,2.5*mlab.normpdf(x2, mu2, sigma2),color='black',linestyle="",label="Exp", marker="o", markersize=4)



for i in range(nrows):
    h, l = axis[i].get_legend_handles_labels()
    for hi, li in zip(h,l):
        if not is_inlist(hi, lines):
            lines.append(hi)
            labels.append(li)
           





# only 3 Legend entries Label1, Label2 and Label3 are visible .. Differences in colors and markers are ignored
plt.legend(handles=lines, labels=labels,bbox_to_anchor=(0., nrows-1+.02, 1., .102), loc=3,ncol=3, prop=legend_properties,mode="expand", borderaxespad=0.,frameon=False,framealpha=0.0) 


plt.show()

Here my real data is reflected better as I have both matplotlib.collections.PolyCollection) and matplotlib.lines.Line2D objects which need to be compared.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
NorrinRadd
  • 545
  • 6
  • 21
  • None of the solutions in the questions linked worked for you? I'd suggest a simple `ax.legend(bbox_to_anchor=(1.05, 0), loc='lower center', borderaxespad=0.)` – Vinícius Figueiredo Jul 06 '17 at 20:11
  • You need to type in the code manually, somehow you're pasting in screenshots of the code. – ngoldbaum Jul 06 '17 at 20:40
  • if i do what you suggest i get : AttributeError: 'numpy.ndarray' object has no attribute 'legend' ! I added a question above. – NorrinRadd Jul 06 '17 at 21:03

1 Answers1

1

The Edit2 looks promising. You can then check if the label is already in the labels list and if not, append it. Of course I cannot test the following, but it should at least show the concept.

lines=[]
labels=[]
for idata, datafile in enumerate(datafiles):

    MYData = The_predefined_plotting_class.from_file(datafile) 

    axis[idata] = The_predefined_plotting_class.get_plot( *kwargs)
    h, l = axis[idata].get_legend_handles_labels()

    for handle, label in zip(h, l):
        if label not in labels:
            lines.append(handle)
            labels.append(label)

plt.legend(handles=lines, labels=labels, loc="upper left", bbox_to_anchor=[0, 1],ncol=3, shadow=True, title="Legend", fancybox=True)
plt.show()

If you want to aviod diplicate handles, you may use the properties which make them appear equal and see if a similar artist is already present in the handles list.

def is_inlist(handle, handles):
    for h in handles:
        if h.get_color() == handle.get_color() and \
            h.get_linestyle() == handle.get_linestyle() and \
            h.get_marker() == handle.get_marker():
            return True
    return False

lines=[]
labels=[]
for i in range(nrows):
    h, l = axis[i].get_legend_handles_labels()
    for hi, li in zip(h,l):
        if not is_inlist(hi, lines):
            lines.append(hi)
            labels.append(li)

plt.legend(handles=lines, labels=labels) 

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • @NorrinRadd next time please provide a [mcve]. Otherwise it's really hard to provide any help. – ImportanceOfBeingErnest Jul 06 '17 at 21:33
  • To early.... if a handle is "connected" to two labels the above solution deletes it anyways.... This means if a subplot has and element a that is colord red and another cublot has an element a that is colored green only the first one appears in the legend. But this should not be the case. Do you have any idea? – NorrinRadd Jul 06 '17 at 22:02
  • How can a handle be connected to two labels? In any case, as I already said, a [mcve] is necessary to be able to provide you with a good solution. Mind that you do not need `The_predefined_plotting_class` or so for such an example. You just need the working code that shows the issue, and, if corrected, allows you to solve your problem. – ImportanceOfBeingErnest Jul 06 '17 at 22:05
  • If you plot the same component with two different colors in different subplots.... "Cat" could than be "black" and "white" ..... The problem with the MWE ist that i did not find a way to simmulate exactly what the The_predefined_plotting_class does unfortunately – NorrinRadd Jul 07 '17 at 06:51
  • I added a MWE for the list sorting problem – NorrinRadd Jul 07 '17 at 07:21
  • So the same label appears on different handles? That would be rather confusing for anyone looking at the plot, won't it? What is the criterion by which you judge that the third "Cat" is equal to the first, but that the second "Cat" is unequal to the "Mouse" (given that you don't have strings for which this is obvious)? – ImportanceOfBeingErnest Jul 07 '17 at 09:43
  • I added a complete MWE. I want to plot XPS data where small shifts in BE for identical components should be indicated by a different color in each subplot. – NorrinRadd Jul 07 '17 at 10:12
  • I updated the anwer. However, I must say that the plot is not really understandable. So if this is for a report and needs to be shown to other I would suggest to rethink the way of presentation. – ImportanceOfBeingErnest Jul 07 '17 at 13:17
  • It is really easy to understand for the actual data, The duplicates will be the same chemical group (by name) albeit in a different local environment. hence it is colored differently The plot is always complemented by a drawing of the chemical structure. Your solution is perfect for my MWE. I will mark it as solved . Unfortunately for my actual script i get the error: `if h.get_color() == handle.get_color() and \ AttributeError: 'PolyCollection' object has no attribute 'get_color'` – NorrinRadd Jul 07 '17 at 16:36
  • I guess this is related to the actually data type? Thank you for helping me even is you did not believe it is a good idea to do this!! – NorrinRadd Jul 07 '17 at 16:44
  • Up to know you were talking about lines. Where does the polycollection come from (i.e. which artist did you add other than a line)? I guess that makes it more complicated, but the solution would be to check if the handle is a line or a polycollection first and check for equality depending on other parameters for the polycollection then. – ImportanceOfBeingErnest Jul 08 '17 at 08:44
  • I added the actual part from the script in question were the actual plots are generated in the original Question. – NorrinRadd Jul 08 '17 at 09:45
  • Maybe you create the handles list already while creating the plots, instead of filtering out afterwards. This seems easier, now that you have also different plot types. – ImportanceOfBeingErnest Jul 08 '17 at 09:47
  • could you give me an example how this is done? I think i do not really understand how to do this wouldn´t still have to filter the list of all subplots for duplicates afterwards? – NorrinRadd Jul 08 '17 at 09:49
  • i added another idea in Edit6 . This seems to work for lists containing strings but not in this case. – NorrinRadd Jul 08 '17 at 16:04