Repository navigation
Closed
Labels
GUI: tk🌱 Good first issueOpen a pull request against these issues if there are no active ones!Open a pull request against these issues if there are no active ones!
Description
Bug report
Bug summary
The bug revolves around the interaction between tkinter and matplotlib, while using PdfPages. Specifically, somehow a call to pyplot.close() let's the current 'module' finish but afterwards the tk.Tk() instance is closed without warning/error (see the attached MVE code). I expect that this is a bug in the matplotlib domain after hearing that this occurs more often (https://stackoverflow.com/q/54772180/1093485).
Code for reproduction
import tkinter as tk
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_pdf import PdfPages
from pathlib import Path
class Pdf(object):
def __init__(self, master):
self.master = master
pdf = PdfPages(Path.cwd() / 'demo.pdf')
self.pdf = pdf
def plot_initial(self):
fig = plt.figure(figsize=(8,6))
fig.add_subplot(111)
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
count, bins, ignored = plt.hist(s, 30, density=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
linewidth=2, color='r')
plt.title('Overview')
plt.xlabel('X')
plt.ylabel('Y')
self.pdf.savefig(fig)
# THE CULPRIT
plt.close(fig)
def plot_extra(self):
fig = plt.figure(figsize=(8,6))
fig.add_subplot(111)
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
count, bins, ignored = plt.hist(s, 30, density=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
linewidth=2, color='r')
plt.title('Extra')
plt.xlabel('X')
plt.ylabel('Y')
self.pdf.savefig(fig)
plt.close(fig)
def close(self):
self.pdf.close()
class MVE(object):
@classmethod
def run(cls):
root = tk.Tk()
MVE(root)
root.mainloop()
def __init__(self, master):
self.root = master
tk.Frame(master)
menu = tk.Menu(master)
master.config(menu=menu)
test_menu = tk.Menu(menu, tearoff=0)
menu.add_cascade(label='Bug', menu=test_menu)
test_menu.add_command(label='PDF', command=
self.generate_pdf)
def generate_pdf(self):
pdf = Pdf(self)
pdf.plot_initial()
for i in range(0,3):
pdf.plot_extra()
pdf.close()
if __name__ == "__main__":
MVE.run()Expected outcome
I expect that the initial tk.Tk() window isn't closed once the PDF report has been generated.
Matplotlib version
- Operating system: Tested on Win 7 and Win 10
- Matplotlib version: 2.2.3 from pip
- Matplotlib backend (
print(matplotlib.get_backend())): TkAgg - Python version: 3.7.0
- Jupyter version (if applicable):
- Other libraries: Tkinter 8.6 from pip
Metadata
Metadata
Assignees
Labels
GUI: tk🌱 Good first issueOpen a pull request against these issues if there are no active ones!Open a pull request against these issues if there are no active ones!
Activity
ImportanceOfBeingErnest commentedon Feb 19, 2019
This does not occur in matplotlib 3.0.2. Very likely it has been fixed by #12707. It has however not been backported to 2.2Not sure what's going on, it sometimes works, sometimes not.Tarskin commentedon Feb 19, 2019
I am still encountering the issue after upgrading to 3.0.2 (TkAgg backend), with the above code snippet.
ImportanceOfBeingErnest commentedon Feb 19, 2019
What is your exact version of Tk?
With TK version 8.6.8 I cannot reproduce this, while it appears to fail with 8.6.7.Tarskin commentedon Feb 19, 2019
I think simply 8.6 as that's what tkinter.TkVersion lists, unless that lists only major versions somehow.
ImportanceOfBeingErnest commentedon Feb 19, 2019
Now I'm really lost. I can reproduce this with any mpl version, and any of the two tk versions if this is being run from the command line. If however I run it from within Spyder with the setting "Use external system command" (which I would have thought to be identical to the CLI) it works as expected. (In both cases adding
matplotlib.use("TkAgg"), as other backends don't show this issue)tacaswell commentedon Feb 20, 2019
The "problem" is the logic in
matplotlib/lib/matplotlib/backends/_backend_tk.py
Lines 553 to 562 in a276243
plt.show(block=True)to work (so when you close all of the plots we return control to the terminal). Is there a way to ask tk "do you still have any windows open" to trigger that logic on instead?In this case do you want to show the figures as interactive tk windows or just save pdf? In the former I think you may have to do the embedding your self, if the later, picking a non-interactive backend is probably the correct solution.
I have no guesses as to why in is working is spyder though...
tacaswell commentedon Feb 20, 2019
moved to 3.2 as this is not a regression.
Tarskin commentedon Feb 20, 2019
The program should be able to have plots open in it's own GUI or create plots in a PDF report (depending on the user selection). Would it in any way be useful for you to see the full code of how I was using this?I think I may always have mis-interpreted the meaning of a non-interactive backend as using simply ('Agg') fixes this issue for my system, while still providing the option to interact with the plot inside the tkinter window.
tacaswell commentedon Feb 20, 2019
See https://matplotlib.org/gallery/user_interfaces/embedding_in_tk_sgskip.html for how to do a 'full embedding'. If you are writing a stand-alone application, I strongly suggest going that route and not even importing pyplot.
This is yet another of the points of tension in Matplotlib between providing a low-level library to be used by application developers and being a front-line user interface. It this case, the niceties we need to provide for the end-user (ex, exiting the GUI main loop when the last figure is closed) is conflicting with how @Tarskin wants to use Matplot as a low-level library.
@Tarskin "interactive" means we hook up all the mouse / keyboard listeners + embedded the image of the plot in a native-GUI window + re-draw on changes + do some rudimentary hooking into the GUI main loop (basically, all the details that need to happen for
plt.ion(); plt.plot(range(5))to pop up a 'live' window you can pan / zoom / mutate and have it 'do the right thing')Tarskin commentedon Feb 20, 2019
The plotting was already embedded in the GUI part, and I was trying to be 'simple' in my
Pdfclass that created the PDF reports. I am now refactoring those to useFigureinstead, which I didn't consider.I do want to check if I understood you correctly, in suggesting that one should never use pyplot in a stand-alone and instead resort to always working with theFigureclass?I will admit that just using the
Figureclass directly, makes it much simpler as the underlying logic is then the same for the gui and the reports, I should have considered that to begin with. Thanks for the tip!tacaswell commentedon Feb 20, 2019
https://matplotlib.org/tutorials/introductory/usage.html#coding-styles may also be a useful link (but it sounds like you already figured out everything in there!).
I am going to go ahead and close this as it sounds like this is primarily a conflict in usage and not a bug we can fix.
anntzer commentedon Feb 20, 2019
I think we could at least try to not close the main Tk instance as long as there are windows (not managed by matplotlib) left open?
tacaswell commentedon Feb 25, 2019
That is a fair point, re-opening.
Tagging as good first issue as this is primarily an issue with how we use Tk (rather than Matplotlib internals).
sameshl commentedon Aug 11, 2019
@tacaswell Could you summarize what should be done here? I would love to contribute to this.
richardsheridan commentedon Jun 30, 2020
@tacaswell This looks fixable but the fix is not pretty without help from upstream, see #17802.