创建后如何修改matplotlib图例?

13

我可以访问图形实例fig = pylab.gcf(),并且我知道在该图形中有一个图例,我可以通过myLegend = fig.gca().legend_进行访问。现在我想要更改图例的属性。其中一些我可以通过像myLegend.set_frame_on(True)这样的设置器进行访问。

当图例被创建时,它接受许多关键字参数:

 

class matplotlib.legend.Legend(parent, handles, labels, loc=None,   numpoints=None, markerscale=None, scatterpoints=None,   scatteryoffsets=None, prop=None, fontsize=None, borderpad=None,   labelspacing=None, handlelength=None, handleheight=None,   handletextpad=None, borderaxespad=None, columnspacing=None, ncol=1,   mode=None, fancybox=None, shadow=None, title=None, framealpha=None,   bbox_to_anchor=None, bbox_transform=None, frameon=None,   handler_map=None)

如何在图例创建后修改所有关键字参数?

其中一个有问题的参数是numpoints(图例中的标记数,默认为2)。以下是我想要更改它的示例:

这展示了我想要如何编程

import pylab
pylab.plot(0,0,'ro', label = 'one point')
pylab.legend(loc = "lower left")
# no modifications above this line
setattr(pylab.gcf().gca().legend_, 'numpoints',1)
pylab.show()

这展示了我希望它看起来的样子。

import pylab
pylab.plot(0,0,'ro', label = 'one point')
pylab.legend(numpoints = 1, loc = "lower left")
pylab.show()

我已经检查了源代码,发现一个名为numpoint的变量被更改了,但大写字母没有被更新到屏幕上。我错过了什么吗?


我怀疑你可以伸手进去并触发处理程序。 - tacaswell
4个回答

7

我写了一个名为modify_legend的函数,可以在传入参数后修改图例。它会读取已经创建好的图例参数,使用您提供的键值对参数进行更新,并再次调用所有可用参数来重新创建legend(...)

如果按照以下步骤,您的问题将得到解决:

import pylab
pylab.plot(0,0,'ro', label = 'one point')
pylab.legend(loc = "lower left")

modify_legend(numpoints = 1)

pylab.show()

Here's the code for modify_legend:

def modify_legend(**kwargs):
    import matplotlib as mpl

    l = mpl.pyplot.gca().legend_

    defaults = dict(
        loc = l._loc,
        numpoints = l.numpoints,
        markerscale = l.markerscale,
        scatterpoints = l.scatterpoints,
        scatteryoffsets = l._scatteryoffsets,
        prop = l.prop,
        # fontsize = None,
        borderpad = l.borderpad,
        labelspacing = l.labelspacing,
        handlelength = l.handlelength,
        handleheight = l.handleheight,
        handletextpad = l.handletextpad,
        borderaxespad = l.borderaxespad,
        columnspacing = l.columnspacing,
        ncol = l._ncol,
        mode = l._mode,
        fancybox = type(l.legendPatch.get_boxstyle())==mpl.patches.BoxStyle.Round,
        shadow = l.shadow,
        title = l.get_title().get_text() if l._legend_title_box.get_visible() else None,
        framealpha = l.get_frame().get_alpha(),
        bbox_to_anchor = l.get_bbox_to_anchor()._bbox,
        bbox_transform = l.get_bbox_to_anchor()._transform,
        frameon = l._drawFrame,
        handler_map = l._custom_handler_map,
    )

    if "fontsize" in kwargs and "prop" not in kwargs:
        defaults["prop"].set_size(kwargs["fontsize"])

    mpl.pyplot.legend(**dict(defaults.items() + kwargs.items()))

代码注释:

  • 一些参数可以轻松从Legend对象中读取,其他参数(如titlefancybox)需要一些“艺术处理”。您可以查看matplotlib.legend.Legend.__init__了解如何以及为什么这样做。
  • fontsize参数上的额外条件用于覆盖当传入一个prop创建图例时字体大小,因为prop通常会覆盖fontsize
  • 我没有测试所有情况,因为时间不充足(特别是bbox_to_anchorbbox_transform参数),所以请随意尝试并改进代码:)

