在多个matplotlib.pyplot子图上创建图例单击事件

时间:2019-05-23 07:51:47

标签: python matplotlib

我有几个matplolib.pyplot数字。每个都有一个图例,我想要的是单击图例上的线以隐藏图中的线。点击事件处理位于以下位置:https://matplotlib.org/examples/event_handling/legend_picking.html

当只有一个数字时,此方法工作正常,但当有多个数字时,仅适用于最后一个数字。当我单击另一个图例的图例时,没有任何异常或警告,但是什么也没有发生。

以下是具有此问题的示例代码:

import matplotlib.pyplot as plt
import numpy as np

a = np.arange(0,10,1)
    b = np.arange(0,20,2)
    c = np.arange(0,5,.5)
    d = np.arange(-1,9,1)

    lined = {}

    for var1, var2 in [(a,b), (c,d)]:
        fig, ax = plt.subplots()
        line1, = ax.plot(var1, label="l1")
        line2, = ax.plot(var2, label="l2")
        leg = fig.legend([line1, line2], ["l1", "l2"])
        legl1, legl2 = leg.get_lines()
        legl1.set_picker(5)
        lined[legl1] = line1
        legl2.set_picker(5)
        lined[legl2] = line2

        def onpick(event, figu):
            legl = event.artist
            origl = lined[legl]
            vis = not origl.get_visible()
            origl.set_visible(vis)
            if vis:
                legl.set_alpha(1.0)
            else:
                legl.set_alpha(0.2)
            figu.canvas.draw()

        fig.canvas.mpl_connect('pick_event', lambda ev: onpick(ev, fig))
    plt.show()

如何使click事件也适用于第一个数字?

4 个答案:

答案 0 :(得分:1)

原因由https://docs.python-guide.org/writing/gotchas/#late-binding-closures给出。我必须承认我自己并不完全理解它,但是它提供了解决问题的技巧:使用默认参数。

import matplotlib.pyplot as plt
import numpy as np

a = np.arange(0,10,1)
b = np.arange(0,20,2)
c = np.arange(0,5,.5)
d = np.arange(-1,9,1)

lined = {}

for var1, var2 in [(a,b), (c,d)]:
    fig, ax = plt.subplots()
    line1, = ax.plot(var1, label="l1")
    line2, = ax.plot(var2, label="l2")
    leg = fig.legend([line1, line2], ["l1", "l2"])
    legl1, legl2 = leg.get_lines()
    legl1.set_picker(5)
    lined[legl1] = line1
    legl2.set_picker(5)
    lined[legl2] = line2

    def onpick(event, figu=fig):
        legl = event.artist
        origl = lined[legl]
        vis = not origl.get_visible()
        origl.set_visible(vis)
        if vis:
            legl.set_alpha(1.0)
        else:
            legl.set_alpha(0.2)
        figu.canvas.draw()

    fig.canvas.mpl_connect('pick_event', onpick)  # no need for a lambda
plt.show()

如前所述,该解决方案有点不可靠。比较看似等效的

# works
    def onpick(event, figu=fig):
        (...)
        figu.canvas.draw()  # using a default arg equal to fig
    fig.canvas.mpl_connect('pick_event', onpick)

vs。

# fails as described
    def onpick(event):
        (...)
        fig.canvas.draw()  # using fig from main loop directly
    fig.canvas.mpl_connect('pick_event', onpick)

答案 1 :(得分:1)

@Leporello's answer就诊断和解决方案而言是完整的。这是完整的解释:

lambda ev: onpick(ev, fig)行创建了一个引用名称onpickfig的函数对象。在循环运行plt.show()之后,循环结束后,函数称为

名称onpickfig是非本地的,因此它在模块名称空间中搜索。在调用侦听器时,onpick指的是创建的最后一个函数,fig指的是在循环的最后一次迭代中创建的图形。

Leporello关于默认参数的建议可能是执行此操作最优雅的方法。之所以有效,是因为立即评估了整个def语句。 def创建一个内部带有代码块的功能对象,然后在该位置和那里分配对默认值的引用。这意味着您最终将回调设置为正确的函数对象,而循环内的fig就是figu指向的内容。

任何将名称onpickfig绑定到回调的本地名称空间或循环中创建的任何其他名称空间的操作将解决您的问题。

答案 2 :(得分:0)

这是一个完美的应用程序,其中使用类可能会有所帮助。它将允许将各个图形存储在实例变量中,并在类的方法中使用它。

import matplotlib.pyplot as plt
import numpy as np

class MyPlot():
    def __init__(self, var1, var2):
        self.lined = {}
        self.fig, ax = plt.subplots()
        line1, = ax.plot(var1, label="l1")
        line2, = ax.plot(var2, label="l2")
        leg = self.fig.legend([line1, line2], ["l1", "l2"])
        legl1, legl2 = leg.get_lines()
        legl1.set_picker(5)
        self.lined[legl1] = line1
        legl2.set_picker(5)
        self.lined[legl2] = line2
        self.cid = self.fig.canvas.mpl_connect('pick_event', self.onpick)

    def onpick(self, event):
        legl = event.artist
        if legl in self.lined:
            origl = self.lined[legl]
            vis = not origl.get_visible()
            origl.set_visible(vis)
            if vis:
                legl.set_alpha(1.0)
            else:
                legl.set_alpha(0.2)
            self.fig.canvas.draw()


a = np.arange(0,10,1)
b = np.arange(0,20,2)
c = np.arange(0,5,.5)
d = np.arange(-1,9,1)

plots = [MyPlot(*var) for var in [(a,b), (c,d)]]

plt.show()

答案 3 :(得分:0)

实际上,我找到了一个比ImportanceOfBeingErnest's更简单并且比Leporello's更有意义的解决方案,因为它只定义了一次函数onpick,并且每个图形都相同

由于仅canvas才需要引用该图,并且canvas中可以找到event,因此以下代码可以完美地工作:

import matplotlib.pyplot as plt
import numpy as np

a = np.arange(0,10,1)
    b = np.arange(0,20,2)
    c = np.arange(0,5,.5)
    d = np.arange(-1,9,1)

    lined = {}
    def onpick(event):
            legl = event.artist
            origl = lined[legl]
            vis = not origl.get_visible()
            origl.set_visible(vis)
            if vis:
                legl.set_alpha(1.0)
            else:
                legl.set_alpha(0.2)
            event.canvas.draw()

    for var1, var2 in [(a,b), (c,d)]:
        fig, ax = plt.subplots()
        line1, = ax.plot(var1, label="l1")
        line2, = ax.plot(var2, label="l2")
        leg = fig.legend([line1, line2], ["l1", "l2"])
        legl1, legl2 = leg.get_lines()
        legl1.set_picker(5)
        lined[legl1] = line1
        legl2.set_picker(5)
        lined[legl2] = line2

        fig.canvas.mpl_connect('pick_event', onpick)
    plt.show()