【问题标题】:Get legend as a separate picture in Matplotlib在 Matplotlib 中获取图例作为单独的图片
【发布时间】:2010-12-26 16:09:53
【问题描述】:

我正在开发一个 Web 应用程序,并希望在页面的不同位置显示一个图形及其图例。这意味着我需要将图例保存为单独的 png 文件。这在 Matplotlib 中是否可能以一种或多或少直接的方式实现?

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    您可以使用bbox_inchesfig.savefig 的参数将图形的保存区域限制在图例的边界框内。下面是函数的版本,您可以使用要保存为参数的图例简单地调用该函数。您可以在此处使用原始图中创建的图例(然后将其删除,legend.remove()),也可以为图例创建一个新图并直接使用该函数。

    导出图例边界框

    如果要保存完整的图例,提供给bbox_inches 参数的边界框将只是图例的转换边界框。如果图例周围没有边框,这很有效。

    import matplotlib.pyplot as plt
    
    colors = ["crimson", "purple", "gold"]
    f = lambda m,c: plt.plot([],[],marker=m, color=c, ls="none")[0]
    handles = [f("s", colors[i]) for i in range(3)]
    labels = colors
    legend = plt.legend(handles, labels, loc=3, framealpha=1, frameon=False)
    
    def export_legend(legend, filename="legend.png"):
        fig  = legend.figure
        fig.canvas.draw()
        bbox  = legend.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
        fig.savefig(filename, dpi="figure", bbox_inches=bbox)
    
    export_legend(legend)
    plt.show()
    

    导出扩展图例边界框

    如果图例周围有边框,则上述解决方案可能不是最理想的。在这种情况下,将边界框扩展一些像素以将边框完全包含在内是有意义的。

    import numpy as np
    import matplotlib.pyplot as plt
    
    colors = ["crimson", "purple", "gold"]
    f = lambda m,c: plt.plot([],[],marker=m, color=c, ls="none")[0]
    handles = [f("s", colors[i]) for i in range(3)]
    labels = colors
    legend = plt.legend(handles, labels, loc=3, framealpha=1, frameon=True)
    
    def export_legend(legend, filename="legend.png", expand=[-5,-5,5,5]):
        fig  = legend.figure
        fig.canvas.draw()
        bbox  = legend.get_window_extent()
        bbox = bbox.from_extents(*(bbox.extents + np.array(expand)))
        bbox = bbox.transformed(fig.dpi_scale_trans.inverted())
        fig.savefig(filename, dpi="figure", bbox_inches=bbox)
    
    export_legend(legend)
    plt.show()
    

    【讨论】:

      【解决方案2】:

      可以使用axes.get_legend_handles_labels 从一个axes 对象中获取图例句柄和标签,并使用它们将它们添加到不同图形中的轴。

      # create a figure with one subplot
      fig = plt.figure()
      ax = fig.add_subplot(111)
      ax.plot([1,2,3,4,5], [1,2,3,4,5], 'r', label='test')
      # save it *without* adding a legend
      fig.savefig('image.png')
      
      # then create a new image
      # adjust the figure size as necessary
      figsize = (3, 3)
      fig_leg = plt.figure(figsize=figsize)
      ax_leg = fig_leg.add_subplot(111)
      # add the legend from the previous axes
      ax_leg.legend(*ax.get_legend_handles_labels(), loc='center')
      # hide the axes frame and the x/y labels
      ax_leg.axis('off')
      fig_leg.savefig('legend.png')
      

      如果出于某种原因您只想隐藏坐标轴标签,您可以使用:

      ax.xaxis.set_visible(False)
      ax.yaxis.set_visible(False)
      

      或者,如果出于某种奇怪的原因,您想隐藏坐标轴框架,但不隐藏您可以使用的坐标轴标签:

      ax.set_frame_on(False)
      

      ps:这个答案改编自我对duplicate question的回答

      【讨论】:

      • 此方法对于 ax.fill_between 失败。例如,如果我在第一个 fig.save 调用之前插入行 ax.fill_between([1,2,3,4,5], [2,3,4,5,6], y2=[1,2,3,4,5], label='test2'),那么 ax_leg.legend 会抛出以下错误:RuntimeError: Can not put single artist in more than one figure 你能想到这种情况的解决方案吗?
      • 我认为这是一个很好的问题。如果您详细说明问题,您肯定会得到一些答案。我不知道为什么会失败
      【解决方案3】:

      灵感来自 Maxim 和 ImportanceOfBeingErnest 的答案,

      def export_legend(ax, filename="legend.pdf"):
          fig2 = plt.figure()
          ax2 = fig2.add_subplot()
          ax2.axis('off')
          legend = ax2.legend(*ax.get_legend_handles_labels(), frameon=False, loc='lower center', ncol=10,)
          fig  = legend.figure
          fig.canvas.draw()
          bbox  = legend.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
          fig.savefig(filename, dpi="figure", bbox_inches=bbox)
      

      这允许我将图例水平保存在单独的文件中。举个例子

      【讨论】:

        【解决方案4】:

        这可以工作:

        import pylab
        fig = pylab.figure()
        figlegend = pylab.figure(figsize=(3,2))
        ax = fig.add_subplot(111)
        lines = ax.plot(range(10), pylab.randn(10), range(10), pylab.randn(10))
        figlegend.legend(lines, ('one', 'two'), 'center')
        fig.show()
        figlegend.show()
        figlegend.savefig('legend.png')
        

        【讨论】:

        • 很好,或者使用figlegend.legend(ax.get_legend_handles_labels()[0], ax[get_legend_handles_labels()[1]) 例如如果在 for 循环中将多个绘图添加到 ax
        【解决方案5】:

        在 2020 年 11 月,我几乎尝试了这篇文章中的所有内容,但没有一个对我有用。经过一段时间的挣扎,我找到了一个我想要的解决方案。

        假设您想分别绘制如下图所示的图形和图例(显然我没有足够的声誉在帖子中嵌入图片;点击链接查看图片)。

        import matplotlib.pyplot as plt
        %matplotlib inline
        
        fig, ax = plt.subplots()
        
        ax.plot([1, 2, 3], label="test1")
        ax.plot([3, 2, 1], label="test2")
        
        ax.legend()
        

        target figure

        您可以将图形和图例分隔在两个不同的斧头对象中:

        fig, [ax1, ax2] = plt.subplots(1, 2)
        
        ax1.plot([1, 2, 3], label="test1")
        ax1.plot([3, 2, 1], label="test2")
        
        ax2.plot([1, 2, 3], label="test1")
        ax2.plot([3, 2, 1], label="test2")
        h, l = ax2.get_legend_handles_labels()
        ax2.clear()
        ax2.legend(h, l, loc='upper left')
        ax2.axis('off')
        

        fixed figure 1

        您可以轻松控制图例的位置:

        fig, [ax1, ax2] = plt.subplots(2, 1)
        
        ax1.plot([1, 2, 3], label="test1")
        ax1.plot([3, 2, 1], label="test2")
        
        ax2.plot([1, 2, 3], label="test1")
        ax2.plot([3, 2, 1], label="test2")
        h, l = ax2.get_legend_handles_labels()
        ax2.clear()
        ax2.legend(h, l, loc='upper left')
        ax2.axis('off')
        

        fixed figure 2

        【讨论】:

          【解决方案6】:

          我发现最简单的方法就是创建你的图例,然后用plt.gca().set_axis_off() 关闭axis

          # Create a color palette
          palette = dict(zip(['one', 'two'], ['b', 'g']))
          # Create legend handles manually
          handles = [mpl.patches.Patch(color=palette[x], label=x) for x in palette.keys()]
          # Create legend
          plt.legend(handles=handles)
          # Get current axes object and turn off axis
          plt.gca().set_axis_off()
          plt.show()
          

          【讨论】:

            【解决方案7】:

            我无法在现有答案中准确找到我想要的,所以我实现了它。它想生成一个独立的图例,没有任何附加的图形,也没有视觉上的“故障”。我想出了这个:

            import numpy as np
            import matplotlib.pyplot as plt
            from matplotlib.patches import Patch
            
            
            palette = dict(zip(['one', 'two', 'tree', 'four'], ['b', 'g', 'r', 'k']))
            
            def export_legend(palette, dpi="figure", filename="legend.png"):
                # Create empty figure with the legend
                handles = [Patch(color=c, label=l) for l, c in palette.items()]
                fig = plt.figure()
                legend = fig.gca().legend(handles=handles, framealpha=1, frameon=True)
            
                # Render the legend
                fig.canvas.draw()
            
                # Export the figure, limiting the bounding box to the legend area,
                # slighly extended to ensure the surrounding rounded corner box of
                # is not cropped. Transparency is enabled, so it is not an issue.
                bbox  = legend.get_window_extent().padded(2)
                bbox = bbox.transformed(fig.dpi_scale_trans.inverted())
                fig.savefig(filename, dpi=dpi, transparent=True, bbox_inches=bbox)
            
                # Delete the legend along with its temporary figure
                plt.close(fig)
            
            export_legend(palette, dpi=400)
            

            请注意,周围的背景是透明的,因此在图形顶部添加图例不应在角落出现白色“毛刺”,也不应出现裁剪边框的问题。

            如果你不想保存磁盘映像,这里有窍门!

            DPI = 400
            
            def export_legend(palette):
                # Create empty figure with the legend
                handles = [Patch(color=c, label=l) for l, c in palette.items()]
                fig = plt.figure()
                legend = fig.gca().legend(handles=handles, framealpha=1, frameon=True)
            
                # Render the legend
                fig.canvas.draw()
            
                # Export the figure, limiting the bounding box to the legend area,
                # slighly extended to ensure the surrounding rounded corner box of
                # is not cropped. Transparency is enabled, so it is not an issue.
                bbox = legend.get_window_extent().padded(2)
                bbox_inches = bbox.transformed(fig.dpi_scale_trans.inverted())
                bbox_inches = bbox.from_extents(np.round(bbox_inches.extents * 400) / 400)
                io_buf = io.BytesIO()
                fig.savefig(io_buf, format='rgba', dpi=DPI, transparent=True, bbox_inches=bbox_inches)
                io_buf.seek(0)
                img_raw = io_buf.getvalue()
                img_size = (np.asarray(bbox_inches.bounds)[2:] * DPI).astype(int)
            
                # Delete the legend along with its temporary figure
                plt.close(fig)
            
                return img_raw, img_size
            

            可以使用PIL 或任何处理原始缓冲区的方法直接读取原始缓冲区。

            【讨论】:

            • 像魅力一样工作!
            【解决方案8】:

            使用pylab.figlegend(..)get_legend_handles_labels(..)

            import pylab, numpy 
            x = numpy.arange(10)
            
            # create a figure for the data
            figData = pylab.figure()
            ax = pylab.gca()
            
            for i in xrange(3):
                pylab.plot(x, x * (i+1), label='line %d' % i)
            
            # create a second figure for the legend
            figLegend = pylab.figure(figsize = (1.5,1.3))
            
            # produce a legend for the objects in the other figure
            pylab.figlegend(*ax.get_legend_handles_labels(), loc = 'upper left')
            
            # save the two figures to files
            figData.savefig("plot.png")
            figLegend.savefig("legend.png")
            

            虽然以自动方式正确获取图例的大小可能会很棘手。

            【讨论】:

            • 应该有 figData.save... 而不是 figPlot.save... 很好的例子。
            【解决方案9】:

            我想为自定义图例的特定情况添加一点贡献,例如:https://matplotlib.org/3.1.1/gallery/text_labels_and_annotations/custom_legends.html

            在这种情况下,您可能必须采用不同的方法。我遇到过这个问题,上面的答案对我不起作用。

            下面的代码设置图例。

                import cmocean
                import matplotlib
                from matplotlib.lines import Line2D
            
                lightcmap = cmocean.tools.lighten(cmo.solar, 0.7)
                custom_legend = []
                custom_legend_strings=['no impact - high confidence', 'no impact - low confidence', 'impact - low confidence', 'impact - high confidence']
            
                for nbre_classes in range(len(custom_legend_strings)):
                    custom_legend.append(Line2D([0], [0], color=lightcmap(nbre_classes/len(custom_legend_strings)), lw=4))
                       
            

            我认为因为这种传说是附在轴上的,所以需要一个小技巧:

            以大字体居中图例,使其占用大部分可用空间,并且不擦除但将轴设置为关闭:

                fig,ax = plt.subplots(figsize=(10,10))
                ax.legend(custom_legend,custom_legend_strings, loc = 10, fontsize=30)
                plt.axis('off')
                fig.savefig('legend.png', bbox_inches='tight')
            

            结果是:

            the result

            【讨论】:

              【解决方案10】:

              这会自动计算图例的大小。如果mode == 1,代码类似于Steve Tjoa 的答案,而mode == 2 类似于Andre Holzner 的答案。

              loc 参数必须设置为 'center' 以使其工作(但我不知道为什么这是必要的)。

              mode = 1
              #mode = 2
              
              import pylab
              fig = pylab.figure()
              if mode == 1:
                  lines = fig.gca().plot(range(10), pylab.randn(10), range(10), pylab.randn(10))
                  legend_fig = pylab.figure(figsize=(3,2))
                  legend = legend_fig.legend(lines, ('one', 'two'), 'center')
              if mode == 2:
                  fig.gca().plot(range(10), pylab.randn(10), range(10), pylab.randn(10), label='asd')
                  legend_fig = pylab.figure()
                  legend = pylab.figlegend(*fig.gca().get_legend_handles_labels(), loc = 'center')
              legend.get_frame().set_color('0.70')
              legend_fig.canvas.draw()
              legend_fig.savefig('legend_cropped.png',
                  bbox_inches=legend.get_window_extent().transformed(legend_fig.dpi_scale_trans.inverted()))
              legend_fig.savefig('legend_original.png')
              

              原始(未裁剪)图例:

              裁剪图例:

              【讨论】:

              • 这很好,但似乎为我剪掉了图例文本的顶部。
              • @joeln,你是对的,这确实有点过分了。您可以通过获取剪裁的边界框并将其扩展一个因子(在这种情况下,宽度和高度大 1.1 倍)来解决此问题:bbox = legend.get_window_extent().transformed(legend_fig.dpi_scale_trans.inverted()); ll, ur = bbox.get_points(); x0, y0 = ll; x1, y1 = ur; w, h = x1 - x0, y1 - y0; x1, y1 = x0 + w * 1.1, y0 + h * 1.1; bbox = matplotlib.transforms.Bbox(np.array(((x0, y0),(x1, y1)))); legend_fig.savefig('filename', bbox_inches=bbox)
              • @joeln,您能否重现答案中包含的数字?它们看起来是正确的。
              • @RichardLaw,我尝试了你的方法,但图例框架的左边框仍然被裁剪。还必须扩展左下角(x0 = x1 - w*1.1,y0 = y1 - h*1.1,其中 x1 和 y1 是扩展前的值)才能使其工作。如果您的解决方案也包含在答案中,那就太好了。
              【解决方案11】:

              所以我在玩这个想法,我发现最简单的事情是这个(适用于多个轴):

              def export_legend(filename=legend.png, fig=fig):
                      legend = fig.legend(framealpha=1)
              
                      fig2  = legend.figure
                      fig2.canvas.draw()
                      bbox  = legend.get_window_extent().transformed(fig2.dpi_scale_trans.inverted())
                      fig2.savefig(filename, dpi="figure", bbox_inches=bbox, facecolor="w")
                      legend.remove() # removes legend from showing on plot
              
                  export_legend()
              

              函数的输出(我用方框隐藏了标签):

              fig 来自fig, ax = plt.subplots()

              如果您希望图例仍显示在图上,您可以使用(例如):

              fig.legend(loc="upper right", bbox_to_anchor=(1, 1), bbox_transform=ax.transAxes)

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2016-11-27
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多