簡體   English   中英

在多個matplotlib.pyplot子圖上創建圖例單擊事件

[英]Create legend click events on multiple matplotlib.pyplot subplots

我有幾個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事件也適用於第一個圖形?

原因由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)

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

在診斷和解決方案方面, @ Leporello的答案是完整的。 這是完整的解釋:

lambda ev: onpick(ev, fig)lambda ev: onpick(ev, fig)創建一個函數對象,該函數對象引用名稱onpickfig plt.show()運行之后 ,循環結束后調用該函數。

名稱onpickfig是非本地的,因此它在模塊名稱空間中搜索。 在調用偵聽器時, onpick指的是創建的最后一個函數,而fig指的是在循環的最后一次迭代中創建的圖。

Leporello關於默認參數的建議可能是最優雅的方法。 之所以有效,是因為立即評估了整個def語句。 def會創建一個內部帶有代碼塊的功能對象,然后在此位置將引用分配給默認對象。 這意味着您最終將回調設置為正確的函數對象,並且循環中的任何fig都將成為figu指向的對象。

將名稱onpickfig綁定到回調的本地名稱空間中的任何操作或在循環中創建的任何其他名稱空間的任何操作都可以解決您的問題。

這是一個完美的應用程序,其中使用類可能是有益的。 它將允許將各個圖形存儲在實例變量中,並在類的方法中使用它。

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()

實際上,我發現了一個比ImportanceOfBeingErnest的解決方案簡單一些的解決方案,並且比Leporello的解決方案更有意義,因為它只定義了onpick函數一次,並且每個圖都相同。

由於僅對canvas才需要引用該圖,並且可以在event找到canvas ,因此以下代碼可以完美地工作:

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()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM