【问题标题】:Python Matplotlib Multi-color Legend EntryPython Matplotlib 多色图例入口
【发布时间】:2015-08-09 21:23:45
【问题描述】:

我想让 matplotlib 中的图例条目看起来像这样:

对于给定的图例项,它有多种颜色。代码如下所示,它输出一个红色矩形。我想知道我需要做什么才能将一种颜色覆盖在另一种颜色之上?还是有更好的解决方案?

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt

red_patch = mpatches.Patch(color='red', label='Foo')
plt.legend(handles=[red_patch])

plt.show()

【问题讨论】:

    标签: python matplotlib legend


    【解决方案1】:

    我提出的解决方案是将两个不同的proxy-artists 组合为一个条目图例,如下所述:Combine two Pyplot patches for legend

    然后策略是将第一个方形标记的fillstyle 设置为left,而将另一个设置为right(请参阅http://matplotlib.org/1.3.0/examples/pylab_examples/filledmarker_demo.html)。然后可以将两种不同的颜色分配给每个标记,以生成所需的双色图例条目。

    下面的代码显示了如何做到这一点。请注意,plt.legend 中的 numpoints=1 参数很重要,因为它只为每个条目显示一个标记。

    import matplotlib.pyplot as plt
    
    plt.close('all')
    
    #---- Generate a Figure ----
    
    fig = plt.figure(figsize=(4, 4))
    ax = fig.add_axes([0.15, 0.15, 0.75, 0.75])
    ax.axis([0, 1, 0, 1])
    
    #---- Define First Legend Entry ----
    
    m1, = ax.plot([], [], c='red' , marker='s', markersize=20,
                  fillstyle='left', linestyle='none')
    
    m2, = ax.plot([], [], c='blue' , marker='s', markersize=20,
                  fillstyle='right', linestyle='none')
    
    #---- Define Second Legend Entry ----
    
    m3, = ax.plot([], [], c='cyan' , marker='s', markersize=20,
                  fillstyle='left', linestyle='none')
    
    m4, = ax.plot([], [], c='magenta' , marker='s', markersize=20,
                  fillstyle='right', linestyle='none')
    
    #---- Plot Legend ----
    
    ax.legend(((m2, m1), (m3, m4)), ('Foo', 'Foo2'), numpoints=1, labelspacing=2,
              loc='center', fontsize=16)
    
    plt.show(block=False)
    

    结果:

    免责声明:这仅适用于双色图例条目。如果需要两种以上的颜色,除了@jwinterm (Python Matplotlib Multi-color Legend Entry) 描述的方法之外,我想不出任何其他方法来做到这一点

    【讨论】:

      【解决方案2】:

      也许另一种 hack 可以处理两个以上的补丁。确保根据列数对手柄/标签进行排序:

      from matplotlib.patches import Patch
      import matplotlib.pyplot as plt
      
      fig, ax = plt.subplots()
      
      pa1 = Patch(facecolor='red', edgecolor='black')
      pa2 = Patch(facecolor='blue', edgecolor='black')
      pa3 = Patch(facecolor='green', edgecolor='black')
      #
      pb1 = Patch(facecolor='pink', edgecolor='black')
      pb2 = Patch(facecolor='orange', edgecolor='black')
      pb3 = Patch(facecolor='purple', edgecolor='black')
      
      ax.legend(handles=[pa1, pb1, pa2, pb2, pa3, pb3],
                labels=['', '', '', '', 'First', 'Second'],
                ncol=3, handletextpad=0.5, handlelength=1.0, columnspacing=-0.5,
                loc='center', fontsize=16)
      
      plt.show()
      

      导致:

      【讨论】:

      • 到目前为止,这是最容易应用的。
      【解决方案3】:

      实际上有一个适当的方法可以通过实现自定义来做到这一点 图例处理程序,如 matplotlib-doc 中的说明,“实现自定义图例处理程序” (here):

      import matplotlib.pyplot as plt
      from matplotlib.collections import PatchCollection
      
      # define an object that will be used by the legend
      class MulticolorPatch(object):
          def __init__(self, colors):
              self.colors = colors
              
      # define a handler for the MulticolorPatch object
      class MulticolorPatchHandler(object):
          def legend_artist(self, legend, orig_handle, fontsize, handlebox):
              width, height = handlebox.width, handlebox.height
              patches = []
              for i, c in enumerate(orig_handle.colors):
                  patches.append(plt.Rectangle([width/len(orig_handle.colors) * i - handlebox.xdescent, 
                                                -handlebox.ydescent],
                                 width / len(orig_handle.colors),
                                 height, 
                                 facecolor=c, 
                                 edgecolor='none'))
      
              patch = PatchCollection(patches,match_original=True)
      
              handlebox.add_artist(patch)
              return patch
      
      
      # ------ choose some colors
      colors1 = ['g', 'b', 'c', 'm', 'y']
      colors2 = ['k', 'r', 'k', 'r', 'k', 'r']
      
      # ------ create a dummy-plot (just to show that it works)
      f, ax = plt.subplots()
      ax.plot([1,2,3,4,5], [1,4.5,2,5.5,3], c='g', lw=0.5, ls='--',
              label='... just a line')
      ax.scatter(range(len(colors1)), range(len(colors1)), c=colors1)
      ax.scatter([range(len(colors2))], [.5]*len(colors2), c=colors2, s=50)
      
      # ------ get the legend-entries that are already attached to the axis
      h, l = ax.get_legend_handles_labels()
      
      # ------ append the multicolor legend patches
      h.append(MulticolorPatch(colors1))
      l.append("a nice multicolor legend patch")
      
      h.append(MulticolorPatch(colors2))
      l.append("and another one")
      
      # ------ create the legend
      f.legend(h, l, loc='upper left', 
               handler_map={MulticolorPatch: MulticolorPatchHandler()}, 
               bbox_to_anchor=(.125,.875))
      

      【讨论】:

        【解决方案4】:

        可能不完全是您正在寻找的东西,但您可以(非常)手动地通过在情节上放置补丁和文本来完成。例如:

        import matplotlib.patches as mpatches
        import matplotlib.pyplot as plt
        
        fig, ax = plt.subplots()
        
        red_patch = mpatches.Patch(color='red', label='Foo')
        plt.legend(handles=[red_patch])
        
        r1 = mpatches.Rectangle((0.1, 0.1), 0.18, 0.1, fill=False)
        r2 = mpatches.Rectangle((0.12, 0.12), 0.03, 0.06, fill=True, color='red')
        r3 = mpatches.Rectangle((0.15, 0.12), 0.03, 0.06, fill=True, color='blue')
        ax.add_patch(r1)
        ax.add_patch(r2)
        ax.add_patch(r3)
        ax.annotate('Foo', (0.2, 0.13), fontsize='x-large')
        
        plt.show()
        

        【讨论】:

          【解决方案5】:

          我非常喜欢@raphael 的回答。 这是一个带圆圈的版本。此外,我对代码进行了一些重构和修剪,使其更加模块化。

          import matplotlib.pyplot as plt
          import matplotlib.colors as mcolors
          
          class MulticolorCircles:
              """
              For different shapes, override the ``get_patch`` method, and add the new
              class to the handler map, e.g. via
          
              ax_r.legend(ax_r_handles, ax_r_labels, handlelength=CONF.LEGEND_ICON_SIZE,
                      borderpad=1.2, labelspacing=1.2,
                      handler_map={MulticolorCircles: MulticolorHandler})
              """
          
              def __init__(self, face_colors, edge_colors=None, face_alpha=1,
                           radius_factor=1):
                  """
                  """
                  assert 0 <= face_alpha <= 1, f"Invalid face_alpha: {face_alpha}"
                  assert radius_factor > 0, "radius_factor must be positive"
                  self.rad_factor = radius_factor
                  self.fc = [mcolors.colorConverter.to_rgba(fc, alpha=face_alpha)
                             for fc in face_colors]
                  self.ec = edge_colors
                  if edge_colors is None:
                      self.ec = ["none" for _ in self.fc]
                  self.N = len(self.fc)
          
              def get_patch(self, width, height, idx, fc, ec):
                  """
                  """
                  w_chunk = width / self.N
                  radius = min(w_chunk / 2, height) * self.rad_factor
                  xy = (w_chunk * idx + radius, radius)
                  patch = plt.Circle(xy, radius, facecolor=fc, edgecolor=ec)
                  return patch
          
              def __call__(self, width, height):
                  """
                  """
                  patches = []
                  for i, (fc, ec) in enumerate(zip(self.fc, self.ec)):
                      patch = self.get_patch(width, height, i, fc, ec)
                      patches.append(patch)
                  result = PatchCollection(patches, match_original=True)
                  #
                  return result
          
          
          class MulticolorHandler:
              """
              """
              @staticmethod
              def legend_artist(legend, orig_handle, fontsize, handlebox):
                  """
                  """
                  width, height = handlebox.width, handlebox.height
                  patch = orig_handle(width, height)
                  handlebox.add_artist(patch)
                  return patch
          

          示例用法和图片,注意一些图例句柄有radius_factor=0.5,因为真实尺寸太小了。

          ax_handles, ax_labels = ax.get_legend_handles_labels()
          ax_labels.append(AUDIOSET_LABEL)
          ax_handles.append(MulticolorCircles([AUDIOSET_COLOR],
                                              face_alpha=LEGEND_SHADOW_ALPHA))
          ax_labels.append(FRAUNHOFER_LABEL)
          ax_handles.append(MulticolorCircles([FRAUNHOFER_COLOR],
                                              face_alpha=LEGEND_SHADOW_ALPHA))
          ax_labels.append(TRAIN_SOURCE_NORMAL_LABEL)
          ax_handles.append(MulticolorCircles(SHADOW_COLORS["source"],
                                              face_alpha=LEGEND_SHADOW_ALPHA))
          ax_labels.append(TRAIN_TARGET_NORMAL_LABEL)
          ax_handles.append(MulticolorCircles(SHADOW_COLORS["target"],
                                              face_alpha=LEGEND_SHADOW_ALPHA))
          ax_labels.append(TEST_SOURCE_ANOMALY_LABEL)
          ax_handles.append(MulticolorCircles(DOT_COLORS["anomaly_source"],
                                              radius_factor=LEGEND_DOT_RATIO))
          ax_labels.append(TEST_TARGET_ANOMALY_LABEL)
          ax_handles.append(MulticolorCircles(DOT_COLORS["anomaly_target"],
                                              radius_factor=LEGEND_DOT_RATIO))
          #
          ax.legend(ax_handles, ax_labels, handlelength=LEGEND_ICON_SIZE,
                      borderpad=1.1, labelspacing=1.1,
                      handler_map={MulticolorCircles: MulticolorHandler})
          

          【讨论】:

          • 一直在寻找如何为单个标签制作彩色圆圈。非常适合我!
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-26
          • 2012-04-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-12-30
          相关资源
          最近更新 更多