【问题标题】:Matplotlib Legend with Different Number and Color of Markers per HandleMatplotlib 图例,每个句柄具有不同数量和颜色的标记
【发布时间】:2016-09-16 19:29:04
【问题描述】:

鉴于以下情况:

import pandas as pd
import matplotlib.pyplot as plt
d=pd.DataFrame({'category':['a','a','a','b','b','b'],
                'year':[1,2,1,2,1,2],
                'x':[2,4,5,1,2,3],
                'y':[1,2,3,2,4,6],
                'clr':['grey','green','grey','blue','grey','orange']})
d
    category    clr     x   y   year
0         a     grey    2   1   1
1         a     green   4   2   2
2         a     grey    5   3   1
3         b     blue    1   2   2
4         b     grey    2   4   1
5         b     orange  3   6   2

for i in np.arange(len(d)):
    plt.plot(d.x[i],d.y[i],marker='o',linestyle='none',markerfacecolor=d.clr[i],
             markeredgecolor='none',markersize=15)
#clean up axes
plt.tick_params(axis='x',which='both',bottom='off',top='off',color='none',labelcolor='none')
plt.tick_params(axis='y',which='both',left='off',right='off',color='none',labelcolor='none')
lgnd=plt.legend(['Year 1','Year 2'],
            numpoints=1,
            loc=0,
            ncol=1,
            fontsize=10,
            frameon=False)
lgnd.legendHandles[0]._legmarker.set_markersize(15)
lgnd.legendHandles[1]._legmarker.set_markersize(15)

我希望图例中的第 1 年标记有一个灰点(就像现在一样),但对于第 2 年的标记,每种不同颜色都有一个点(在这种情况下,橙色、蓝色和此时,同一行顺序上的所有绿点都无关紧要,连续)。 像这样:

我尝试了以下方法,但无济于事:

lgnd.legendHandles[1]._legmarker.set_numpoints(len(d.clr.unique()))
lgnd.legendHandles[1]._legmarker.set_markeredgecolor(d.clr)

提前致谢!

【问题讨论】:

    标签: matplotlib legend markers


    【解决方案1】:

    我很高兴找到解决问题的方法(并在此过程中学习了一些新技巧)。本质上,您可以制作自己的图例处理程序对象以将所有颜色映射到一年。可以通过制作任何具有函数legend_artist(self, legend, orig_handle, fontsize, handlebox) 的对象来制作自定义图例处理程序。可以在this page 的“实现自定义处理程序”部分中找到有关此工作原理的详细信息。我把代码中的所有解释都注释了,因为没有代码演示的文字解释太多了。

    示例代码:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import pdb
    
    import matplotlib.patches as mpatches
    
    
    class MyLegendHandler(object):
        def __init__(self,color):
            self.color = color
    
        def legend_artist(self, legend, orig_handle, fontsize, handlebox):
            x0, y0 = handlebox.xdescent, handlebox.ydescent #offset of the lower left corner
            width, height = handlebox.width, handlebox.height #width, height bound box of legend, for now, it is the dimension of each circle legend
            #NOTE: to be practicle, let's just set radius = height as if width != height, it's an ellipse
            #NOTE: these will latter on be changed internally to accomdate adding text
            handlebox.width += len(self.color)*height  # reset width of handlebox to accomodate multiple legends
            for i in range(len(self.color)): #loop through all colors
                #for each color, draw a circle of that color
                #NOTE: play around here to align the legends right rather than left :)
            center = [0.5*(i + 1) * width - 0.5*x0, 0.5 * height - 0.5 * y0]
                patch = mpatches.Ellipse(center, height, height, facecolor=self.color[i],
                                       edgecolor=None, hatch=None, transform=handlebox.get_transform())
    
                handlebox.add_artist(patch)
            return patch
    
    
    ###################################
    
    d=pd.DataFrame({'category':['a','a','a','b','b','b'],
                    'year':[1,2,1,2,1,2],
                    'x':[2,4,5,1,2,3],
                    'y':[1,2,3,2,4,6],
                    'clr':['grey','green','grey','blue','grey','orange']})
    unique_year_elements = []
    years_seen = []
    tmp = None
    my_map = {}
    for i in np.arange(len(d)):
        tmp, = plt.plot(d.x[i],d.y[i],marker='o',linestyle='none',markerfacecolor=d.clr[i],
                 markeredgecolor='none',markersize=15) 
        #collect the plot elements that are of unique years-- 1 year might have several plot element, we only need 1
        if not (d.year[i] in years_seen):
            years_seen.append(d.year[i])
            unique_year_elements.append(tmp)
    
    #build handler_map for plt.legend to map elements to its legend handler object
    for i in np.arange(len(years_seen)):
        color_list = d.loc[d['year'] == years_seen[i]].clr.unique().tolist()
        #pdb.set_trace()
        my_map[unique_year_elements[i]] = MyLegendHandler(color_list)
    
    #creating the legend object
    plt.legend( unique_year_elements, ["Year "+str(y) for y in years_seen],
               handler_map=my_map)
    #clean up axes
    plt.tick_params(axis='x',which='both',bottom='off',top='off',color='none',labelcolor='none')
    plt.tick_params(axis='y',which='both',left='off',right='off',color='none',labelcolor='none')
    
    plt.show()
    

    样本输出:

    【讨论】:

      【解决方案2】:

      另一种对我有用的方法是绘制圆圈(椭圆 - 看看为什么here)和文字:

      import matplotlib.patches as mpatches
      #Set ellipse dimension coordinates
      xmax_el=xmax/30
      ymax_el=ymax/28
      #Set ellipse y-location coordinates
      yloc1=max(ind)+2.5
      yloc2=max(ind)+1.75
      
      #Create first circle in grey as just one grey circle is needed:
      circlex=mpatches.Ellipse((pmax-.2*pmax,yloc1), xmax_el, ymax_el ,alpha=0.5,clip_on=False\
      ,edgecolor='grey',linewidth=2,facecolor='none')
      
      #Start a list of patches (circles), with the grey one being the first:
      patches=[circlex]
      
      clrs=['g','r','b']
      #Populate a list of circles, one for each unique color for patch names
      circles=[]
      for i in np.arange(len(clrs)):
         circles.append('circle'+str(i))
      
      #This list is for multiplying by the x-position dimension to space out the colored bubbles: 
      clrnum=np.arange(len(clrs))
      
      #Reverse the order of colors so they plot in order on the chart (if clrs was based on real data that is being plotted)
      clrs2=clrs[::-1]
      
      #Iterate through the color, circle, and circle number lists, create patches, and plot.
       for i,j,k in zip(clrs2,circles,clrnum):
           j=mpatches.Ellipse((pmax-(.2+k*0.05)*pmax,yloc2),xmax_el,ymax_el,alpha=0.5,clip_on=False,edgecolor=i,linewidth=2,facecolor='none')
           patches.append(j)
      for i in patches:
         ax.add_artist(i)
      
      
      #Add text:
      ax.text(pmax-.15*pmax,yloc1,'2015 Plan Offering',color='grey',ha='left',va='center')
      ax.text(pmax-.15*pmax,yloc2,'2016 Plan Offering',color='grey',ha='left',va='center')
      

      结果:

      【讨论】:

        猜你喜欢
        • 2014-12-16
        • 1970-01-01
        • 2020-11-24
        • 1970-01-01
        • 1970-01-01
        • 2017-05-19
        • 1970-01-01
        • 2017-07-26
        • 1970-01-01
        相关资源
        最近更新 更多