0

I have a simple code to plot a figure. I want to manually change the range for the colorbar. So, I added two Entries and defined a second function change(). I want to make this change for the colorbar to happen instantly without having the second button.

from tkinter import *
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

root = Tk()
root.geometry("500x500")

Max, Min = IntVar(), IntVar()

label1 = Label(root, text="Max")
label1.place(x=10, y=35)

label2 = Label(root, text="Min")
label2.place(x=10, y=60)

entry1 = Entry(root, textvariable=Max, width=5)
entry1.place(x=50, y=35)

entry2 = Entry(root, textvariable=Min, width=5)
entry2.place(x=50, y=60)

def plot():
    global x, y
    x, y = np.mgrid[slice(0, 100), slice(0, 100)]
    z = (x*y)

    figure = Figure(figsize=(4, 4))
    ax = figure.add_subplot(111)

    c = ax.pcolormesh(x, y, z, cmap='YlGn')
    ax.figure.colorbar(c)

    canvas = FigureCanvasTkAgg(figure, root)
    canvas.get_tk_widget().place(x=0, y=80)

def change():
    z = (x*y)
    figure = Figure(figsize=(4, 4))
    ax = figure.add_subplot(111)

    c = ax.pcolormesh(x, y, z, cmap='YlGn', vmin=entry1.get(), vmax=entry2.get())
    ax.figure.colorbar(c)

    canvas = FigureCanvasTkAgg(figure, root)
    canvas.get_tk_widget().place(x=0, y=80)

button1 = Button(root, text="Plot", command=plot)
button1.place(x=30, y=0)

button2 = Button(root, text="change", command=change)
button2.place(x=80, y=0)

root.mainloop()

I found this post Constantly Update Label Widgets From Entry Widgets TKinter, and I tried to use method 2, and I changed the code in this part:

...
def auto():
    c.config(vmin=entry1.get(), vmax=entry2.get())

entry1 = Entry(root, textvariable=Max, width=5)
entry1.place(x=50, y=35)

entry2 = Entry(root, textvariable=Min, width=5)
entry2.place(x=50, y=60)

auto()
...

But as c is a local variable, the code doesn't work. can anybody help me instantly update the colorbar range?

PSB
  • 19
  • 6

2 Answers2

0

So what You want in the end is to change colormap vmin and vmax, when user change Min and Max input. You don't need to constantly update colormap, but just on change of those inputs.

You can do that by tracing input change with update callback.

Here is modified code which does colormap update when Min and Max input is changed:

from tkinter import *

import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure

root = Tk()
root.geometry("500x500")

Max, Min = IntVar(), IntVar()

label1 = Label(root, text="Min")
label1.place(x=10, y=35)

label2 = Label(root, text="Max")
label2.place(x=10, y=60)

vmin_entry = Entry(root, textvariable=Min, width=5)
vmin_entry.place(x=50, y=35)

vmax_entry = Entry(root, textvariable=Max, width=5)
vmax_entry.place(x=50, y=60)

# Define global variables
c, canvas = None, None

def plot():
    global x, y, c, canvas
    x, y = np.mgrid[slice(0, 100), slice(0, 100)]
    z = (x * y)
    figure = Figure(figsize=(4, 4))
    ax = figure.add_subplot(111)

    c = ax.pcolormesh(x, y, z, cmap='YlGn')
    ax.figure.colorbar(c)

    canvas = FigureCanvasTkAgg(figure, root)
    canvas.get_tk_widget().place(x=0, y=80)
    canvas.draw()


def update_colormap(*args, **kwargs):
    global c, canvas
    if c is not None:
        try:
            # Get vmin and vmax
            vmin, vmax = int(vmin_entry.get()), int(vmax_entry.get())
        except ValueError:
            # Could not convert values to int, non integer value
            return
        if vmin > vmax:
            return
        # Set new limits
        c.set_clim(vmin, vmax)
        # Update plot
        canvas.flush_events()
        canvas.draw()


# Trace change of Min and Max and call update_colormap as a callabck
Min.trace("w", update_colormap)
Max.trace("w", update_colormap)

button1 = Button(root, text="Plot", command=plot)
button1.place(x=30, y=0)

