Python Forum
How to instantly update the plot by getting values from a Scale widget?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to instantly update the plot by getting values from a Scale widget?
#11
I managed to modify the code using you and menator01's help. I have 2 more questions. how I can destroy the initial canvas. currently, the new canvases stack on each other. I tried to use convas.destroy() and convase.delete() in the update(), but it raises errors.
And, Can I move the slider into the toplevel window and update the plot from there. I mean I keep only the button in main window.
here is the modified code:

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))

        self.button['command'] = self.open
        self.h_slider1['command'] = self.update

    def plot(self, var, window):
        nx = 3
        ny = 4
        num_reshaped = np.array(self.num).reshape(10, nx * ny)
        layer = num_reshaped[var - 1:var, :]
        layer = layer.reshape(nx, ny)
        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)
        self.canvas = FigureCanvasTkAgg(self.figure, window)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack()

    def open(self):
        self.toplevel = tk.Toplevel()
        self.toplevel.geometry('+500+100')
        self.plot(self.data.layer.get(), self.toplevel)

    def update(self, *args):
        self.plot(self.data.layer.get(), self.toplevel)

app = SampleApp()
app.mainloop()
Reply
#12
I'm beginning to think that everything we've said is wrong. You do not want to replot the data each time you move the slider because this will quickly result in hundreds off plots. Instead you want to rescale the axes of the existing plot. Now you have open_window() calling a method that draws the initial plot and scale_plot that changes the the existing plot.
Reply
#13
(May-16-2022, 08:10 PM)deanhystad Wrote: I'm beginning to think that everything we've said is wrong. You do not want to replot the data each time you move the slider because this will quickly result in hundreds off plots. Instead you want to rescale the axes of the existing plot. Now you have open_window() calling a method that draws the initial plot and scale_plot that changes the the existing plot.