太好了,谢谢。我不得不注释掉#handler_map = l._custom_handler_map,这一行,因为它在我的图例中不存在(Python 2.7.8,MPL 1.3.1)。 - Juha
如果我没记错的话,在 matplotlib 版本 1.4.0 之前,属性 _custom_handler_map 被称为 _handler_map(详情请见 https://github.com/matplotlib/matplotlib/commit/99f325dcd3f045f92c422d335e3d5be4a72b33f6#diff-58172dfa9d6f39eba6a725389fbf179bL253)。所以如果您在这段被注释的代码中遇到了麻烦,请尝试更改属性名称 :) - Peater de Xel
但是我该如何更改文本? - Oren
这里是一个类似seaborn实现的参考:move_legend - paime

3
您可以再次使用正确的关键字/参数使用pylab.legend命令。这将修改现有的图例而不是创建新的图例。下面是稍作修改后的示例:
import pylab
pylab.plot(0,0,'ro', label = 'one point')
pylab.legend(loc = "lower left")
# Change the number of markers shown in the legend
pylab.legend(numpoints = 1, loc = "lower left")

pylab.show()

希望能对你有所帮助。

1
这是一个不错的方法。但还有一些改进。例如,您会失去图例。可以按照以下步骤操作letit = ax.get_legend().get_title().get_text() ax.legend(shadow=True, facecolor=(0.7,0.75,0.7,0.8))
ax.get_legend().set_title(letit)
- Anabar

1
您在图例中看到的实际上是一个Line2D。在创建该行之后更改numpoints不会更新该行,因此您需要获取Line2D对象的句柄并手动删除其中一个点:
import pylab
pylab.plot(0,0,'ro', label = 'one point')
legend = pylab.legend(loc = "lower left")
markers = legend.get_children()[0].get_children()[1].get_children()[0].get_children()[0].get_children()[0].get_children()[1]
markers.set_data(map(pylab.mean, markers.get_data()))
pylab.show()

get_children()链是必需的,因为matplotlib在几层水平和垂直包装中包裹了该行。上面的片段应该足以给你一个大致的概念,但在现实世界的应用中,获取句柄的首选方法是遵循图例指南中关于图例处理程序的提示并使用自定义的HandlerLine2D以某种方式存储该行。


-1

如果是我,我会将它放入另一个文本文件中进行操作,因为这样更容易更改和跟踪,特别是在此之前和之后有很多代码的情况下。

要打开一个文件进行写入,我们将第二个参数设置为"w"而不是"r"。(如fobj = open("ad_lesbiam.txt", "r"))要将数据实际写入此文件,我们使用文件句柄对象的write()方法。

让我们从一个非常简单和直接的例子开始:

fh = open("example.txt", "w")
fh.write("To write or not to write\nthat is the question!\n")
fh.close()

特别是当你在写文件时,一定不要忘记再次关闭文件句柄。否则,你将面临数据处于不一致状态的风险。
通常情况下,你会使用with语句来读写文件。这样做的好处是,在with执行完缩进块后,文件将自动关闭:
with open("example.txt", "w") as fh:
    fh.write("To write or not to write\nthat is the question!\n")

我们的第一个示例也可以使用 with 语句重写如下:

with open("ad_lesbiam.txt") as fobj:
    for line in fobj:
        print(line.rstrip())

同时读写的示例:

fobj_in = open("ad_lesbiam.txt")
fobj_out = open("ad_lesbiam2.txt","w")
i = 1
for line in fobj_in:
    print(line.rstrip())
    fobj_out.write(str(i) + ": " + line)
    i = i + 1
fobj_in.close()
fobj_out.close()

提醒一下,输入文本文件的每一行都以行号为前缀。


这与问题有什么关系? - Peater de Xel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接