2

So I have a function that scatter-plots some data and does so by creating new figures. The maximum amount of figures allowed at a time is 20 to avoid memory overload. If the user wants to plot a data-set with 6 variables to be exact, then there would be 30 different figures. Is there a way to wait until the user deletes the necessary amount of figures before adding more?

This is what I've though of:

import matplolib.pyplot as plt
... # some code
# this below is inside a loop structure
f = plt.figure
# add some stuff to the figure
plt.show(block=False)
Halt()  # checks to see if there are too many figures

Where Halt() is defined as such:

def halt():
    first = True
    while plt.gcf().number > 20:  # are there more than 20 figures
        if first:
            # show message
            first  = False
            # time.sleep(100)

The only problem with this is that it "freezes" the program, not allowing the user to exit out of any of the figures, as it is "not responding". I've also tried the time.sleep() but that does not seem work either.

Does anyone know of a good way to loop until a condition is met?

notMyName
  • 690
  • 2
  • 6
  • 17

1 Answers1

1

https://matplotlib.org/api/_as_gen/matplotlib.pyplot.show.html says:

If False ensure that all windows are displayed and return immediately. In this case, you are responsible for ensuring that the event loop is running to have responsive figures.

How to do this, you ask? Well, the documentation is at https://matplotlib.org/users/interactive_guide.html#explicitly-spinning-the-event-loop .

After some fiddling around, I made the following which plots 20 figures with maximum 5 at the same time:

import matplotlib.pyplot as plt
import numpy as np
from time import sleep

def plot_stuff(exponent, titlenum):
    x = np.linspace(0.0, 1.0)
    f = plt.figure()
    ax = f.add_subplot(1, 1, 1)
    ax.set_title('{} - {}'.format(titlenum, exponent))
    ax.plot(x, x**exponent)

def get_fighandles():
    fignumbers = plt.get_fignums()
    return [plt.figure(fign) for fign in fignumbers]

N_figs_eventually_plotted = 20
N_figs_max_simultaneous = 5

N=0
while N < N_figs_eventually_plotted:
    if len(get_fighandles()) < N_figs_max_simultaneous:
        N += 1
        # put here whichever update is needed when you can add new figures
        plot_stuff(np.random.random(), N)
        plt.show(block=False)
    print('hi')
    for fig in get_fighandles():
        print(fig.canvas)
        fig.canvas.flush_events()
        fig.canvas.draw_idle() # might not be needed, but here it's fast
    sleep(0.1)

# note: solution terminates when the last figure is plotted, you might want something to prevent this (for instance a plt.show(block=True) when the last figure is plotted)

There might be some subtle concurrency bugs (for instance, if you close a figure after the loop reads the figure handles but before it flushes the events), but I do not see how you can avoid that for your use case.

Leporello
  • 638
  • 4
  • 12
  • The code works good but if you close a figure not in order, then it crashes. For example, if you close figure 5 instead of figure 1, it crahses with "can't invoke "update" command – notMyName Aug 19 '20 at 18:11
  • I found this question with the same error that I just got:https://stackoverflow.com/questions/46370432/tkinter-tclerror-cant-invoke-update-command-application-has-been-destroyed, seems that it was trying to update a figure, which has been closed. So we need to check if the figure is closed; then remove it from the list – notMyName Aug 19 '20 at 18:22
  • it also seems that if you delete a figure out of order, the get_fighandles() does not update and returns the same list as before, therefore causing the crash – notMyName Aug 19 '20 at 18:29
  • think I just figured it out: inside the `for fig in get_fighandles()` loop check the following statement: `if len(plt.get_fignums()) < 5: break`, this way whenever we delete a figure, even out of order, it breaks out of the for loop, not allowing the chance for `fig.canvas.flush_events()` to be called on a destroyed figure – notMyName Aug 19 '20 at 22:10
  • Hmm, the code works fine for me whatever the order figures are closed in. I suppose that is a backend issue. – Leporello Aug 28 '20 at 15:06