Sorry, I didn't quite understand what do you mean by scale_plot
Reply
#14
My earlier post mentioned having a method named "scale_plot" that rescaled the plot. At that time I thought that drawing the initial plot and drawing the rescaled plot would be the same. Now I think they are completely different operations. You are not going to be happy with drawing a new plot every time you move the slider. It is going to be slow and very clunky. What you want to do is redraw the existing plot with different axis scaling or different data (I'm not sure exactly what your slider is supposed to do).

Should be easy to find lots of examples of how to do this. Like this example:

https://www.geeksforgeeks.org/how-to-upd...atplotlib/
Reply
#15
(May-16-2022, 08:32 PM)deanhystad Wrote: My earlier post mentioned having a method named "scale_plot" that rescaled the plot. At that time I thought that drawing the initial plot and drawing the rescaled plot would be the same. Now I think they are completely different operations. You are not going to be happy with drawing a new plot every time you move the slider. It is going to be slow and very clunky. What you want to do is redraw the existing plot with different axis scaling or different data (I'm not sure exactly what your slider is supposed to do).

this plot is an example. this plot and updating that using a slider is for observing the changes in the surface in different layers. The numbers here are random. but in my case, the numbers in each layer have meaningful numbers. So one who moves the slider can quickly see how the values are changing through layers.
By moving the slider, the new values for my case are always within the axis range. so, rescaling is not necessary I believe. but I still didn't get your point on how to redraw that with different data? if the current code is not redrawing?
Reply
#16
I never use colormesh, so I'm not familiar with it. I did find something about animating a colormesh.

https://www.tutorialspoint.com/how-to-an...matplotlib

My guess for scaling a colormesh is you do it much the same way. You keep the QuadMesh object returned by the pcolormesh() call. When you rescale the data you update the coordinates using set_array() and then call canvas.draw() and canvas.flush_events().

From you existing code you'll need to make some of the plotting variables attributes of your class.
c = ax.pcolormesh(x, y, layer, cmap=newcmp, edgecolor='lightgrey', linewidth=0.003)  # Keep this around and give it a better name
The method for rescaling the data will look something like this:
    def update(self):
        # Compute new data
        var = self.data.layer.get()
        nx = 3
        ny = 4
        num_reshaped = np.array(self.num).reshape(10, nx * ny)
        layer = num_reshaped[var - 1:var, :]
        layer = layer.reshape(nx, ny)
        x, y = np.mgrid[slice(0, nx + 1, 1), slice(0, ny + 1, 1)]
        self.c.set_array(x, y)    # Reusing the QuadMesh object from the original plot. Loading in new data
        self.canvas.draw()     # Redraw with new data
        self.canvas.flush_events()  # Not sure if this is needed
Reply
#17
I rewrote them in this way. did you mean it in this way? this time the code doesnt remove the old figures. I didn't understand what is the usage of set_array(x,y)

 def plot(self, var, window):
        global ax, newcmp, nx, ny, x, y, num_reshaped
        nx = 3
        ny = 4
        num_reshaped = np.array(self.num).reshape(10, nx * ny)
        layer = num_reshaped[var - 1:var, :]
        layer = layer.reshape(nx, ny)
        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)
        self.canvas = FigureCanvasTkAgg(self.figure, window)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack()

    def open(self):
        self.toplevel = tk.Toplevel()
        self.toplevel.geometry('+500+100')
        self.plot(self.data.layer.get(), self.toplevel)

    def update(self, *args):
        var = self.data.layer.get()
        layer = num_reshaped[var - 1:var, :]
        layer = layer.reshape(nx, ny)
        c = ax.pcolormesh(x, y, layer, cmap=newcmp, edgecolor='lightgrey', linewidth=0.003) 
        ax.figure.colorbar(c)
        #c.set_array(x, y)
        self.canvas.draw()  # Redraw with new data
        #self.canvas.flush_events()
Reply
#18
I reworked the colormesh animation I mentioned above to use a scale object in a tkinter window.
import numpy as np
from matplotlib import pyplot as plt, animation
import tkinter as tk

plt.rcParams["figure.figsize"] = [7.00, 3.50]
plt.rcParams["figure.autolayout"] = True

# This is the plot that you call when first opening the plot window.
fig, ax = plt.subplots()
x = np.linspace(-3, 3, 91)
t = np.linspace(0, 25, 30)
y = np.linspace(-3, 3, 91)
X3, Y3, T3 = np.meshgrid(x, y, t)
sinT3 = np.sin(2 * np.pi * T3 / T3.max(axis=2)[..., np.newaxis])
G = (X3 ** 2 + Y3 ** 2) * sinT3
cax = ax.pcolormesh(x, y, G[:-1, :-1, 0], vmin=-1, vmax=1, cmap='Blues')
fig.colorbar(cax)

plt.show(block=False)

# This is the function called when the slider is moved.
def update_plot(level):
    cax.set_array(G[:-1, :-1, int(level)].flatten())
    fig.canvas.draw()

root = tk.Tk()
slider = tk.Scale(
    root,
    from_=1,
    to=len(t)-1,
    orient="horizontal",
    length=100,
    command=update_plot)
slider.pack()
root.mainloop()
And here it is using a canvas in a tkinter window.
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
 
class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.layer = tk.IntVar(self, 1)

        self.plot()
        canvas = FigureCanvasTkAgg(self.figure, self)
        canvas.draw()
        canvas.get_tk_widget().pack(padx=10, pady=10)
        self.slider = tk.Scale(self, from_=0, to=29, orient="horizontal", length=300, command=self.update_plot)
        self.slider.pack(padx=10, pady=10)

    def plot(self):
        x = np.linspace(-3, 3, 91)
        t = np.linspace(0, 25, 30)
        y = np.linspace(-3, 3, 91)
        X3, Y3, T3 = np.meshgrid(x, y, t)
        sinT3 = np.sin(2 * np.pi * T3 / T3.max(axis=2)[..., np.newaxis])
        self.data = (X3 ** 2 + Y3 ** 2) * sinT3
        self.figure = Figure(figsize=(4, 4))
        ax = self.figure.add_subplot(111)
        self.mesh = ax.pcolormesh(x, y, self.data[:-1, :-1, 0], vmin=-1, vmax=1, cmap='Blues')
        self.figure.colorbar(self.mesh)

    def update_plot(self, level):
        level = int(level)
        self.mesh.set_array(self.data[:-1, :-1, level].flatten())
        self.figure.canvas.draw()

app = SampleApp()
app.mainloop()
Reply
#19
Actually, the previous code worked. I removed this line ax.figure.colorbar( c) in update().
Now I only need to move the slider initially to the toplevel window.

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))

        self.button['command'] = self.open
        self.h_slider1['command'] = self.update

    def plot(self, var, window):
        global ax, newcmp, nx, ny, x, y, num_reshaped
        nx = 3
        ny = 4
        num_reshaped = np.array(self.num).reshape(10, nx * ny)
        layer = num_reshaped[var - 1:var, :]
        layer = layer.reshape(nx, ny)
        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)
        self.canvas = FigureCanvasTkAgg(self.figure, window)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack()

    def open(self):
        self.toplevel = tk.Toplevel()
        self.toplevel.geometry('+500+100')
        self.plot(self.data.layer.get(), self.toplevel)

    def update(self, *args):
        var = self.data.layer.get()
        layer = num_reshaped[var - 1:var, :]
        layer = layer.reshape(nx, ny)
        ax.pcolormesh(x, y, layer, cmap=newcmp, edgecolor='lightgrey', linewidth=0.003)
        self.canvas.draw() 
