Posts: 10
Threads: 1
Joined: May 2022
May-16-2022, 07:48 PM
(This post was last modified: May-17-2022, 01:25 AM by OLE.)
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()
Posts: 6,798
Threads: 20
Joined: Feb 2020
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.
Posts: 10
Threads: 1
Joined: May 2022
(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
Posts: 6,798
Threads: 20
Joined: Feb 2020
May-16-2022, 08:38 PM
(This post was last modified: May-16-2022, 08:38 PM by deanhystad.)
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/
Posts: 10
Threads: 1
Joined: May 2022
(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?
Posts: 6,798
Threads: 20
Joined: Feb 2020
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
Posts: 10
Threads: 1
Joined: May 2022
May-16-2022, 11:15 PM
(This post was last modified: May-17-2022, 01:24 AM by OLE.)
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()
Posts: 6,798
Threads: 20
Joined: Feb 2020
May-17-2022, 12:24 AM
(This post was last modified: May-17-2022, 01:22 AM by deanhystad.)
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()
Posts: 10
Threads: 1
Joined: May 2022
May-17-2022, 01:46 AM
(This post was last modified: May-17-2022, 01:46 AM by OLE.)
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()
Posts: 6,798
Threads: 20
Joined: Feb 2020
May-17-2022, 03:52 PM
(This post was last modified: May-17-2022, 05:22 PM by deanhystad.)
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.
|