【问题标题】:Roundtrip legend往返传奇
【发布时间】:2021-05-27 20:44:47
【问题描述】:

这是一个小代码sn-p:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()

ax.errorbar([1, 2, 3], [1, 1, 1], yerr=[.1, .1, .1], c='orange', label='orange')
ax.legend()
ax.plot([1, 2, 3], [2, 2, 2], c='blue', label='blue')
leg = ax.get_legend()

看起来像这样:

现在,如果给了我leg,我怎么能重现传奇?

我试过了

ax.legend(leg.legendHandles, [i.get_text() for i in leg.get_texts()])

但是,这不会保留制造商信息(注意现在图例中的线只是一条直线,而不是一条带有错误栏的线)

我也试过

ax.legend(*ax.get_legend_handles_labels());

但是,这会添加一个在原始图例中不可见的新行。


编辑

如果原来的情节是

import matplotlib.pyplot as plt

fig, ax = plt.subplots()

ax.errorbar([1, 2, 3], [1, 1, 1], yerr=[.1, .1, .1], c='orange', label='orange')
ax.plot([1, 2, 3], [3, 3, 3], c='green', label='green')
ax.legend()
ax.plot([1, 2, 3], [2, 2, 2], c='blue', label='blue')
leg = ax.get_legend()

,如下所示:

然后我想保留橙色和绿色的线。基本上,我只想保留图例中已经可见的内容,而 ax.get_legend_handles_labels 将所有内容归还给我。

编辑2

leg.legendHandlesax.get_legend_handles_labels() 之间的 1-1 映射可以实现这一点,可以做到吗?

【问题讨论】:

  • @DavidG 在这个特定实例中工作,但根本不是通用的,所以我不能使用它
  • 是通用问题,如何在图例中仅包含使用errorbar 绘制的线(并在图例标记中保留错误栏)?
  • @DavidG 保留图例中的任何内容,可以使用errorbar 绘制线条,也可以使用plot 绘制线条。我已经更新了问题以澄清
  • 如果你用这个plt.legend()替换现有的图例设置,你会得到一个带有绿色、蓝色和错误条的图例,但这就是你想要的吗?

标签: python matplotlib legend legend-properties


【解决方案1】:

leg.legendHandlesax.get_legend_handles_labels() 之间的 1-1 映射可以实现这一点,可以做到吗?

是的,看下面的例子

import matplotlib.pyplot as plt

figure, (axes1, axes2) = plt.subplots(nrows=1, ncols=2, figsize=(8, 4))

orange = axes1.errorbar([1, 2, 3], [1, 1, 1], yerr=[.1, .1, .1], c='orange', label='orange')

axes1.legend()

axes1.plot([1, 2, 3], [2, 2, 2], c='blue', label='blue')

handles, labels = axes1.get_legend_handles_labels()

axes2.legend([handles[1]], [labels[1]])

plt.show()

您还可以创建返回 matplotlib.pyplot.plot() 的图例。

import matplotlib.pyplot as plt

figure, (axes1, axes2) = plt.subplots(nrows=1, ncols=2, figsize=(8, 4))

orange = axes1.errorbar([1, 2, 3], [1, 1, 1], yerr=[.1, .1, .1], c='orange', label='orange')

axes1.legend()

axes1.plot([1, 2, 3], [2, 2, 2], c='blue', label='blue')

axes2.legend([orange], ['orange'])

plt.show()

现在,如果给了我leg,我怎么能重现传奇?

我试过了

ax.legend(leg.legendHandles, [i.get_text() for i in leg.get_texts()])

我在sourcecode of legend 中找到的与句柄相关的属性也是legendHandles。该属性在_init_legend_box() 中初始化。相关代码是

    def _init_legend_box(self, handles, labels, markerfirst=True):
        ...

        text_list = []  # the list of text instances
        handle_list = []  # the list of text instances  <-- I think this might be typo
        handles_and_labels = []

        ...

        for orig_handle, lab in zip(handles, labels):
            handler = self.get_legend_handler(legend_handler_map, orig_handle)
            if handler is None:
                _api.warn_external(
                    "Legend does not support {!r} instances.\nA proxy artist "
                    "may be used instead.\nSee: "
                    "https://matplotlib.org/users/legend_guide.html"
                    "#creating-artists-specifically-for-adding-to-the-legend-"
                    "aka-proxy-artists".format(orig_handle))
                # We don't have a handle for this artist, so we just defer
                # to None.
                handle_list.append(None)
            else:
                textbox = TextArea(lab, textprops=label_prop,
                                   multilinebaseline=True)
                handlebox = DrawingArea(width=self.handlelength * fontsize,
                                        height=height,
                                        xdescent=0., ydescent=descent)

                text_list.append(textbox._text)
                # Create the artist for the legend which represents the
                # original artist/handle.
                handle_list.append(handler.legend_artist(self, orig_handle,
                                                         fontsize, handlebox))
                handles_and_labels.append((handlebox, textbox))

        ...

        self.texts = text_list
        self.legendHandles = handle_list

正如我们所见,handle_list 最终分配给了self.legendHandles。在for循环中将值附加到handle_list,如下所示

# Create the artist for the legend which represents the
# original artist/handle.
handle_list.append(handler.legend_artist(self, orig_handle,
                                         fontsize, handlebox))

# print(f'{orig_handle} - {type(orig_handle}') gives
"""
<ErrorbarContainer object of 3 artists> - <class 'matplotlib.container.ErrorbarContainer'>
"""