Reply
#20
Quote:I didn't understand what is the usage of set_array(x,y)
In the animation demo, and my subsequent reuse of that code, set_array(x, y) changes the data displayed in the plot.

This code change the values stored in the QuadMesh object self.mesh.
self.mesh.set_array(self.data[:-1, :-1, level].flatten())
When the plot is redrawn, it displays the new values.
self.figure.canvas.draw()
Your code does something different. Your code creates a new QuadMesh object.
ax.pcolormesh(x, y, layer, cmap=newcmp, edgecolor='lightgrey', linewidth=0.003)
This not only changes the data in the QuadMesh object (actually creates a new QuadMesh object), but it reconfigures the plot. The new plot might have different range. The labels might change.

The result is not much different. You see a plot of the new data. But your code is doing a lot more work then mine. This isn't really noticeable for your application, but it would be very noticeable if you had the plot changing several times a second. If I wanted to make a plot of real-time values, like an oscilloscope, I would not be pleased with the extra delay introduced by reconfiguring the entire plot each time I just added some now signal points.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Update exist plot while change upper limit or lower limit SamLiu 1 949 Feb-05-2023, 10:09 AM
Last Post: SamLiu
Exclamation Update Point Coordinates from Mouse Event in a Plot Jguillerm 2 1,243 Jan-10-2023, 07:53 AM
Last Post: Jguillerm
  [Tkinter] Update matplotlib plot correctly Particledust 0 4,608 Apr-20-2020, 04:59 PM
Last Post: Particledust
  [Tkinter] Scale at the Top Friend 5 2,789 Jul-20-2019, 05:02 PM
Last Post: Yoriz
  Update plot by <Return> bind with entry widget Zorro 1 4,089 Mar-09-2019, 12:27 PM
Last Post: Zorro
  [Tkinter] update the content of Text widget atlass218 10 16,080 Dec-15-2018, 11:51 AM
Last Post: atlass218
  [Tkinter] Update value in Entry widget dannyH 7 27,522 Apr-02-2017, 10:12 AM
Last Post: dannyH
  [Tkinter] Resetting scale value back to 0 after the slider loses focus lwhistler 3 6,871 Mar-07-2017, 10:01 PM
Last Post: Larz60+
  Bokeh - dynamic update widget.Select values sixteenornumber 0 10,207 Dec-28-2016, 02:15 PM
Last Post: sixteenornumber

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020