0

I need to update an already existing figure. However I want it to be completely redrawn. I currently have:

import matplotlib.pyplot as plt
def plot_func(xdata, ydata, subplot_kw=None, axis_setters=None):

    fig, ax = plt.subplots(**subplot_kw)
    fig.clf()
    ax.cla()
    fig, ax = plt.subplots(**subplot_kw)

    if axis_setters:
        for setter_name, setter_value in axis_setters.items():
            setter_func = getattr(ax, setter_name)
            setter_func(setter_value)

    plot(xdata, ydata)
    return fig, ax

and then at later times it will be called with:

plot_func([1,2,3], [1,2,3], subplot_kw={'num': 'name'}, axis_setters={'set_title': 'Title'})

The problem is, the awkward:

    fig, ax = plt.subplots(**subplot_kw)
    fig.clf()
    ax.cla()
    fig, ax = plt.subplots(**subplot_kw) 

I need it because I need to call plot_func an arbitrary amount of times, and each time I want it to produce the correct plot. If I don't have the second: fig, ax = plt.subplots(**subplot_kw) axis_setters entries don't work. If I don't have the fig.clf() recalls of the function overwrite the old figure and lines and text gets blurry.

So what is the "correct" way of doing it?

Okay it seems to be misunderstandable:

Here is the plot without the first 3 lines after 10 calls: enter image description here

and here after 10 calls with the strange 3 lines at the beginning: enter image description here

Notice how the fonts get blurry, because, they get redrawn each time. This gets worse with each call. Its not an compression artifact or anything. It is, because the figure get not redrawn correctly.

Now you might say: "Hey then just don't update the complete figure but only its data content" like they do here. The problem here is, that I cant change axes/figure properties any more. Take the e.g. axis_setters={'set_title': 'Title'} I gave in the beginning. If I remove the second fig, ax = plt.subplots(**subplot_kw) this call of a setter doesn't do anything any more. Same happens when you add a fig.canvas.draw() the title will still not show up.

Edit: So this brings me almost there:

import matplotlib.pyplot as plt
def plot_func(xdata, ydata, figure_kw={}, subplot_args=[111], subplot_kw={}, axis_setters=None):

    fig = plt.figure(**figure_kw)
    ax = fig.add_subplot(*subplot_args, **subplot_kw)
    ax.cla()

    if axis_setters:
        for setter_name, setter_value in axis_setters.items():
            setter_func = getattr(ax, setter_name)
            setter_func(setter_value)

    plot(xdata, ydata)
    return fig, ax

and call with:

for i in range(50):
    plot_func([1,2,3], [1,2,3], figure_kw={'num': 'name'}, axis_setters={'set_title': 'Title'})

works, now the only "issue" is the: subplot_args=[111], subplot_kw={} any way of combining this into one keyword. This is now only for convenience, because thats the way I handle almost all other keyword arguemnts through the code this is in, and it just sticks out as strange.

user3613114
  • 188
  • 8
  • Have a look at [this answer](https://stackoverflow.com/a/4098938/2454357) for another way how to do this. – Thomas Kühn Jan 29 '18 at 12:37
  • 1
    The first 3 lines of the function seem to be pretty useless. What is their purpose? – ImportanceOfBeingErnest Jan 29 '18 at 12:41
  • You need them if you call the `plot_func` multiple times. On a first call they make no sense. But if you call it again at a later time, they make a difference. – user3613114 Jan 29 '18 at 12:57
  • @ThomasKühn I know this thread. However it doesn't really help. Because the problem is that I don't want a redraw of just the data, but rather of the complete figure. – user3613114 Jan 29 '18 at 13:00
  • 1
    What difference would that be? I would suggest that in your question you clearly describe what you get when using the "usual" code (deleting the first 3 lines) and state in how far the output does not meet your requirements. Otherwise we would just run deeper into the [xyproblem](http://http://xyproblem.info). – ImportanceOfBeingErnest Jan 29 '18 at 13:00
  • @ImportanceOfBeingErnest better now? I tried my best to be clear. – user3613114 Jan 29 '18 at 13:24
  • Would it in this case be easiest do [remove the `Axes` instance](https://stackoverflow.com/q/14694501/2454357) and call `fig.add_subplot()` for the plot function? – Thomas Kühn Jan 29 '18 at 13:44
  • It seems you are calling the function with the same `num`. Why is that? Wouldn't the idea be to use a different name each time? – ImportanceOfBeingErnest Jan 29 '18 at 14:12
  • @ImportanceOfBeingErnest no, because this runs interactively and I want to be able to update the figure dynamically – user3613114 Jan 29 '18 at 14:29
  • @ThomasKühn I give it a try – user3613114 Jan 29 '18 at 14:30
  • Ok, now we're at it (did I say that I hate xyproblems?). As you say yourself, you want to *update the figure*. So that is what needs to be done, updating the figure, not producing new ones. Then the idea is of course not to call `plt.subplots` again, if one such figure already exists. What exactly does "interactively" mean? Is this in an Ipython notebook? – ImportanceOfBeingErnest Jan 29 '18 at 14:38
  • Currently its a notebook but I'm not forced to use it. ipython session would be fine as well. – user3613114 Jan 29 '18 at 14:48

1 Answers1

0

Here is some correct way of updating a figure if it exists and otherwise create a new one. Note that the num argument to the figure should be a number, otherwise it's not possible to detect whether the figure already exists.

import matplotlib.pyplot as plt

def plot_func(xdata, ydata, subplot_kw={}, axis_setters=None):
    fign = subplot_kw.get("num", False)
    title = subplot_kw.pop("title", False)
    if fign and fign in plt.get_fignums():
        fig = plt.figure(fign)
        ax = fig.get_axes()[0]
        #process other subplot_kw here
    else:
        fig, ax = plt.subplots(**subplot_kw)
    if title:
        fig.canvas.set_window_title(title)
    if axis_setters:
        for setter_name, setter_value in axis_setters.items():
            setter_func = getattr(ax, setter_name)
            setter_func(setter_value)

    ax.plot(xdata, ydata)
    return fig, ax

if __name__ == "__main__":
    for i in range(10):
        plot_func([1,2,3], [1,i,3], subplot_kw={"num": 2, "title" : "mytitle"}, 
                  axis_setters={'set_title': 'Title'})
    plt.show()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • There is no way around num beeing a number? Usually I have around 20 - 30 plots at the same time that I need to manage and remembering numbers becomes really difficult. Naming them with num, made my live a lot easier. Sure the is no way around? – user3613114 Jan 30 '18 at 14:43
  • In order for this solution to work, num needs to be a number, such that `if fign in plt.get_fignums()` can find out if the figure already exists. Note that I am not a big fan of this solution anyways. So if you ask me how I would manage some 20 figures or so: I would store the figures in variables of catchy names, `fig_dF`, `fig_deriv`etc. I would create my plotting function as to take an argument `fig`, so I can call `plot_func(xdata, ydata,fig=fig_dF, **some_arguments)`. As I understood from the question this is not what you want, so I did not suggest it. – ImportanceOfBeingErnest Jan 30 '18 at 21:02
  • @user3613114 If you really want to stick to the way you currently do things, you can use a second `dict` to map your figure names to the numbers that are required by `plt.figure()`. But in the end that is really just what ImportanceOfBeingErnest said in his comment, only more complicated. – Thomas Kühn Jan 31 '18 at 12:56