0

I have written a code to use the RangeSldider widget to have control over my axis range.

from tkinter import *
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import cm
from matplotlib.colors import ListedColormap
from RangeSlider.RangeSlider import RangeSliderH, RangeSliderV

root = Tk()
root.geometry("600x600")

def plot():
    x, y = np.mgrid[slice(0, 6, 1), slice(0, 6, 1)]
    z = np.arange(1,26).reshape(5,5)
    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
    newcmp = ListedColormap(newcolors)

    c = ax.pcolormesh(x, y, z, cmap=newcmp, edgecolor='lightgrey', linewidth=0.003)
    ax.figure.colorbar(c)

    ax.set_title('mY Title', fontweight="bold")
    ax.set_xlabel("X", fontsize=14)
    ax.set_ylabel("Y", fontsize=14)

    canvas = FigureCanvasTkAgg(figure, root)
    canvas.get_tk_widget().place(x=100, y=25)
    figure.patch.set_facecolor('#f0f0f0')

    ax.set_xlim(rs1.getValues())
    ax.set_ylim(rs2.getValues())

hVar1 = IntVar() # left handle variable
hVar2 = IntVar()  # right handle variable
rs1 = RangeSliderH(root, [hVar1, hVar2], Width=230, Height=55, padX=17, min_val=0, max_val=5, font_size=12,\
                   show_value=True, digit_precision='.0f', bgColor='#f0f0f0', line_s_color='black',\
                   line_color='black', bar_color_inner='black', bar_color_outer='#f0f0f0')
rs1.place(x=150, y=420)

vVar1 = IntVar()  # top handle variable
vVar2 = IntVar()  # down handle variable
rs2 = RangeSliderV(root, [vVar1, vVar2], Width=81, Height=180, padY=11, min_val=0, max_val=5, font_size=12,\
                   show_value=True, digit_precision='.0f', bgColor='#f0f0f0', line_s_color='black',\
                   line_color='black', bar_color_inner='black', bar_color_outer='#f0f0f0')
rs2.place(x=0, y=150)

button = Button(root, text="Plot", command=plot)
button.pack()

root.mainloop()

I got a suggestion to use the range slider feature provided by matplotlib. I searched in the libraries, and used the FloatRangeSlider. But I don't know how to add the range sliders to show on the canvas and make it work and update the figure instantly.

from tkinter import *
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.colors import ListedColormap

import ipywidgets as widgets

root = Tk()
root.geometry("600x600")

def plot():
    x, y = np.mgrid[slice(0, 6, 1), slice(0, 6, 1)]
    z = np.arange(1,26).reshape(5,5)
    figure = Figure(figsize=(8, 8))
    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
    newcmp = ListedColormap(newcolors)

    c = ax.pcolormesh(x, y, z, cmap=newcmp, edgecolor='lightgrey', linewidth=0.003)
    ax.figure.colorbar(c)

    ax.set_title('mY Title', fontweight="bold")
    ax.set_xlabel("X", fontsize=14)
    ax.set_ylabel("Y", fontsize=14)

    sliderH = widgets.FloatRangeSlider(value=(0, 5), min=0, max=5, step=1, orientation='horizontal')
    sliderV = widgets.FloatRangeSlider(value=(0, 5), min=0, max=5, step=1, orientation='vertical')

    canvas = FigureCanvasTkAgg(figure, root)
    canvas.get_tk_widget().pack()

    def update():
        x, y = np.mgrid[slice(sliderH.value[0], sliderH.value[1], 1), slice(sliderV.value[0], sliderV.value[1], 1)]
        figure.canvas.draw()

    sliderH.observe(update, names='value')
    sliderV.observe(update, names='value')
    display(sliderH)
    display(sliderV)

button = Button(root, text="Plot", command=plot)
button.pack()

root.mainloop()

1 Answers1

0

Unless I misunderstood something, You were not using RangeSLider of matplotlib, but ipywidgets. That library is used for Jupyter notebook.

Here is implementation using RangeSLider of matplotlib with live axis range update:

from tkinter import *

import matplotlib
import numpy as np