评论说Create the artist for the legend which represents the original artist/handle。但是从下面的例子中,我们可以看到返回的艺术家并不代表原始的句柄。我认为这可能是 matplotlib 的一个错误。

import matplotlib.pyplot as plt

figure, (axes1, axes2) = plt.subplots(nrows=1, ncols=2, figsize=(8, 4))

orange = axes1.errorbar([1, 2, 3], [1, 1, 1], yerr=[.1, .1, .1], c='orange', label='orange')

axes1.legend()

axes1.plot([1, 2, 3], [2, 2, 2], c='blue', label='blue')

leg = axes1.get_legend()

handles, labels = axes1.get_legend_handles_labels()

print(f'{orange} - {type(orange)}')
print(f'{handles[1]} - {type(handles[1])}')
print(f'{leg.legendHandles} - {type(leg.legendHandles)}')

"""
<ErrorbarContainer object of 3 artists> - <class 'matplotlib.container.ErrorbarContainer'>
<ErrorbarContainer object of 3 artists> - <class 'matplotlib.container.ErrorbarContainer'>
[<matplotlib.collections.LineCollection object at 0x7f4c79919040>] - <class 'list'>
"""

【讨论】:

  • 你写了axes2.legend([handles[1]], [labels[1]]),但你怎么知道使用[1]而不是[0]
  • @ignoring_gravity 因为我们现在是橙色错误栏的标签,所以我们可以从labelslabels.index("orange") 一样得到这个索引。
  • 如果labels 中有多个元素是"orange" 怎么办?这似乎很脆弱
  • @ignoring_gravity 实际上,我认为重复的标签对其他人理解您的图表没有任何好处。
【解决方案2】:

坐标轴图例ax 存储在ax.legend_ 中。要覆盖它,只需分配ax.legend_ = leg。然后,您需要使用 ax.update_from(ax) 更新轴。

一些示例代码来演示,使用你的情节:

from matplotlib import pyplot as plt


#### SECTION 1 ####

fig, ax = plt.subplots()
ax.errorbar([1, 2, 3], [1, 1, 1], yerr=[.1, .1, .1], c='orange', label='orange')
ax.plot([1, 2, 3], [3, 3, 3], c='green', label='green')
# Modifying your code a little to highlight what ax.legend() returns 
# vs what ax.get_legend() returns
leg_orig = ax.legend()
ax.plot([1, 2, 3], [2, 2, 2], c='blue', label='blue')
leg = ax.get_legend()

# ax.get_legend() is just returning ax.legend_
assert(leg==leg_orig)


#### SECTION 2 ####

# calling ax.legend() overwrites ax.legend_ with a new legend object,
# updates the axes, and returns the new legend object
leg = ax.legend()
assert(leg==ax.legend_)
assert(leg==ax.get_legend())
assert(leg!=leg_orig)


#### SECTION 3 ####

# we can manually overwite and update with the original legend
ax.legend_ = leg_orig
ax.update_from(ax) 

尝试分段运行代码,看看每次更新图例的效果。

重要提示

像 Python 中的大多数可变对象一样,当您覆盖 ax.legend_ 时,您只是覆盖了一个指针,而不是创建一个新对象。该对象独立于ax.legend_ 存在,这意味着您对图例对象所做的任何更改都会影响共享该图例对象的所有轴。

【讨论】:

    【解决方案3】:

    以下函数可以将图例复制到选定的轴:

    import matplotlib.pyplot as plt
    
    def duplicate_legend(leg, axis_to):
        """Duplicate a legend to another axis"""
        labels_in = [l.get_text() for l in leg.texts]
        handles, labels = [], []
        for h, l in zip(*leg.axes.get_legend_handles_labels()):
            if l in labels_in:
                handles.append(h)
                labels.append(l)
                labels_in.remove(l)
        axis_to.legend(handles, labels)
    

    这里是一个例子:

    fig, (ax1, ax2) = plt.subplots(2, 1)
    
    ax1.errorbar([1, 2, 3], [1, 1, 1], yerr=[.1, .1, .1], c='orange', label='orange')
    ax1.plot([1, 2, 3], [3, 3, 3], c='green', label='green')
    ax1.legend()
    ax1.plot([1, 2, 3], [2, 2, 2], c='blue', label='blue')
    leg = ax1.get_legend()
    
    duplicate_legend(leg, ax2)
    plt.show()
    

    在多个标签的情况下,有的隐藏有的可见,只会添加可见的:

    fig, (ax1, ax2) = plt.subplots(2, 1)
    
    ax1.errorbar([1, 2, 3], [1, 1, 1], yerr=[.1, .1, .1], c='orange', label='orange')
    ax1.plot([1, 2, 3], [3, 3, 3], c='green', label='green')
    ax1.plot([1, 2, 3], [4, 4, 4], c='green', label='green')
    ax1.legend()
    ax1.plot([1, 2, 3], [5, 5, 5], c='green', label='green')
    ax1.plot([1, 2, 3], [2, 2, 2], c='blue', label='blue')
    leg = ax1.get_legend()
    
    duplicate_legend(leg, ax2)
    plt.show()
    

    【讨论】:

    • 标签重复怎么办?
    • @ignoring_gravity 你的意思是如果你有重复的标签并且其中一些不可见?
    • 我已经更新了处理重复标签的答案:请看一下 :)
    猜你喜欢
    • 2016-10-27
    • 2014-03-08
    • 2015-04-02
    • 1970-01-01
    • 1970-01-01
    • 2019-04-11
    • 2013-12-28
    • 2018-03-08
    • 2011-02-26
    相关资源
    最近更新 更多