root.mainloop()
Domarm
  • 2,360
  • 1
  • 5
  • 17
  • thank you. Actually, I cannot take out the x,y,z out of function. this is just an example I raised to understand the solution. because x,y,x are generating inside the functions. and one thing with this code is that, it initially reads the values for min_val and max_val as the entries are zero initially. – PSB Mar 04 '22 at 15:44
  • Sure, it was just optimization of the code You had in the example. I moved x,y,z back into the function and removed vmax and vmin from the pcolormesh in plot method. – Domarm Mar 04 '22 at 16:00
  • Thanks. Just one more question. If I had introduced my code in a class such as the one in this post https://stackoverflow.com/questions/71343383/updating-the-matplotlib-subplot-figure-by-deleting-replacing-the-old-values/71345133?noredirect=1#comment126121109_71345133, How would I do that? – PSB Mar 04 '22 at 19:09
  • Well, I think to answer this, one really need to know more about what You want in the end. There are plenty of ways how to implement Your code in a class (if it's even necessary). Here in SO You ask the Question and community will answer it. Good start would be trying to follow some simple code with class implementation, trying to implement what You need and then if You need help, You can always aske here. Otherwise this Answer would get off topic. Good luck! – Domarm Mar 04 '22 at 19:20
  • I managed to write it for classes. However, I found a problem with this code. when we set the `vmax` and `vmin` once, the next time we enter a new integer, it forgets the previous value for `vmax` and `vmin` and changes them. you can see that by changing values a couple of time. – PSB Mar 04 '22 at 20:43
  • Isn't it the point, that always only the last value from Min and Max input is used? Or this issues is specific to Your class implementation? – Domarm Mar 04 '22 at 20:48
  • No, the issue exist in this code as well. for example, I enter, `vmax=1000`, and `vmin=500`. Then, if I change the `vmax=1010`, the `vmin` is not the same as before. – PSB Mar 04 '22 at 20:50
  • You are right. It seems to be related to situation when vmin is bigger than vmax. I added check which just `return` if `vmin > vmax`. I updated my answer as well. – Domarm Mar 04 '22 at 20:58
  • thanks alot. it works perfectly now. could you please just replace `vmin, vmax = int(entry1.get()), int(entry2.get())` that if anybody used the code dont confused with them. I cannot edit that. – PSB Mar 04 '22 at 21:01
  • If You meant renaming `entry1` and `entry2`, I changed name to `vmin_entry` and `vmax_entry`. `vmin, vmax = int(vmin_entry.get()), int(vmax_entry.get())` line must stay in the code to work properly. – Domarm Mar 04 '22 at 21:08
0

You can bind the "<Key>" event of the Entry widget to the change function as the callback, this will call the change function whenever, anything is typed within the entry widget.

entry1.bind('<Key>', lambda x : change())

Doing this for both entries would be adding these lines to your code -:

entry1.bind('<Key>', lambda x : change())
entry2.bind('<Key>', lambda x : change())

The full code will become -:

from tkinter import *
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

root = Tk()
root.geometry("500x500")

Max, Min = IntVar(), IntVar()

label1 = Label(root, text="Max")
label1.place(x=10, y=35)

label2 = Label(root, text="Min")
label2.place(x=10, y=60)

entry1 = Entry(root, textvariable=Max, width=5)
entry1.place(x=50, y=35)

entry2 = Entry(root, textvariable=Min, width=5)
entry2.place(x=50, y=60)

def plot():
    global x, y
    x, y = np.mgrid[slice(0, 100), slice(0, 100)]
    z = (x*y)

    figure = Figure(figsize=(4, 4))
    ax = figure.add_subplot(111)

    c = ax.pcolormesh(x, y, z, cmap='YlGn')
    ax.figure.colorbar(c)

    canvas = FigureCanvasTkAgg(figure, root)
    canvas.get_tk_widget().place(x=0, y=80)

def change():
    z = (x*y)
    figure = Figure(figsize=(4, 4))
    ax = figure.add_subplot(111)

    c = ax.pcolormesh(x, y, z, cmap='YlGn', vmin=entry1.get(), vmax=entry2.get())
    ax.figure.colorbar(c)

    canvas = FigureCanvasTkAgg(figure, root)
    canvas.get_tk_widget().place(x=0, y=80)

button1 = Button(root, text="Plot", command=plot)
button1.place(x=30, y=0)

entry1.bind('<Key>', lambda x : change()) # binding the first entry's keypress event to the change function.
entry2.bind('<Key>', lambda x : change()) # binding the second entry's keypress event to the change function.

root.mainloop()

NOTE:

The "<Key>" event's callback is triggered whenever a key is pressed inside the widget, for more info take a look at events and bindings.

typedecker
  • 1,351
  • 2
  • 13
  • 25
  • 1
    Thanks for the reply. It works well. only there is a delay in entering the numbers. for example If I want to enter 1000, there is delay between each digit. we cannot quickly type 1000. – PSB Mar 04 '22 at 15:47
  • I think, the same can perhaps be fixed by refocusing the widget, using `focus_set`. – typedecker Mar 04 '22 at 15:49