Posts: 10
Threads: 1
Joined: May 2022
May-15-2022, 09:32 PM
(This post was last modified: May-15-2022, 09:32 PM by OLE.)
I have a 3d grid system of 3*4*10 grids (nx*ny*nz), and I have assigned a random value to each grid. I want to plot different surfaces of nx*ny (or let's call it a layer). I have written this code below to do that. In this code, the layer number is obtained from a Scale widget. This code works well and when I change the slider and plot it again, the corresponding surface plots in a top-level window.
Now, I am looking for a way to update the plot instantly using the slider. I want to plot the initial layer using the button and while the top-level window is open, I want to update the plot by moving the slider in the already opened window. I know there are some ways such as .trace() method or .after() method to instantly update the results but I cannot figure it out how to use them in this case.
import tkinter as tk
import numpy as np
from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib.colors import ListedColormap
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
class Data:
def __init__(self):
self.layer = tk.IntVar()
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
container = tk.Frame(self)
container.pack()
self.data = Data()
self.frames = {}
for F in (page1,):
frame = F(container, self.data)
self.frames[F] = frame
frame.pack()
def show_frame(self, c):
frame = self.frames[c]
frame.tkraise()
class page1(tk.Frame):
def __init__(self, parent, data):
super().__init__(parent)
self.data = data
frame1 = tk.Frame(self, width=200)
frame1.pack()
label1 = tk.Label(frame1, text="Layer number:")
label1.grid(row=0, column=0, padx=10)
self.h_slider1 = tk.Scale(frame1, from_=1, to=10, orient="horizontal", length=100, resolution=1, variable=self.data.layer)
self.h_slider1.grid(row=0, column=1)
self.button = tk.Button(frame1, text="plot", command=self.plot)
self.button.grid(row=1, column=0, columnspan=2, pady=20)
np.random.seed(2)
self.num = list(np.random.randint(low=1, high=10, size=120))
def plot(self):
self.top = tk.Toplevel()
nx = 3
ny = 4
num_reshaped = np.array(self.num).reshape(10, nx*ny)
layer = num_reshaped[self.data.layer.get()-1:self.data.layer.get(), :]
layer = layer.reshape(nx, ny)
print(layer)
x, y = np.mgrid[slice(0, nx+1, 1), slice(0, ny+1, 1)]
self.figure = Figure(figsize=(4, 4))
ax = self.figure.add_subplot(111)
col_type = cm.get_cmap('rainbow', 256)
newcolors = col_type(np.linspace(0, 1, 1000))
white = np.array([1, 1, 1, 1])
newcolors[:1, :] = white
newcmp = ListedColormap(newcolors)
c = ax.pcolormesh(x, y, layer, cmap=newcmp, edgecolor='lightgrey', linewidth=0.003)
ax.figure.colorbar(c)
canvas = FigureCanvasTkAgg(self.figure, self.top)
canvas.draw()
canvas.get_tk_widget().pack()
self.top.mainloop()
app = SampleApp()
app.mainloop()
Posts: 1,145
Threads: 114
Joined: Sep 2019
You might can use this approach
import tkinter as tk
class Window:
def __init__(self, parent):
self.slidervar = tk.IntVar()
self.slider = tk.Scale(parent, from_=1, to=10, orient='horizontal')
self.slider['variable'] = self.slidervar
self.slider.pack()
self.button = tk.Button(parent)
self.button['text'] = 'Open Window'
self.button.pack()
class TopWindow:
def __init__(self):
window = tk.Toplevel(None)
window.geometry('200x200+500+300')
self.label = tk.Label(window)
self.label.pack()
class Controller:
def __init__(self, window):
self.window = window
self.window.button['command'] = self.openwindow
self.window.slider['command'] = self.update
def openwindow(self):
self.topwindow = TopWindow()
self.topwindow.label['text'] = self.window.slidervar.get()
def update(self, event):
self.topwindow.label['text'] = self.window.slidervar.get()
if __name__ == '__main__':
root = tk.Tk()
root.geometry('200x200+250+250')
Controller(Window(root))
root.mainloop()
Posts: 10
Threads: 1
Joined: May 2022
(May-16-2022, 02:40 AM)menator01 Wrote: You might can use this approach
import tkinter as tk
class Window:
def __init__(self, parent):
self.slidervar = tk.IntVar()
self.slider = tk.Scale(parent, from_=1, to=10, orient='horizontal')
self.slider['variable'] = self.slidervar
self.slider.pack()
self.button = tk.Button(parent)
self.button['text'] = 'Open Window'
self.button.pack()
class TopWindow:
def __init__(self):
window = tk.Toplevel(None)
window.geometry('200x200+500+300')
self.label = tk.Label(window)
self.label.pack()
class Controller:
def __init__(self, window):
self.window = window
self.window.button['command'] = self.openwindow
self.window.slider['command'] = self.update
def openwindow(self):
self.topwindow = TopWindow()
self.topwindow.label['text'] = self.window.slidervar.get()
def update(self, event):
self.topwindow.label['text'] = self.window.slidervar.get()
if __name__ == '__main__':
root = tk.Tk()
root.geometry('200x200+250+250')
Controller(Window(root))
root.mainloop()
Thanks for your reply. Actually, I am a little bit new to tkinter. I remember I could show the values got from the slider in a wop-level window. But updating the plot instantly was my main problem. besides that, I don't really want to change my classes.
Posts: 6,798
Threads: 20
Joined: Feb 2020
It is the same solution. Look at how the slider command is set to call a function. Change the function from updating a label to one that updates your plot.
Posts: 10
Threads: 1
Joined: May 2022
May-16-2022, 03:52 PM
(This post was last modified: May-16-2022, 04:00 PM by OLE.)
(May-16-2022, 12:35 PM)deanhystad Wrote: It is the same solution. Look at how the slider command is set to call a function. Change the function from updating a label to one that updates your plot.
Then, should I have the same code both in openwindow() and update()? one for the first plot and the same code for updating the plot?
Could you please show me how to do that? I am a little bit confused about how things work in these classes?
Posts: 6,798
Threads: 20
Joined: Feb 2020
Write a function (or method) to do the plotting. Call the function/method from from openwindow() and update(). Never have duplicate code.
Posts: 1,145
Threads: 114
Joined: Sep 2019
Maybe this will get you started. I've not ever worked with matplotlib or numpy but, anyway, here's my attempt
import tkinter as tk
import numpy as np
from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib.colors import ListedColormap
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
np.random.seed(2)
def plot(var, window):
num = list(np.random.randint(low=1, high=10, size=120))
nx, ny = 3, 4
reshaped = np.array(num).reshape(10, nx*ny)
layer = reshaped[var-1:var, :]
layer = layer.reshape(nx, ny)
x, y = np.mgrid[slice(0, nx+1, 1), slice(0, ny+1, 1)]
figure = Figure(figsize=(4, 4))
ax = figure.add_subplot(111)
col_type = cm.get_cmap('rainbow', 256)
newcolors = col_type(np.linspace(0, 1, 1000))
white = np.array([1, 1, 1, 1])
newcolors[:1, :] = white
newcmap = ListedColormap(newcolors)
c = ax.pcolormesh(x, y, layer, cmap=newcmap, edgecolor='lightgray', linewidth=0.003)
ax.figure.colorbar(c)
canvas = FigureCanvasTkAgg(figure, window)
canvas.draw()
canvas.get_tk_widget().pack()
class Window:
def __init__(self, parent):
self.slidervar = tk.IntVar()
self.slider = tk.Scale(parent, from_=1, to=10, orient='horizontal')
self.slider['variable'] = self.slidervar
self.slider.pack()
self.button = tk.Button(parent)
self.button['text'] = 'Open Window'
self.button.pack()
class TopWindow:
def __init__(self):
self.window = tk.Toplevel(None)
self.window.geometry('+500+300')
self.label = tk.Label(self.window)
self.label.pack()
class Controller:
def __init__(self, window):
self.window = window
self.window.button['command'] = self.openwindow
self.window.slider['command'] = self.update
def openwindow(self):
self.topwindow = TopWindow()
plot(self.window.slidervar.get(), self.topwindow.window)
def update(self, event):
plot(self.window.slidervar.get(), self.topwindow.window)
if __name__ == '__main__':
root = tk.Tk()
root.geometry('200x200+250+250')
Controller(Window(root))
root.mainloop()
Posts: 10
Threads: 1
Joined: May 2022
May-16-2022, 05:41 PM
(This post was last modified: May-16-2022, 05:42 PM by OLE.)
(May-16-2022, 05:14 PM)menator01 Wrote: Maybe this will get you started. I've not ever worked with matplotlib or numpy but, anyway, here's my attempt
import tkinter as tk
import numpy as np
from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib.colors import ListedColormap
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
np.random.seed(2)
def plot(var, window):
num = list(np.random.randint(low=1, high=10, size=120))
nx, ny = 3, 4
reshaped = np.array(num).reshape(10, nx*ny)
layer = reshaped[var-1:var, :]
layer = layer.reshape(nx, ny)
x, y = np.mgrid[slice(0, nx+1, 1), slice(0, ny+1, 1)]
figure = Figure(figsize=(4, 4))
ax = figure.add_subplot(111)
col_type = cm.get_cmap('rainbow', 256)
newcolors = col_type(np.linspace(0, 1, 1000))
white = np.array([1, 1, 1, 1])
newcolors[:1, :] = white
newcmap = ListedColormap(newcolors)
c = ax.pcolormesh(x, y, layer, cmap=newcmap, edgecolor='lightgray', linewidth=0.003)
ax.figure.colorbar(c)
canvas = FigureCanvasTkAgg(figure, window)
canvas.draw()
canvas.get_tk_widget().pack()
class Window:
def __init__(self, parent):
self.slidervar = tk.IntVar()
self.slider = tk.Scale(parent, from_=1, to=10, orient='horizontal')
self.slider['variable'] = self.slidervar
self.slider.pack()
self.button = tk.Button(parent)
self.button['text'] = 'Open Window'
self.button.pack()
class TopWindow:
def __init__(self):
self.window = tk.Toplevel(None)
self.window.geometry('+500+300')
self.label = tk.Label(self.window)
self.label.pack()
class Controller:
def __init__(self, window):
self.window = window
self.window.button['command'] = self.openwindow
self.window.slider['command'] = self.update
def openwindow(self):
self.topwindow = TopWindow()
plot(self.window.slidervar.get(), self.topwindow.window)
def update(self, event):
plot(self.window.slidervar.get(), self.topwindow.window)
if __name__ == '__main__':
root = tk.Tk()
root.geometry('200x200+250+250')
Controller(Window(root))
root.mainloop()
Thanks for your solution. The reason I am confused a little bit, is that this problem is a small example of my bigger code. Some of the parameters in the plot() are called from other classes, and my class's structure is a little bit different than the one you wrote. Should the plot function be always out of the classes or it can be considered within a class?
Posts: 1,145
Threads: 114
Joined: Sep 2019
It can be done either way
Posts: 6,798
Threads: 20
Joined: Feb 2020
The plot function/method will need to know enough to draw the plot. It is likely this will require the plot function be a method of whatever class has access to the plot data. I am going to call this class "Whatever".
Your Whatever class should implement a "scale" method that rescales the plot. You can connect your scale slider to call this method.
class Whatever():
"""I know how to make a plot"""
def plot(self):
"""Does the actual plotting"""
def open_window(self):
"""Draw plot when window is opened"""
self.plot()
def scale_plot(self, scale):
"""Rescale the plot"""
self.plot_scale = scale
self.plot()
# Somewhere else where you are making stuff
whatever = Whatever()
slider = tk.Slider(blah blah blah)
slider['command'] = lambda scale: whatever.scale_plot(scale)
|