3

I have Tkinter based application for which I want to add a custom button that allows the user to specify the x-axis and y-axis, after which the matplotlib canvas would show the specified axes range.

I have been going through other repo's, questions on SO and blog posts, trying to see how to do this which has gotten me to the point where I can add my custom button, it is able to 'zoom' in on the region of interest (fixed to [15,45] for the test) but it forgets it's initial axes (meaning that the default home and back button can't return to anything before the custom plot_axes button was pressed).

The code that I have for my custom toolbar:

# Custom toolbar
class CustomToolbar(NavigationToolbar2TkAgg):
    def plot_axes(self):
        # This function currently makes it so that the 'original view' is lost
        # TODO Fix the above bug
        self.canvas.figure.axes[0].set_xlim([10,60])
        self.canvas.draw()

    def __init__(self,canvas_,parent_):
        self.toolitems = (
            ('Home', 'Reset original view', 'home', 'home'),
            ('Back', 'Back to previous view', 'back', 'back'),
            ('Forward', 'Forward to next view', 'forward', 'forward'),
            ('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),
            ('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),
            # TODO Get this poor thing a nice gif
            ('Axes', 'Zoom in on region of interest (15-45)', 'subplots', 'plot_axes'),
            ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'),
            ('Save', 'Save the figure', 'filesave', 'save_figure'),
            )
        NavigationToolbar2TkAgg.__init__(self,canvas_,parent_)

So the question would be, how should I change the xlim and ylim values in such a way that the GUI doesn't forget the initial xlim/ylim, ensuring that the home and back buttons still work as intended.

MCVE:

#! /usr/bin/env python

# General imports
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from Tkinter import *
import matplotlib
import random

Xdata = xrange(0,60)
Ydata = random.sample(range(1,100),60)

# Custom toolbar
class CustomToolbar(NavigationToolbar2TkAgg):
    def plot_axes(self):
        # This function currently makes it so that the 'original view' is lost
        # TODO Fix the above bug
        self.canvas.figure.axes[0].set_xlim([15,45])
        self.canvas.draw()

    def __init__(self,canvas_,parent_):
        self.toolitems = (
            ('Home', 'Reset original view', 'home', 'home'),
            ('Back', 'Back to previous view', 'back', 'back'),
            # TODO Get this poor thing a nice gif
            ('Axes', 'Zoom in on region of interest (10-60)', 'subplots', 'plot_axes'),
            )
        NavigationToolbar2TkAgg.__init__(self,canvas_,parent_)

# Functions
def openFile(fig,canvas):
    fig.clear()
    axes = fig.add_subplot(111)
    line, = axes.plot(Xdata,Ydata,label="dummy")
    handles, labels = axes.get_legend_handles_labels()
    fig.legend(handles,labels)
    axes.get_xaxis().get_major_formatter().set_useOffset(False)
    axes.set_xlabel("X")
    axes.set_ylabel("Y")
    canvas.draw()   

# Applicatiom
class App():
    def __init__(self, master):
        # CANVAS
        self.fig = matplotlib.figure.Figure()
        self.canvas = FigureCanvasTkAgg(self.fig, master=master)
        self.toolbar = CustomToolbar(self.canvas, master)
        self.canvas.get_tk_widget().pack(fill=BOTH, expand=YES)
        self.canvas.draw()

        # FRAME
        frame = Frame(master)

        # QUIT
        def close():
            root.destroy()
            root.quit()
        root.protocol("WM_DELETE_WINDOW", lambda: close())

        # MENU
        menu = Menu(master)
        master.config(menu=menu)

        filemenu = Menu(menu, tearoff=0)
        menu.add_cascade(label="File", menu=filemenu)
        filemenu.add_command(label="Open Chromatogram", command=lambda: openFile(self.fig, self.canvas))

# Call the main app
if __name__ == "__main__":
    root = Tk()
    app = App(root)
    root.mainloop()

Additional information:

OS: Win7 Py: 2.7.13 (Anaconda 4.3.0) Libs: Matplotlib 2.0.0

Bas Jansen
  • 3,273
  • 5
  • 30
  • 66
  • 1
    In my tests the "home" and "back" buttons keep working after calling `set_xlim`. Maybe a [mcve] would help to spot what's going on. – Stop harming Monica Aug 23 '17 at 08:49
  • 1
    The edit may provide something complete and verifiable, however I don't think it qualifies as "minimal". If this is indeed an issue, it should be reproducible in roughly 40 lines of code. – ImportanceOfBeingErnest Aug 23 '17 at 11:27
  • 1
    For sure it takes time to create mcves. Better ask a good question tomorrow than an incomplete one today. Also don't forget to give information on the system (os, python and library versions) when updating the question or asking a new one. – ImportanceOfBeingErnest Aug 23 '17 at 11:34
  • @ImportanceOfBeingErnest MCVE of ~70 lines has been added. – Bas Jansen Aug 23 '17 at 12:27
  • store the current view `self.push_current() ` – mirvatJ Aug 23 '17 at 12:47
  • @BasJansen Actually my tests didn't work. They seemed to work because I used the standard pan feature before the extra button, which triggered a spurious (for the purpose of testing) call to `push_current`. – Stop harming Monica Aug 23 '17 at 17:48

1 Answers1

3

It seems to me that you only have to store the current view before changing the limits. I.e. adding self.push_current() might be enough.

class CustomToolbar(NavigationToolbar2TkAgg):
    def plot_axes(self):
        self.push_current()   # <--- add this
        self.canvas.figure.axes[0].set_xlim([15,45])
        self.canvas.draw()

Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • That did indeed solve it; are you aware of any documentation that describes all functions/methods belonging to the class (as the descriptions on http://matplotlib.org/api/backend_bases_api.html?highlight=push_current#matplotlib.backend_bases.NavigationToolbar2.push_current are rather limited)? – Bas Jansen Aug 23 '17 at 16:01
  • Well, the documentation itself is a good start. If that is not sufficient you can always refer to the [source code](https://github.com/matplotlib/matplotlib/blob/7dd60e9db06db42d9afc4d82571f6a765791648f/lib/matplotlib/backend_bases.py#L3051), which usually isn't too hard to understand. Asking a question here is also usually very successful (given that it complies to [ask] and has a mcve in it, e.g. you did have an answer 20 minutes after providing the mcve). – ImportanceOfBeingErnest Aug 23 '17 at 16:08