How to plot a graph onto a tkinter canvas - python

So for school I'm doing a project where you can trade with shares and changing course and i want to plot a graph which shows the change of the course onto a canvas which is in the same window, as the other buttons and labels, etc. but I couldn't find any working solution on the internet and since I'm not a pro at python I don't understand the concept of figures etc. It'd be very nice, if somebody could help me out with this and give me a way to have a graph on a tkinter canvas which i can plot based of new generated numbers.

Using matplotlib you could do something like this:
from tkinter import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
# plot function is created for
# plotting the graph in
# tkinter window
def plot():
# the figure that will contain the plot
fig = Figure(figsize = (5, 5),
dpi = 100)
# list of squares
y = [i**2 for i in range(101)]
# adding the subplot
plot1 = fig.add_subplot(111)
# plotting the graph
plot1.plot(y)
# creating the Tkinter canvas
# containing the Matplotlib figure
canvas = FigureCanvasTkAgg(fig,
master = window)
canvas.draw()
# placing the canvas on the Tkinter window
canvas.get_tk_widget().pack()
# creating the Matplotlib toolbar
toolbar = NavigationToolbar2Tk(canvas,
window)
toolbar.update()
# placing the toolbar on the Tkinter window
canvas.get_tk_widget().pack()
# the main Tkinter window
window = Tk()
# setting the title
window.title('Plotting in Tkinter')
# dimensions of the main window
window.geometry("500x500")
# button that displays the plot
plot_button = Button(master = window,
command = plot,
height = 2,
width = 10,
text = "Plot")
# place the button
# in main window
plot_button.pack()
# run the gui
window.mainloop()
Output:

Related

tkinter plot auto rescale

I'm trying to create a GUI that will plot data from an array that is updated when I click a button (in this case move a slider), I can get the data to replot, however, the scale doesn't update and I can't work out how to do it. does anyone have any ideas?
the data is created in a function dataMaker and just returns 2 arrays, one of the time increments and the other of random data.
many thanks
import tkinter
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import numpy as np
import dataMaker as DM
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
fig = Figure(figsize=(5, 4), dpi=100)
rt_data = DM.comboP(1, 1000)
sample = rt_data[1]
torque = rt_data[2]
ax = fig.add_subplot()
line, = ax.plot(sample, torque)
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
# pack_toolbar=False will make it easier to use a layout manager later on.
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
canvas.mpl_connect(
"key_press_event", lambda event: print(f"you pressed {event.key}"))
canvas.mpl_connect("key_press_event", key_press_handler)
button_quit = tkinter.Button(master=root, text="Quit", command=root.destroy)
def update_frequency(new_val):
rt_data = DM.comboP(1, 1000)
sample = rt_data[1]
torque = rt_data[2]
line.set_data(sample, torque)
ax.autoscale_view()
# required to update canvas and attached toolbar!
canvas.draw()
slider_update = tkinter.Scale(root, from_=1, to=5, orient=tkinter.HORIZONTAL,
command=update_frequency, label="Frequency [Hz]")
# Packing order is important. Widgets are processed sequentially and if there
# is no space left, because the window is too small, they are not displayed.
# The canvas is rather flexible in its size, so we pack it last which makes
# sure the UI controls are displayed as long as possible.
button_quit.pack(side=tkinter.BOTTOM)
slider_update.pack(side=tkinter.BOTTOM)
toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X)
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True)
tkinter.mainloop()
I've searched the internet but can't find a solution, id like the scale the change to fit the data that's in the array

Size of matplotlib plot in tkinter GUI depends on the primary monitor

