往返图例

问题描述

这是一个代码片段:

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,2],c='blue',label='blue')
leg = ax.get_legend()

看起来像这样:

enter image description here

现在,如果给了我 leg,我如何重新创造传奇?

我试过了

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

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

enter image description here

我也试过

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

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

enter image description here


编辑

如果原图是

import matplotlib.pyplot as plt

fig,label='orange')
ax.plot([1,[3,3,c='green',label='green')
ax.legend()
ax.plot([1,label='blue')
leg = ax.get_legend()

,看起来像这样:

enter image description here

那么我想同时保留橙色和绿色线条。基本上,我只想保留图例中已经可见的内容,而 ax.get_legend_handles_labels 将所有内容都还给我。

EDIT2

leg.legendHandlesax.get_legend_handles_labels()间的 1-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,2],c='blue',label='blue')

handles,labels = axes1.get_legend_handles_labels()

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

plt.show()

enter image description here

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

import matplotlib.pyplot as plt

figure,label='blue')

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

plt.show()

enter image description here

现在,如果给了我 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,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,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'>
"""
,

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,label='orange')
ax.plot([1,[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,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_ 存在,这意味着您对图例对象所做的任何更改都将影响共享该图例对象的所有轴。

,

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

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,label='orange')
ax1.plot([1,label='green')
ax1.legend()
ax1.plot([1,label='blue')
leg = ax1.get_legend()

duplicate_legend(leg,ax2)
plt.show()

enter image description here

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

fig,label='green')
ax1.plot([1,[4,4,4],[5,5,5],ax2)
plt.show()

enter image description here