Python Forum

Full Version: How to instantly update the plot by getting values from a Scale widget?
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2 3
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()
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()
(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.
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.
(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?
Write a function (or method) to do the plotting. Call the function/method from from openwindow() and update(). Never have duplicate code.
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()
(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?
It can be done either way
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)
Pages: 1 2 3