matplotlib.use('TkAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import cm
from matplotlib.colors import ListedColormap

import matplotlib.widgets as widgets

root = Tk()
root.geometry("600x600")

x, y = np.mgrid[slice(0, 6, 1), slice(0, 6, 1)]
z = np.arange(1, 26).reshape(5, 5)
figure = Figure(figsize=(8, 8))

# Make figure a bit wider and taller
figure.subplots_adjust(bottom=0.2)
figure.subplots_adjust(left=0.2)
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
newcmp = ListedColormap(newcolors)

c = ax.pcolormesh(x, y, z, cmap=newcmp, edgecolor='lightgrey', linewidth=0.003)
ax.figure.colorbar(c)

ax.set_title('mY Title', fontweight="bold")
ax.set_xlabel("X", fontsize=14)
ax.set_ylabel("Y", fontsize=14)

# Create X slider
slider_ax = figure.add_axes([0.2, 0.05, 0.6, 0.03])
sliderH = widgets.RangeSlider(slider_ax, "Val", valmin=0, valmax=5, valstep=1, valinit=(0, 5))
# Create Y slider
slider_ay = figure.add_axes([0.05, 0.25, 0.03, 0.6])
sliderV = widgets.RangeSlider(slider_ay, "Val", valmin=0, valmax=5, valstep=1, valinit=(0, 5), orientation='vertical')


def set_x_range(value):
    ax.set_xlim(xmin=value[0], xmax=value[1])


def set_y_range(value):
    ax.set_ylim(ymin=value[0], ymax=value[1])


sliderH.on_changed(set_x_range)
sliderV.on_changed(set_y_range)

canvas = FigureCanvasTkAgg(figure, root)
canvas.draw()

button = Button(root, text="Plot", command=canvas.get_tk_widget().pack)
button.pack()

root.mainloop()

This is how it looks:

plot

I had a small issue with vertical slider, that buttons were still going horizontal. Maybe it was only my environment or maybe it's matplotlib bug.
You can solve it by using both sliders in horizontal orientation.

Domarm
  • 2,360
  • 1
  • 5
  • 17
  • 1
    Why is it that `from tkinter import *` constantly pops up? No other library users are so reluctant to change their behavior. Here, you only use `Tk` but import everything [leading to namespace cluttering](https://stackoverflow.com/questions/2386714/why-is-import-bad). – Mr. T Feb 28 '22 at 15:54
  • You are absolutely right. My goal is to answer the question, not enforcing best practices. I've just reuse the code, that the person asking understand. Of course, I would many times rewrite code from the scratch, but that's not the point here. At least that's my opinion, but thanks for the comment. – Domarm Feb 28 '22 at 15:59
  • @Domarm Thank you very much for your help. I have an issue here. I still want to use the button to plot the figure initially. then use the slider that appears next to the figure to set the axis. However, when I use the code in a function, and pass that to the button command, the sliders doesn't move. –  Feb 28 '22 at 18:21
  • Ok, it was indeed the problem. I updated my code. You were missing `canvas.draw()` in Your plot method. But now You have to handle multiple plot button click. I would suggest to just check if canvas exists and if so, reset sliders to default value instead of plotting new widget again. – Domarm Feb 28 '22 at 19:03
  • @Domarm I am sorry. I didn't quite understand. Is that possible to only use the button once? after clicking the button, the figure and the sliders appear on the screen. and by only moving the sliders (and not using the button again) set the axis. –  Feb 28 '22 at 19:26
  • That's what is happening now. You click the button, plot appears and then You move the sliders and change axis range. What happens when You click plot button again, You plot another new plot, which You probably don't want. You might just disable the button after clicked once. Or You can check if plot exists and just return from the function. Or You can reset the plot if You click plot again. You just have to define what should happened when Plot button is clicked again. But it should not draw a new plot. – Domarm Feb 28 '22 at 19:32
  • @Domarm Oh, I got your point. but here in your last code, the sliders don't move. I had the same problem when it goes inside a function the sliders don't move. –  Feb 28 '22 at 19:36
  • Strange, before it wasn't moving for me as well, but after I add `canvas.draw()`, it' was working just fine. I'm using Linux and Python3.8. – Domarm Feb 28 '22 at 19:40
  • mine is python 3.9, and I am running on windows, pycharm –  Feb 28 '22 at 19:44
  • Ok, I've tried one more thing. Instead of creating whole plot dynamically, I just use `command=canvas.get_tk_widget().pack` for button command. Otherwise I have no other ideas how to make it work with plot button. – Domarm Feb 28 '22 at 19:56
  • 1
    wow. This one worked –  Feb 28 '22 at 21:00