I am getting some behaviour I do not understand when I put a matplotlib plot in a tkinter GUI on macOS. The size of the plot seems to depend on whether I have an external monitor plugged in or not.
If the external monitor (27" 1440p) is plugged in, the plot is small; if only the internal monitor (14" 3024x1964) is used, the plot is bigger.
External:
Internal:
To show that the size of the plot is different, here is the GUI opened on the external monitor and dragged onto the internal monitor:
I really do not understand what is going on here as the size of the figure (fig_size in inches) doesn't seem to correspond to anything, but the size of the plot is modulated by changing the dpi parameter.
I would like some way to keep the size of the plot consistent in regard to the rest of the GUI. Note that the size of the plot changes in relation to the 'WIDGET' label.
Minimal working example used to produce the screenshots:
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
from tkinter import ttk
import tkinter as tk
class Application(tk.Frame, object):
def __init__(self, master=None):
# Call baseclass constructor.
super(Application, self).__init__(master)
self.fig_size = (6.5, 4)
self.dpi = 100
self.notebook = ttk.Notebook(root)
self.notebook.pack(pady=10, expand=True)
## Buid window with tabs ##
width = 600
height = 400
self.main = tk.Frame(self.notebook, width=width, height=height)
self.main.pack(fill='both', expand=True)
self.notebook.add(self.main, text='TAB 1')
ttk.Style().map("TNotebook.Tab", foreground=[("selected", "#000000")])
self.initialise_main(self.main)
def initialise_main(self, tab):
lbl = tk.Label(tab, text='WIDGET')
self.fig, self.ax = plt.subplots(
1, 1,
figsize=self.fig_size,
dpi=self.dpi,
)
self.line, = self.ax.plot([], []) # empty graph
self.ax.clear()
self.GUIFig = FigureCanvasTkAgg(self.fig, tab)
self.ax.ticklabel_format(useOffset=False)
lbl.grid(row = 0, column=0)
self.GUIFig.get_tk_widget().grid(row=1, column=0)
if __name__ == '__main__':
root = tk.Tk()
app = Application()
app.master.title('MRE')
app.mainloop()
NOTE: this example may not be completely miminimal but I wasn't sure if the OOP approach would change anything so I wanted to keep that consistent.
I haven't had frame size changing on me, but to keep my plots within the frame, I get the dpi and set figure size relative to that and the frame size, and fill the frame with the plot. I also restrict the frame from changing size by setting self.frame2_fileplot.grid_propagate(False)
# get frame size parameters (update frame parameters first)
self.frame2_fileplot.update()
dpi = self.root.winfo_fpixels('1i')
plotwidth = self.frame2_fileplot.winfo_width() / dpi
plotheight = self.frame2_fileplot.winfo_height() / dpi
# create plot
self.plot_figure_fileplot_main = Figure(figsize = (plotwidth, plotheight),
dpi = dpi, frameon = False, tight_layout = True)

Why is this FigureCanvasTkAgg "Tk widget" has annotations and legend outside of view? How to bring them in view?

I wanna place a pie chart on my Tkinter GUI such that the legend and the labels never reach outside of the displayed widget. Currently, my legend and some of the labels are not shown in full.
This is the code I have:
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
#GUI code
window = tk.Tk()
window.title("Test App")
canvas = tk.Canvas(window,width=1200,height=600)
canvas.grid(columnspan=2,rowspan=4)
figure, axes = plt.subplots()
axes.pie([2,3,6], labels=["Cat","Dog","Leopardddddddddddddddddddddddddddddddddddddddddddddddddddddddd"], normalize = True)
axes.legend(["Cat","Dog","Leopardddddddddddddddddddddddddddddddddddddddddddddddddddddddd"],bbox_to_anchor=(0, 1),fontsize=8)
figCanvas = FigureCanvasTkAgg(figure, master=window)
figCanvas.draw()
figCanvas.get_tk_widget().grid(row=1, column=1, padx=10) #place chart on the tkinter grid
window.mainloop()
I do not want to hardcode something like this:
plt.figure(figsize=(10,5), dpi=100)
Because the length of my labels and the size (both height and width) of my legend may change every time the user opens the application.
Any ideas?

stopping NavigationToolbar2Tk from displaying coordinates

I'm trying to get NavigationToolbar2Tk to stop printing the coordinates in red shown below. I want to stop printing becaause the window resizes when the mouse moves over the figure.
My code follows. Even though Frame10 has grid_propagate(False) it resizes slightly when the mouse is moved over it. I want to stop that. My code is given below.
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
from matplotlib.figure import Figure
import tkinter
root = tkinter.Tk()
root.wm_title('Stop toolbar coords')
root.grid_rowconfigure(0, weight=0)
root.grid_rowconfigure(1, weight=0)
root.grid_rowconfigure(2, weight=0)
root.grid_columnconfigure(0, weight=0)
Frame00 = tkinter.Frame(root,width='3.0i',height='3.0i')
Frame10 = tkinter.Frame(root,width='3.0i',height='1.0i')
Frame00.grid(row=0,column=0)
Frame00.grid_rowconfigure(0,weight=0)
Frame00.grid_columnconfigure(0,weight=0)
# stop frame from expanding to size of widget
Frame00.grid_propagate(False)
Frame10.grid(row=1,column=0)
Frame10.grid_rowconfigure(0,weight=0)
Frame10.grid_columnconfigure(0,weight=0)
# stop frame from expanding to size of widget
Frame10.grid_propagate(False)
# initialize matplotlib figure
fig = Figure(figsize=(2.5,2.5),dpi=100)
ax = fig.gca()
ax.plot([0.1,0.2,0.3],[0.5,0.6,0.7],'bo-')
ax.set_title('Title')
canvas = FigureCanvasTkAgg(fig,master=Frame00)
canvas.draw()
canvas.get_tk_widget().grid(row=0,column=0)
toolbar = NavigationToolbar2Tk(canvas,Frame10)
tkinter.mainloop()
The coordinates are displayed by calling the .set_message() method of the NavigationToolbar2Tk. One way to get rid of this behavior is to override this method:
class Toolbar(NavigationToolbar2Tk):
def set_message(self, s):
pass
# ...
toolbar = Toolbar(canvas, Frame10)

