質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Q&A

解決済

2回答

1465閲覧

tkinter:グラフが表示できない原因を知りたいです.

Sayuki

総合スコア21

Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

0グッド

1クリップ

投稿2018/11/01 11:23

前提・実現したいこと

エントリから係数を取得し,二次関数(y = ax^2+bx+c)のグラフ出力するプログラムです.
python3でtkinterでインターフェースを作成し,matplotlibを使ってグラフを作成しています.
初めてインターフェースを作成していますが,下の問題で苦戦しています.

発生している問題・エラーメッセージ

グラフが出力されず,困っています.
コードのどこに問題が有るのかを知りたいです.

ソースコードは下に全容を示しています.
よろしくお願いします.

該当のソースコード

python3

1import tkinter as tk 2import numpy as np 3from matplotlib import pyplot as plt 4 5 6#If you're in trouble, refer to "http://www.geocities.jp/m_hiroi/light/pytk01.html". 7 8# Create entries 9entry_w = 8 10entry_y = 30 11 12def MakeGraph(a_box, b_box, c_box, fig, canvas): 13 # Get the coefficients 14 coefficient_a = float(a_box.get()) 15 coefficient_b = float(b_box.get()) 16 coefficient_c = float(c_box.get()) 17 18 # Calcurate the quadratic equation 19 x_min, x_max = -50, 50 20 x = np.linspace(x_min, x_max) 21 y = coefficient_a * x ** 2 + coefficient_b * x + coefficient_c 22 23 ax.plot(x, y, linestyle = "--", color = "black", label = 'y = ax^2 + bx + c') 24 25 graph = plt.savefig("figure.png") 26 graph = tk.PhotoImage(file = "figure.png") 27 28 canvas.create_image(0,0, image = graph) 29 canvas.pack() 30 31 print("Making a graph is finished.") 32 33 return 34 35root = tk.Tk() 36root.title('Visualizer for quadratic curve') 37root.geometry('800x600') 38 39 40a_box = tk.Entry(width = entry_w) 41a_box.insert(tk.END,"1") 42a_box.pack() 43b_box = tk.Entry(width = entry_w) 44b_box.insert(tk.END,"2") 45b_box.pack() 46c_box = tk.Entry(width = entry_w) 47c_box.insert(tk.END,"1") 48c_box.pack() 49 50fig = plt.figure(figsize = (1,1)) 51ax = fig.add_subplot(111) 52 53canvas = tk.Canvas(root) 54 55# Create button 56button = tk.Button(root, text = 'Plot') 57button.bind("<Button-1>", MakeGraph(a_box, b_box, c_box, fig, canvas)) 58button.pack() 59 60 61root.mainloop()

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

とりあえず「ボタンをクリックしたときに画像を表示する」には2か所修正が必要です。

関数オブジェクトと関数の呼び出しを区別してください

bindの第二引数は「イベントを引数にとる関数オブジェクト」を渡さないといけません。

button.bind("<Button-1>", MakeGraph(a_box, b_box, c_box, fig, canvas))

こう書いてしまうとbindの第二引数に渡すべき値をPythonが評価する際、即座にMakeGraph関数が呼び出されてしまいMakeGraphの戻り値(=None)がbindに渡されます。それだとは「ボタンがクリックされたとき何もしない」という意味になってしまいます。

正しくは「eventを引数にとり、呼び出されたときMakeGraphを起動するような関数」をbindに指定します。

python

1# 書き方1 2def on_click(event): 3 MakeGraph(a_box, b_box, c_box, fig, canvas) 4button.bind("<Button-1>", on_click) # on_click(...)と書いちゃダメ 5 6# 書き方2 7button.bind("<Button-1>", 8 lambda event: MakeGraph(a_box, b_box, c_box, fig, canvas))

create_imageの仕様の罠

create_imageのimage引数にPhotoImageインスタンスを指定するとCanvas上に画像が表示されるはずなのですがちょっとわかりにくい制限があります。それは「create_imageのimage引数に指定する画像オブジェクトは必要な期間中(要するにcanvasを表示している間中)アプリケーション自身でその参照を保持しておかなければならない」ことです。

ご質問のコードではMakeGraph関数の中でPhotoImageを生成しそれをgraphというローカル変数へ設定した上でcreate_imageに渡していますね。ところがcreate_imageはPhotoImageインスタンスの参照をどこにも保持しないためMakeGraphの実行が終わるとこのインスタンスは削除されてしまいます。PhotoImageインスタンスが削除されてしまうとたとえそれがcanvas上に配置済みであっても画像が表示されなくなるようです。

対処として一番分かり易いのは「PhotoImageのインスタンスをグローバル変数に覚えておくこと」です。

Python

1... 2 3graph = None 4 5def MakeGraph(a_box, b_box, c_box, fig, canvas): 6 ... 7 8 global graph 9 10 plt.savefig("figure.png") 11 graph = tk.PhotoImage(file="figure.png") 12 13 canvas.create_image(0,0, image = graph) 14 ...

