diff --git a/doc/api/next_api_changes/behavior/24531-DOS.rst b/doc/api/next_api_changes/behavior/24531-DOS.rst new file mode 100644 index 000000000000..0cdc5eb35739 --- /dev/null +++ b/doc/api/next_api_changes/behavior/24531-DOS.rst @@ -0,0 +1,8 @@ +Tk backend respects file format selection when saving figures +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When saving a figure from a Tkinter GUI to a filename without an +extension, the file format is now selected based on the value of +the dropdown menu, rather than defaulting to PNG. When the filename +contains an extension, or the OS automatically appends one, the +behavior remains unchanged. diff --git a/doc/api/next_api_changes/development/24531-DOS.rst b/doc/api/next_api_changes/development/24531-DOS.rst new file mode 100644 index 000000000000..9de154e410c6 --- /dev/null +++ b/doc/api/next_api_changes/development/24531-DOS.rst @@ -0,0 +1,13 @@ +Increase to minimum supported optional dependencies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For Matplotlib 3.8, the :ref:`minimum supported versions of optional dependencies +` are being bumped: + ++------------+-----------------+---------------+ +| Dependency | min in mpl3.7 | min in mpl3.8 | ++============+=================+===============+ +| Tk | 8.4 | 8.5 | ++------------+-----------------+---------------+ + +This is consistent with our :ref:`min_deps_policy` diff --git a/doc/devel/dependencies.rst b/doc/devel/dependencies.rst index 66775ac4ea42..cad962472af5 100644 --- a/doc/devel/dependencies.rst +++ b/doc/devel/dependencies.rst @@ -46,7 +46,7 @@ Matplotlib figures can be rendered to various user interfaces. See :ref:`what-is-a-backend` for more details on the optional Matplotlib backends and the capabilities they provide. -* Tk_ (>= 8.4, != 8.6.0 or 8.6.1): for the Tk-based backends. Tk is part of +* Tk_ (>= 8.5, != 8.6.0 or 8.6.1): for the Tk-based backends. Tk is part of most standard Python installations, but it's not part of Python itself and thus may not be present in rare cases. * PyQt6_ (>= 6.1), PySide6_, PyQt5_, or PySide2_: for the Qt-based backends. diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 4ed5a1ee6850..96fcaf5b97e1 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -4,6 +4,7 @@ import logging import math import os.path +import pathlib import sys import tkinter as tk import tkinter.filedialog @@ -499,11 +500,8 @@ def create_with_canvas(cls, canvas_class, figure, num): 'images/matplotlib_large.png')) icon_img_large = ImageTk.PhotoImage( file=icon_fname_large, master=window) - try: - window.iconphoto(False, icon_img_large, icon_img) - except Exception as exc: - # log the failure (due e.g. to Tk version), but carry on - _log.info('Could not load matplotlib icon: %s', exc) + + window.iconphoto(False, icon_img_large, icon_img) canvas = canvas_class(figure, master=window) manager = cls(canvas, num, window) @@ -846,15 +844,15 @@ def _Spacer(self): return s def save_figure(self, *args): - filetypes = self.canvas.get_supported_filetypes().copy() - default_filetype = self.canvas.get_default_filetype() + filetypes = self.canvas.get_supported_filetypes_grouped() + tk_filetypes = [ + (name, " ".join(f"*.{ext}" for ext in exts)) + for name, exts in sorted(filetypes.items()) + ] - # Tk doesn't provide a way to choose a default filetype, - # so we just have to put it first - default_filetype_name = filetypes.pop(default_filetype) - sorted_filetypes = ([(default_filetype, default_filetype_name)] - + sorted(filetypes.items())) - tk_filetypes = [(name, '*.%s' % ext) for ext, name in sorted_filetypes] + default_extension = self.canvas.get_default_filetype() + default_filetype = self.canvas.get_supported_filetypes()[default_extension] + filetype_variable = tk.StringVar(self, default_filetype) # adding a default extension seems to break the # asksaveasfilename dialog when you choose various save types @@ -863,7 +861,10 @@ def save_figure(self, *args): # defaultextension = self.canvas.get_default_filetype() defaultextension = '' initialdir = os.path.expanduser(mpl.rcParams['savefig.directory']) - initialfile = self.canvas.get_default_filename() + # get_default_filename() contains the default extension. On some platforms, + # choosing a different extension from the dropdown does not overwrite it, + # so we need to remove it to make the dropdown functional. + initialfile = pathlib.Path(self.canvas.get_default_filename()).stem fname = tkinter.filedialog.asksaveasfilename( master=self.canvas.get_tk_widget().master, title='Save the figure', @@ -871,6 +872,7 @@ def save_figure(self, *args): defaultextension=defaultextension, initialdir=initialdir, initialfile=initialfile, + typevariable=filetype_variable ) if fname in ["", ()]: @@ -879,9 +881,16 @@ def save_figure(self, *args): if initialdir != "": mpl.rcParams['savefig.directory'] = ( os.path.dirname(str(fname))) + + # If the filename contains an extension, let savefig() infer the file + # format from that. If it does not, use the selected dropdown option. + if pathlib.Path(fname).suffix[1:] != "": + extension = None + else: + extension = filetypes[filetype_variable.get()][0] + try: - # This method will handle the delegation to the correct type - self.canvas.figure.savefig(fname) + self.canvas.figure.savefig(fname, format=extension) except Exception as e: tkinter.messagebox.showerror("Error saving file", str(e))