如何在创建后修改matplotlib图例?
我可以访问一个图形实例,代码是 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 变量可以被修改,但大写的部分没有更新到屏幕上。我漏掉了什么呢?
4 个回答
如果是我,我会把它放到另一个文本文件里,这样更容易修改和管理,尤其是当你在这之前和之后有很多代码的时候。
要打开一个文件进行写入,我们需要把第二个参数设置为“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()
顺便说一下,输入文本文件的每一行前面都有它的行号。
你在图例中看到的其实是一个 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
,这样可以以某种方式存储这个线条。
你可以再次使用命令 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()
希望这对你有帮助。
我写了一个叫 modify_legend
的函数,它可以在图例创建后对其进行修改。这个函数的主要功能是读取已经创建的图例的所有参数,然后用你提供的键值对参数来更新它,最后再次调用 legend(...)
,传入所有可能的参数。
这样你的问题就能解决了:
import pylab
pylab.plot(0,0,'ro', label = 'one point')
pylab.legend(loc = "lower left")
modify_legend(numpoints = 1)
pylab.show()
下面是 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
对象中读取,而其他一些参数(比如title
和fancybox
)则需要一些“艺术处理”。你可以查看matplotlib.legend.Legend.__init__
来了解具体是怎么做的以及为什么要这样做。 - 关于
fontsize
参数的额外条件是用来覆盖原本创建图例时使用的prop
,因为prop
通常会覆盖fontsize
。 - 我没有测试所有的情况,因为时间不太够(尤其是
bbox_to_anchor
和bbox_transform
参数),所以你可以随意尝试并改进这段代码 :)