3

The following code does not do what I want: it shows a figure, waits for the user to close the window, then shows a second figure.

def my_plot(arr_x, arr_y):
    import matplotlib.pyplot as plt
    plt.plot(arr_x, arr_y)
    plt.show()

if __name__ == '__main__':
    my_plot((1, 2, 3), (10, 20, 90))
    my_plot((101, 102, 103), (1, 5, 4))

I would like to have the two figures displayed simultaneously, so I could visually compare them, then e.g. choose one and close the other. My requirement is to store all Matplotlib calls in just one user-defined function such as my_plot.

I have read that show has to be called after all plots have been defined, which does not fit the above requirement.

Actually I am almost losing hope that Matplotlib can do that by itself, perhaps the only solution is something like using a new thread for each call of my_plot?


EDIT:
Problem solved. I hereunder share a function I wrote thanks to Nieznany's answer, that does exactly what I want. Of course it's improvable, but for most of my use cases it works and I can happily forget about Matplotlib.

#!/usr/bin/env python
import matplotlib.pyplot as plt
from os.path import splitext

def plot_XY(arr_x, arr_y, graph_file_name=None, graph_title="", x_label="", y_label=""):
    """Plot a series of (x, y) points.

    If graph_file_name is not provided, the graph is
    plotted in a temporary window; else, the file
    format is deduced from the extension contained
    in graph_file_name.
    """

    def create_figure():
        plt.figure()
        plt.plot(arr_x, arr_y)
        plt.title(graph_title)
        plt.xlabel(x_label)
        plt.ylabel(y_label)

    if graph_file_name is None:
        def my_show():
            create_figure()
            plt.show()
        from multiprocessing import Process
        Process(target=my_show, args=[]).start()
    else:
        extension = splitext(graph_file_name)[1][1:] # Get extension, without dot
        if extension not in ("eps", "pdf", "pgf", "png", "ps", "raw", "rgba", "svg", "svgz"):
            print(f"\"{extension}\" is not a file extension recognized by savefig. Assuming png.")
            extension = "png"
            graph_file_name += "." + extension
        create_figure()
        plt.savefig(graph_file_name, format=extension)
        plt.close()

if __name__ == '__main__':
    # Test: calling the function multiple times:
    x = (1, 2, 3)
    plot_XY(x, (1, 4, 2), graph_title="Example")
    plot_XY(x, (2, 3, 1), graph_file_name="output.abc")
    plot_XY(x, (1, 4, 9), x_label="x", y_label="x^2")
Georg
  • 1,078
  • 2
  • 9
  • 18
  • 1
    On a side note, I think many people would like such a simple behavior implemented by Matplotlib. It's a pity Matplotlib offers so many great plotting options, yet is so strict when it comes to displaying figures... – Georg Dec 16 '18 at 00:20
  • You can plot them next to each other using subplots and a 1x2 grid. But then it will be still a single figure – Sheldore Dec 16 '18 at 00:34

2 Answers2

3

You can use individual process to plot a window and show it without blocking main process.
Example solution:

def my_plot(arr_x, arr_y):
    import matplotlib.pyplot as plt
    from multiprocessing import Process
    def _my_plot(arr_x, arr_y):

        plt.figure()
        plt.plot(arr_x, arr_y)
        plt.show()

    Process(target=_my_plot, args=[arr_x,arr_y]).start()

if __name__ == '__main__':
    my_plot((1, 2, 3), (10, 20, 90))
    my_plot((101, 102, 103), (1, 5, 4))

Resources

Nieznany
  • 79
  • 2
  • That seems to work fine. I just edited my question to include the function I wrote using your solution. Thanks – Georg Dec 18 '18 at 23:59
-1

Call plt.show() once you've created all figures.

import matplotlib.pyplot as plt

def my_plot(arr_x, arr_y):
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.plot(arr_x, arr_y)
    return fig

if __name__ == '__main__':
    f1 = my_plot((1, 2, 3), (10, 20, 90))
    f2 = my_plot((101, 102, 103), (1, 5, 4))
    plt.show()
chryss
  • 7,459
  • 37
  • 46
  • 1
    Then matplotlib has to be imported in every function that calls `my_plot`, which is not great in terms of modular programming. Also, the user of `my_plot` must remember to call `plt.show()`, whereas if my requirement were fulfilled the user could be completely "Matplotlib-illiterate". That is why IMHO future versions of Matplotlib should address that problem if no simple solution exists yet. – Georg Dec 16 '18 at 10:26
  • No, matplotlib does not have to be imported into any function that calls `my_plot`: you can hand the returned figure object off wherever you want. You need to import matplotlib into any module (or function) that shows the plots. You don't have to show the plots after all - I usually don't. From the POV of modular programming, I'm not a fan of imports at the function level. I'd import matplotlib.pyplot in any module that either generates or displays the figures. – chryss Dec 16 '18 at 10:36
  • 1
    Right, I was not accurate when stating that matplotlib has to be imported "in every function that calls `my_plot`". My point still stands though. And of course I don't have to show the plots (nor do I have to use Python). Thank you for reminding the Matplotlib way of doing it. I hope they'll modify that behavior in future versions; BTW they claim that "[matplotlib.pyplot is a collection of command style functions that make Matplotlib work like MATLAB](https://matplotlib.org/api/api_overview.html)", and what I was asking for is easily done in Matlab. – Georg Dec 16 '18 at 10:51
  • You seem oddly adversarial about the thing. True, plt.show() is blocking, and Python and MATLAB require different trade-offs: explicitly show() and close() windows, vs. explicitly preventing windows. But if you were to write your Python program in the same non-modular fashion that MATLAB encourages, it's literally of the same complexity. Your problem comes from putting a blocking statement in a function that you call repeatedly. You and I may not find this behavior ideal, but I'm a little confused that you should be so stroppy with people who are working on helping with your issue. – chryss Dec 16 '18 at 11:29
  • 1
    Please try reading my comments with a neutral, non-emotional tone. I was not being stroppy. "Thank you for reminding the Matplotlib way of doing it" was not ironical. The only thing irritating me at the moment is that Matplotlib behavior, but Nieznany's answer apparently solved my issue so hopefully I can forget about it now. – Georg Dec 16 '18 at 13:02
  • You seem to be reading more into my comments than I intended. Good luck with your coding projects. – chryss Dec 16 '18 at 20:45