Search code examples
pythonpython-3.xmatplotlibeventszooming

How can I read new limits from a zoomed matplotlib.pyplot figure?


I have 2 questions, marked Q1, Q2 below. Q1 is the main one, Q2 is of secondary importance. I want to read new limits from a zoomed matplotlib.pyplot figure. That is, I expect something like the following steps take place:

  1. There is a set of data, say y_i where i=0...9999
  2. Full set is drawn with a matplotlib.pyplot.plot command
  3. User looks at the data and zooms into it with the built-in-the-figure-window magnifying-glass operation
  4. When user is happy with the zooming, he/she gives a message (Q1: how?) to the program "I'm happy with these (horizontal) limits, read them and save them"
  5. The code reads in the current horizontal values (Q2: can the figure window remain open? If yes, then steps (6-7) apply)
  6. If the user wants to choose another interval, he/she clicks "reset picture" button and goes back to step (3)
  7. When the user is happy, he/she closes the figure

If Q2 is "no" then steps (6-7) are replaced by another loop where the figure is closed in (5) and re-drawn and start from (1). I would be ok with that, but prefer a "Q2=yes" version.

Based on get the key value when calling matplolib pyplot waitforbuttonpress() I guess plt.waitforbuttonpress() is somehow involved, but I cannot decipher the advice in that link. I have never used the "event" stuff in python. I'm hoping the plot window already has some built-in events in it, can I access them somehow? Example code:

import numpy as np
import matplotlib.pyplot as plt
N = 9999
dt = 0.1 / 180 * np.pi  # 0.1 degree steps
tt = np.arange(0, N*dt, dt)
yy = np.sin(tt) + 0.05 * np.random.random(tt.shape)  # include some noise
fig = plt.figure()
plt.plot(tt,yy)
plt.show()
# now the user zooms in... and questions Q1, Q2 apply.

Solution

  • Ok, I found a solution, although I feel this is quite non-aesthetic. I would very much welcome better solutions. This one works, for both question Q1 and Q2, but has 2 drawbacks: (1) it uses a global variable, which I've been told is bad programming(?), and (2) it gives a deprecation warning. Code modification is motivated from https://matplotlib.org/3.3.1/gallery/widgets/rectangle_selector.html (h/t @tmdavison)

    import numpy as np
    import matplotlib.pyplot as plt
    
    def press_key_in_figure(event):
        global xlimits
        print("You pressed %s" % event.key)
        if event.key == 'a':
            xlimits = ax.get_xlim()  # floats
            print("new x-limits: %.2f %.2f" % xlimits)
        if event.key == 'b':
            ylimits = ax.get_ylim()  # floats
            print("new y-limits: %.2f %.2f" % ylimits)
    
    xlimits = (0, 0)
    N = 9999
    dt = 0.1 / 180 * np.pi  # 0.1 degree steps
    tt = np.arange(0, N*dt, dt)
    yy = np.sin(tt) + 0.05 * np.random.random(tt.shape)  # include some noise
    fig, ax = plt.subplots()
    ax.plot(tt,yy)
    
    fig.canvas.mpl_connect('key_press_event', press_key_in_figure)
    plt.show()
    # now the user zooms in... and questions Q1, Q2 apply.
    print("The new x-limits after the window was closed are", xlimits)
    

    When I run it and zoom the picture, and press "a" or "b" it gives out:

    C:\python\lib\tkinter\__init__.py:1705: MatplotlibDeprecationWarning: Toggling axes navigation from the keyboard is deprecated since 3.3 and will be removed two minor releases later.
      return self.func(*args)
    You pressed a
    new x-limits: 3.62 6.48
    You pressed b
    new y-limits: -0.73 -0.23
    

    I'm not sure what "Toggling axes navigation" means; I'm not toggling anything here? Interestingly, my python --version is 3.7.2 so it seems not to be removed, contrary to what is claimed in the warning.