0

There are plenty of web examples (1,2,3,4) and threads (1,2,3) about imbedding a plot into a tkinter window, but very few that address plotting in a separate environment and importing the resulting graph to the tkinter window.

In a nutshell, I have a program that calculates many different values, and exports those values to a separate file that creates a large number of plots. My tkinter application accepts parameters in Entry boxes, before applying them to the main file that does all the calculations. Typically, I would just follow the examples I linked, but with such a large number of plots being generated and the need to be able to select any particular graph I need at a given time, this would be inefficient and time consuming to brute-force. There must be a better way!

Below is a simplified example of how I am trying to accomplish this task:


import tkinter as tk
from matplotlib import pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, 
NavigationToolbar2Tk)

import numpy as np


def example_plot(A):
    # Plot generated outside of tkinter environment, but controlled by 
    # variable within tkinter window.
    x = np.linspace(0, 10, 50)
    y = A*x**2
    
    fig, ax = plt.subplots()
    ax.plot(x,y)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    
    return fig


window = tk.Tk()
window.geometry('256x256')
variableEntry = tk.Entry(width = 10)
variableLabel = tk.Label(window, text = "A")


variableEntry.grid(row = 0, column = 0)
variableLabel.grid(row = 0, column = 1)


def plotButton():
    A = variableEntry.get()
    A = int(A)
    
    figure = Figure(figsize = (1,1), dpi = 128)
    add = figure.add_subplot(1,1,1)
    
    example = example_plot(A)
    
    add.imshow(example)
    
    canvas = FigureCanvasTkAgg(figure)
    canvas.get_tk_widget().grid(row = 2, column = 0)
    
    toolbar = NavigationToolbar2Tk(canvas)
    toolbar.update()
    canvas._tkcanvas.grid(row = 3 , column = 0)
    canvas.show()
    

applyButton = tk.Button(master = window, text = "Apply", command = plotButton)
applyButton.grid(row = 1,column = 0)

window.mainloop()

When I run this, set A to some integer and press apply, I get an error

TypeError: Image data of dtype object cannot be converted to float

It seems that add.imshow() doesn't like that I fed it the figure. Is there some way to obtain the figure (ie: example = example_plot(A)) and store it to display later?

t.o.
  • 832
  • 6
  • 20
  • 1
    Try using `figure = example_plot(A)` instead of `example = example_plot(A); add.imshow(example)` – TheLizzard Sep 08 '21 at 17:21
  • Oh that worked, the only issue is I get an error about the navigation toolbar `toolbar = NavigationToolbar2Tk(canvas) TypeError: __init__() missing 1 required positional argument: 'window'`. I tried to add `master = window` but it doesn't recognize that. – t.o. Sep 08 '21 at 17:25
  • Try using `NavigationToolbar2Tk(canvas, window)`. From the [source code](https://github.com/matplotlib/matplotlib/blob/971d1106464560038151bf79d032515cd843b149/lib/matplotlib/backends/_backend_tk.py#L522) - "`def __init__(self, canvas, window, *, pack_toolbar=True)`" – TheLizzard Sep 08 '21 at 17:28
  • I think if I manage the placement with `pack()` rather than `grid()` I can kill that `__init__()` error. Thanks for your help! If you want to submit your response as an answer I'll accept it. – t.o. Sep 08 '21 at 17:39

1 Answers1

1

Try this:

import tkinter as tk
from matplotlib import pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, \
                                              NavigationToolbar2Tk

import numpy as np


def example_plot(A):
    # Plot generated outside of tkinter environment, but controlled by 
    # variable within tkinter window.
    x = np.linspace(0, 10, 50)
    y = A*x*x # This should run slightly faster

    fig, ax = plt.subplots()
    ax.plot(x,y)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    
    return fig


window = tk.Tk()
frame = tk.Frame(window)
frame.pack()
variable_entry = tk.Entry(frame, width=10)
variable_label = tk.Label(frame, text="A")


variable_entry.pack(side="left", fill="x")
variable_label.pack(side="left")


def plot():
    A = int(variable_entry.get())
    
    figure = Figure(figsize=(1, 1), dpi=128)
    add = figure.add_subplot(1, 1, 1)
    
    figure = example_plot(A)
    
    canvas = FigureCanvasTkAgg(figure)
    canvas.get_tk_widget().pack()
    
    toolbar = NavigationToolbar2Tk(canvas, window)
    toolbar.update()
    canvas.get_tk_widget().pack()
    # canvas.show() # There is no need for this
    

apply_button = tk.Button(window, text="Apply", command=plot)
apply_button.pack(fill="x")

window.mainloop()

Your example_plot returns a Figure so you can use figure = example_plot(A) and then FigureCanvasTkAgg(figure). I also added a frame and tried to make everything look better.

TheLizzard
  • 7,248
  • 2
  • 11
  • 31