Skip to content

Commit

Permalink
Merge pull request #24531 from daniilS/tk_savefig_formats
Browse files Browse the repository at this point in the history
Use user-selected format in Tk savefig, rather than inferring it from the filename
  • Loading branch information
QuLogic authored Mar 15, 2023
2 parents 92b4b95 + b876de8 commit ac32c78
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 17 deletions.
8 changes: 8 additions & 0 deletions doc/api/next_api_changes/behavior/24531-DOS.rst
Original file line number Diff line number Diff line change
@@ -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.
13 changes: 13 additions & 0 deletions doc/api/next_api_changes/development/24531-DOS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Increase to minimum supported optional dependencies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For Matplotlib 3.8, the :ref:`minimum supported versions of optional dependencies
<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`
2 changes: 1 addition & 1 deletion doc/devel/dependencies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
41 changes: 25 additions & 16 deletions lib/matplotlib/backends/_backend_tk.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging
import math
import os.path
import pathlib
import sys
import tkinter as tk
import tkinter.filedialog
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -863,14 +861,18 @@ 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',
filetypes=tk_filetypes,
defaultextension=defaultextension,
initialdir=initialdir,
initialfile=initialfile,
typevariable=filetype_variable
)

if fname in ["", ()]:
Expand All @@ -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))

Expand Down

0 comments on commit ac32c78

Please sign in to comment.