matplotlib's zoom functionality inside a tkinter canvas

I have been trying to transfer some scripts behind a GUI (using Tkinter) and so far have made it thus far that any data that is opened is shown in a Tkinter canvas (using matplotlib to draw it).
The only problem that I have with this is that the standard zoom/scrolling that are in matplotlib (using left mouse button to 'move' the plot and right mouse button to 'zoom') are not accessible in the canvas, basically the functionality of the '4 pointed cross' in the matplotlib plot window.
I think this would require creating my own handlers but I would assume that there has to be a way to use the default handlers of matplotlib? I have also looked at 'scrolling' canvas options as mentioned in this question but those only seem to change the size of the plot area instead of zooming in/out on the data, also I do not want to add any further buttons just to manipulate the plot area.
The bare minimum code that I have currently:
#! /usr/bin/env python
from Tkinter import *
import matplotlib.pyplot as plt
import matplotlib.backends.backend_tkagg as tkagg
import tkFileDialog
class App():
def __init__(self,master):
# VARIABLES
self.inputFile = ""
self.fig = plt.Figure()
self.canvas = tkagg.FigureCanvasTkAgg(self.fig, master = master)
self.canvas.get_tk_widget().pack()
self.canvas.draw()
# FRAME
frame = Frame(master)
master.title("MassyTools 0.1.1 (Alpha)")
# VARIABLE ENTRIES
# BUTTONS
# MENU
menu = Menu(root)
root.config(menu = menu)
filemenu = Menu(menu)
menu.add_cascade(label="File", menu=filemenu)
filemenu.add_command(label="Open Input File", command = self.openFile)
calibmenu = Menu(menu)
menu.add_cascade(label="Calibrate",menu=calibmenu)
calibmenu.add_command(label="Open Calibration File", command = self.openCalibrationFile)
calibmenu.add_command(label="Calibrate", command = self.calibrateData)
def openFile(self):
file_path = tkFileDialog.askopenfilename()
setattr(self,'inputFile',file_path)
data = self.readData()
self.plotData(data)
def openCalibrationFile(self):
print "Place holder for selection of the calibration file"
def calibrateData(self):
print "Place holder for actual calibration"
def readData(self):
x_array = []
y_array = []
with open(self.inputFile,'r') as fr:
for line in fr:
line = line.rstrip('\n')
values = line.split()
x_array.append(float(values[0]))
y_array.append(float(values[1]))
return zip(x_array,y_array)
def plotData(self,data):
x_array = []
y_array = []
for i in data:
x_array.append(i[0])
y_array.append(i[1])
self.fig.clear()
self.axes = self.fig.add_subplot(111)
self.line, = self.axes.plot(x_array,y_array)
self.canvas.draw()
# Stuff that is not being used now but can be useful
"""def openFile(self,number):
name = tkFileDialog.askopenfilename()
ops = {
1: 'deglycoData',
2: 'peptideFile',
3: 'mzML'
}
setattr(self,ops[number],name)
"""
# End of 'stuff'
root = Tk()
app = App(root)
root.mainloop()
So you can affix a NavigationToolbar2TkAgg object to your canvas that will give you all the normal matplotlib methods and tools.
import matplotlib.backends.backend_tkagg as tkagg
# canvas is your canvas, and root is your parent (Frame, TopLevel, Tk instance etc.)
tkagg.NavigationToolbar2TkAgg(canvas, root)
A good example of its usage can be found here: Updating a graphs coordinates in matplotlib.
And an example of how to add custom methods to it can be found here (see class NavSelectToolbar(NavigationToolbar2TkAgg)).

Categories

Resources