ただこの方法は「分かり易くはあるがグローバル変数の乱立につながるため設計方法としては下策」と思います。小さなアプリケーションのうちはよいでしょうけど。どちらかといえばしかるべきオブジェクト指向的な設計にしてどこか適切なインスタンスのインスタンス変数に覚えておくといった対処の方がよいと思います。

なお、上記の仕様はPythonの公式リファレンスに記載されているtkinterに関する複数の参考ページのどれにも載っているというわけではないので気づきにくいです。自分は同じ現象ではまったときあちこち見て以下のページにようやくこの仕様を見つけましたが「なぜこういう仕様になるの?」と思ってしまいました。

http://effbot.org/tkinterbook/canvas.htmのcreate_imageの項

image=
The image object. This should be a PhotoImage or BitmapImage, or a compatible object (such as the PIL PhotoImage). The application must keep a reference to the image object.

(太字は回答者)

投稿2018/11/02 02:55

KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KSwordOfHaste

2018/11/02 02:57

あ・・・magichanさん回答とかぶってしまいました。
Sayuki

2018/11/02 04:10

回答いただき、ありがとうございました。 参考ページまで載せていただいて、勉強していく上でも助かります。
guest

0

ベストアンサー

修正点は2点

1つ目は button.bind() の第2引数に渡すのは 関数となります。
ところが、現状は
button.bind("<Button-1>", MakeGraph(a_box, b_box, c_box, fig, canvas))
となっておりまして、これは 『MakeGraph() を実行してその戻り値(None)を第2引数に渡す』という意味になってしまいます。
今回は MakeGraph() に複数のパラメータを渡す必用がありそうですので、lambda でWrapして
button.bind("<Button-1>", lambda evt: MakeGraph(a_box, b_box, c_box, fig, canvas))
のように記述するとよいかと思います。

2つ目は MakeGraph() 関数内の
graph = tk.PhotoImage(file = "figure.png")
の処理ですが、 graphローカル変数 ですので、MakeGraph()関数から抜けると削除対象 となります。
その為
canvas.create_image(0,0, image = graph)
を行って Canvasに描画しても、直ぐに消えてしまうという現象が起きてしまいます。
とりあえずの対策としては、 graph をグローバル変数として設定すると良いかと思います。

以上の2点(のみ)を変更したコードを記載しましたので参考にしてください。

動作させてみていただくとお分かりになるかと思いますが、他にもまだ不具合はありそうです。が、とりあえず上記の変更にてグラフが描画されていることは確認できるかと思います。

Python

1import tkinter as tk 2import numpy as np 3from matplotlib import pyplot as plt 4 5 6#If you're in trouble, refer to "http://www.geocities.jp/m_hiroi/light/pytk01.html". 7 8# Create entries 9entry_w = 8 10entry_y = 30 11 12graph = None 13 14def MakeGraph(a_box, b_box, c_box, fig, canvas): 15 global graph 16 17 # Get the coefficients 18 coefficient_a = float(a_box.get()) 19 coefficient_b = float(b_box.get()) 20 coefficient_c = float(c_box.get()) 21 22 # Calcurate the quadratic equation 23 x_min, x_max = -50, 50 24 x = np.linspace(x_min, x_max) 25 y = coefficient_a * x ** 2 + coefficient_b * x + coefficient_c 26 27 ax.plot(x, y, linestyle = "--", color = "black", label = 'y = ax^2 + bx + c') 28 29 graph = plt.savefig("figure.png") 30 graph = tk.PhotoImage(file = "figure.png") 31 32 canvas.create_image(0,0, image = graph) 33 canvas.pack() 34 35 print("Making a graph is finished.") 36 37 return 38 39root = tk.Tk() 40root.title('Visualizer for quadratic curve') 41root.geometry('800x600') 42 43 44a_box = tk.Entry(width = entry_w) 45a_box.insert(tk.END,"1") 46a_box.pack() 47b_box = tk.Entry(width = entry_w) 48b_box.insert(tk.END,"2") 49b_box.pack() 50c_box = tk.Entry(width = entry_w) 51c_box.insert(tk.END,"1") 52c_box.pack() 53 54fig = plt.figure(figsize = (1,1)) 55ax = fig.add_subplot(111) 56 57canvas = tk.Canvas(root) 58 59# Create button 60button = tk.Button(root, text = 'Plot') 61button.bind("<Button-1>", lambda evt: MakeGraph(a_box, b_box, c_box, fig, canvas)) 62button.pack() 63 64root.mainloop()

投稿2018/11/02 02:39

magichan

総合スコア15898

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Sayuki

2018/11/02 04:24 編集

説明が分かりやすくて本当に助かりました。 ありがとうございました。
KSwordOfHaste

2018/11/02 04:50 編集

コメント変更されたようなので自分の発言を取り消します。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問