@mickey1129

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

python matplotlibのツールバー使用時クリックした点の座標を取得したいがglobal変数にしない方法がありますか

解決したいこと

pythonでcsvデータをグラフ表示し、グラフ上の点をクリックした時にカーソルの座標値を取得しようとしています。matplotlibのツールバーを使用し、panかzoomのボタンが押された時は座標値取得のクリックを無効、ボタンが押されていない時に座標値取得としたいです。
取得した座標値をglobal変数とする事で座標値を取得できるようになったのですが、global変数は使用しない方がよいとの記事を見ました。global変数を用いないで座標値を取得できる方法をご教示頂けないでしょうか。
pythonを使い始めた初心者なので、その他にもコードの書き方等ご指導を頂けると幸甚です。

該当するソースコード

import tkinter as tk
import tkinter.filedialog
from tkinter import ttk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg \
    import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.backend_bases import (_Mode)

def main():
    RC = ReadCSV()
    datCSV, datNum = RC.set_data()
    
    PA = PlotAll(datCSV, datNum)
    PA.plot()
    
    print("クリックした点の座標は、X座標:", PUx, "、 Y座標:", PUy)
    
class ReadCSV:
# 実際はCSVデータの読込み
# 検討用データを入力しておく
    def __init__(self):
        self.datCSV = []

    def set_data(self):
        self.datCSV = [(0.000, 1.000), (0.007,-0.468), (0.013,-0.438), \
            (0.020, 0.819), (0.027,-0.383), (0.033,-0.358), (0.040, 0.670), \
            (0.047,-0.314), (0.053,-0.293), (0.060, 0.549), (0.067,-0.257), \
            (0.073,-0.240), (0.080, 0.449), (0.087,-0.210), (0.093,-0.197), \
            (0.100, 0.368), (0.107,-0.172), (0.113,-0.160), (0.120, 0.301), \
            (0.127,-0.141), (0.133,-0.132), (0.140, 0.247), (0.147,-0.115), \
            (0.153,-0.108), (0.160, 0.202), (0.167,-0.094), (0.173,-0.088), \
            (0.180, 0.165), (0.187,-0.077), (0.193,-0.072), (0.200, 0.135)]
        self.datCSV = tuple(self.datCSV)
        self.datNum = 31
        return self.datCSV, self.datNum

class PlotAll:
# 全データをグラフに表示
# クリックした点の座標を取得する
    def __init__(self, dC, dN):
        self.datCSV   = dC
        self.datNum   = dN
        self.plot_x = []
        self.plot_y = []
        
    def setData(self):
        for n in range(self.datNum):
            self.plot_x.append(self.datCSV[n][0])
            self.plot_y.append(self.datCSV[n][1])

    def plot(self):
        self.setData()

        self.fig, self.ax = plt.subplots()
        self.ax.plot(self.plot_x, self.plot_y)

        self.win = tk.Tk()
        self.win.withdraw()
        self.win.protocol('WM_DELETE_WINDOW', lambda: self.clickX(self.win))
    
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.win)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack()
        self.toolbar = \
                NavigationToolbarPlotAll(self.fig, self.win, self.canvas)

        self.win.update()
        self.win.deiconify()
        self.win.mainloop()

    def clickX(self, win):
        self.win = win
        global PUx
        global PUy
        PUx = 0
        PUy = 0
        self.destroyWinAll(self.win)

    def destroyWinAll(self, win):
        self.win = win
        self.win.quit()
        self.win.destroy()

class NavigationToolbarPlotAll(NavigationToolbar2Tk):
# matplotlibのツールバーを利用
# panかzoomの時はクリックを無効にする
# ボタンが押されていなければクリックした点の座標を取得する
    toolitems = [t for t in NavigationToolbar2Tk.toolitems \
                    if t[0] in ('Home', 'Back', 'Forward', 'Pan', 'Zoom')]

    def __init__(self, fig, win, canvas):
        super().__init__(canvas, win)
        self.fig    = fig
        self.win    = win
        self.canvas = canvas
        self.pressevent_cid = \
            self.fig.canvas.mpl_connect('button_press_event', \
                lambda event: self.clickSP(event, self.win))

    def pan(self):
        super().pan()
        if self.mode == _Mode.PAN:
            self.fig.canvas.mpl_disconnect(self.pressevent_cid)
        else:
            self.fig.canvas.mpl_connect("button_press_event", \
                lambda event: self.clickSP(event, self.win))

    def zoom(self):
        super().zoom()
        if self.mode == _Mode.ZOOM:
            self.fig.canvas.mpl_disconnect(self.pressevent_cid)
        else:
            self.fig.canvas.mpl_connect("button_press_event", \
                lambda event: self.clickSP(event, self.win))

    def clickSP(self, event, win):
        self.win = win
        global PUx
        global PUy
        x_val, y_val = (event.xdata, event.ydata)
        PUx = x_val
        PUy = y_val
        self.destroyWinAll(self.win)

    def destroyWinAll(self, win):
        self.win = win
        self.win.quit()
        self.win.destroy()

if __name__ == "__main__":
    main()
0 likes

1Answer

 global変数は使用しない方がよいとの記事を見ました。

 協業によるコードメンテナンスの関係でglobal変数反対派は多いのは事実です。残念なことに、メンテナンスされないアプリも多いことも事実です。生産性の尺度が現在なのか?未来なのか?の違いではないでしょうか?

 さて、グローバル変数以外に、クラス変数、インスタンス変数があります。積極的にインスタンス変数を利用してもよいのでは?

class PlotAll と class NavigationToolbarPlotAll を一つにするか? 継承できるならインスタンス変数ばかりでなく、メソッドとプロパティを共有できる思います。

global PUx
global PUy

 尚、pythonのグローバル変数はローカル側から、その場しのぎに(一時的に)スコープを広げる宣言をしているものです。一見、面倒ですね!
 他の言語は宣言した時点で、永続的に全体で利用できます。私は、別にpythonのグローバル変数に抵抗はないです。

訂正、クラスでプログラミングするなら、グローバル変数はクラスの継承のメリットを激減します。global変数反対派に一票!

1Like

Comments

  1. @mickey1129

    Questioner

    HalHarada様、
    ご丁寧にご教示頂きありがとうございます。
    協業でプログラミングされる方はグローバル変数反対派が多いとの事、私のように個人で使用する場合は気にする必要はなさそうですね。クラス変数とインスタンス変数についてはもっと勉強します。
    グローバル変数はクラスの継承のメリットを激減します、という一文はまだ理解できていません。まだまだ基本が理解できていないようです。これからも日々精進します。
  2. @mickey1129

    Questioner

    HalHarada様、
    class PlotAll と class NavigationToolbarPlotAll を一つにし、インスタンス変数を使用してグローバル変数をなくす事ができました。グローバル変数は使用しないようにすべきか、クラス継承のメリットについてはまだ理解できていませんので、これからも勉強を続け理解できるように精進します。
    ご教示ありがとうございました。

Your answer might help someone💌