ZhengyangZhang commited on
Commit
161afc2
·
verified ·
1 Parent(s): 03248b6

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/matplotlib/__pycache__/_animation_data.cpython-312.pyc +0 -0
  2. lib/python3.12/site-packages/matplotlib/__pycache__/_cm_multivar.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/matplotlib/__pycache__/_pylab_helpers.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/matplotlib/__pycache__/backend_managers.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/matplotlib/__pycache__/colorbar.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/matplotlib/__pycache__/colorizer.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/matplotlib/__pycache__/contour.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/matplotlib/__pycache__/hatch.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/matplotlib/__pycache__/lines.cpython-312.pyc +0 -0
  10. lib/python3.12/site-packages/matplotlib/__pycache__/quiver.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/matplotlib/__pycache__/rcsetup.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/matplotlib/__pycache__/streamplot.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/matplotlib/backends/__pycache__/__init__.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/matplotlib/backends/__pycache__/_backend_gtk.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/matplotlib/backends/__pycache__/_backend_pdf_ps.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/matplotlib/backends/__pycache__/_backend_tk.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_agg.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_cairo.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk3.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk3agg.cpython-312.pyc +0 -0
  21. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk3cairo.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk4.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk4agg.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk4cairo.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_macosx.cpython-312.pyc +0 -0
  26. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_mixed.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_nbagg.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_pgf.cpython-312.pyc +0 -0
  29. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_ps.cpython-312.pyc +0 -0
  30. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt.cpython-312.pyc +0 -0
  31. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt5.cpython-312.pyc +0 -0
  32. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt5agg.cpython-312.pyc +0 -0
  33. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt5cairo.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qtagg.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qtcairo.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_svg.cpython-312.pyc +0 -0
  37. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_template.cpython-312.pyc +0 -0
  38. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_tkagg.cpython-312.pyc +0 -0
  39. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_tkcairo.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_webagg.cpython-312.pyc +0 -0
  41. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_webagg_core.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_wx.cpython-312.pyc +0 -0
  43. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_wxagg.cpython-312.pyc +0 -0
  44. lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_wxcairo.cpython-312.pyc +0 -0
  45. lib/python3.12/site-packages/matplotlib/backends/__pycache__/qt_compat.cpython-312.pyc +0 -0
  46. lib/python3.12/site-packages/matplotlib/backends/__pycache__/registry.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/matplotlib/backends/_backend_agg.pyi +0 -0
  48. lib/python3.12/site-packages/matplotlib/backends/_backend_pdf_ps.py +189 -0
  49. lib/python3.12/site-packages/matplotlib/backends/_backend_tk.py +1077 -0
  50. lib/python3.12/site-packages/matplotlib/backends/_macosx.pyi +0 -0
lib/python3.12/site-packages/matplotlib/__pycache__/_animation_data.cpython-312.pyc ADDED
Binary file (8.08 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/_cm_multivar.cpython-312.pyc ADDED
Binary file (6.05 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/_pylab_helpers.cpython-312.pyc ADDED
Binary file (7.5 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/backend_managers.cpython-312.pyc ADDED
Binary file (15.4 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/colorbar.cpython-312.pyc ADDED
Binary file (73.6 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/colorizer.cpython-312.pyc ADDED
Binary file (30.1 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/contour.cpython-312.pyc ADDED
Binary file (78.9 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/hatch.cpython-312.pyc ADDED
Binary file (12.1 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/lines.cpython-312.pyc ADDED
Binary file (69.3 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/quiver.cpython-312.pyc ADDED
Binary file (56.6 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/rcsetup.cpython-312.pyc ADDED
Binary file (51.6 kB). View file
 
lib/python3.12/site-packages/matplotlib/__pycache__/streamplot.cpython-312.pyc ADDED
Binary file (30.2 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (310 Bytes). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/_backend_gtk.cpython-312.pyc ADDED
Binary file (18.4 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/_backend_pdf_ps.cpython-312.pyc ADDED
Binary file (8 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/_backend_tk.cpython-312.pyc ADDED
Binary file (58.3 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_agg.cpython-312.pyc ADDED
Binary file (25.8 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_cairo.cpython-312.pyc ADDED
Binary file (30.2 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk3.cpython-312.pyc ADDED
Binary file (36.5 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk3agg.cpython-312.pyc ADDED
Binary file (4.53 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk3cairo.cpython-312.pyc ADDED
Binary file (2.4 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk4.cpython-312.pyc ADDED
Binary file (36 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk4agg.cpython-312.pyc ADDED
Binary file (2.55 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk4cairo.cpython-312.pyc ADDED
Binary file (2.22 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_macosx.cpython-312.pyc ADDED
Binary file (11.7 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_mixed.cpython-312.pyc ADDED
Binary file (4.96 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_nbagg.cpython-312.pyc ADDED
Binary file (12 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_pgf.cpython-312.pyc ADDED
Binary file (49.9 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_ps.cpython-312.pyc ADDED
Binary file (64.7 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt.cpython-312.pyc ADDED
Binary file (64.3 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt5.cpython-312.pyc ADDED
Binary file (1.49 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt5agg.cpython-312.pyc ADDED
Binary file (773 Bytes). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt5cairo.cpython-312.pyc ADDED
Binary file (683 Bytes). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qtagg.cpython-312.pyc ADDED
Binary file (3.77 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qtcairo.cpython-312.pyc ADDED
Binary file (3.74 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_svg.cpython-312.pyc ADDED
Binary file (59.6 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_template.cpython-312.pyc ADDED
Binary file (7.75 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_tkagg.cpython-312.pyc ADDED
Binary file (1.47 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_tkcairo.cpython-312.pyc ADDED
Binary file (2.13 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_webagg.cpython-312.pyc ADDED
Binary file (16.6 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_webagg_core.cpython-312.pyc ADDED
Binary file (27.5 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_wx.cpython-312.pyc ADDED
Binary file (81 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_wxagg.cpython-312.pyc ADDED
Binary file (2.92 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_wxcairo.cpython-312.pyc ADDED
Binary file (1.94 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/qt_compat.cpython-312.pyc ADDED
Binary file (5.86 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/__pycache__/registry.cpython-312.pyc ADDED
Binary file (14.6 kB). View file
 
lib/python3.12/site-packages/matplotlib/backends/_backend_agg.pyi ADDED
File without changes
lib/python3.12/site-packages/matplotlib/backends/_backend_pdf_ps.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Common functionality between the PDF and PS backends.
3
+ """
4
+
5
+ from io import BytesIO
6
+ import functools
7
+ import logging
8
+
9
+ from fontTools import subset
10
+
11
+ import matplotlib as mpl
12
+ from .. import font_manager, ft2font
13
+ from .._afm import AFM
14
+ from ..backend_bases import RendererBase
15
+
16
+
17
+ @functools.lru_cache(50)
18
+ def _cached_get_afm_from_fname(fname):
19
+ with open(fname, "rb") as fh:
20
+ return AFM(fh)
21
+
22
+
23
+ def get_glyphs_subset(fontfile, characters):
24
+ """
25
+ Subset a TTF font
26
+
27
+ Reads the named fontfile and restricts the font to the characters.
28
+
29
+ Parameters
30
+ ----------
31
+ fontfile : str
32
+ Path to the font file
33
+ characters : str
34
+ Continuous set of characters to include in subset
35
+
36
+ Returns
37
+ -------
38
+ fontTools.ttLib.ttFont.TTFont
39
+ An open font object representing the subset, which needs to
40
+ be closed by the caller.
41
+ """
42
+
43
+ options = subset.Options(glyph_names=True, recommended_glyphs=True)
44
+
45
+ # Prevent subsetting extra tables.
46
+ options.drop_tables += [
47
+ 'FFTM', # FontForge Timestamp.
48
+ 'PfEd', # FontForge personal table.
49
+ 'BDF', # X11 BDF header.
50
+ 'meta', # Metadata stores design/supported languages (meaningless for subsets).
51
+ 'MERG', # Merge Table.
52
+ 'TSIV', # Microsoft Visual TrueType extension.
53
+ 'Zapf', # Information about the individual glyphs in the font.
54
+ 'bdat', # The bitmap data table.
55
+ 'bloc', # The bitmap location table.
56
+ 'cidg', # CID to Glyph ID table (Apple Advanced Typography).
57
+ 'fdsc', # The font descriptors table.
58
+ 'feat', # Feature name table (Apple Advanced Typography).
59
+ 'fmtx', # The Font Metrics Table.
60
+ 'fond', # Data-fork font information (Apple Advanced Typography).
61
+ 'just', # The justification table (Apple Advanced Typography).
62
+ 'kerx', # An extended kerning table (Apple Advanced Typography).
63
+ 'ltag', # Language Tag.
64
+ 'morx', # Extended Glyph Metamorphosis Table.
65
+ 'trak', # Tracking table.
66
+ 'xref', # The cross-reference table (some Apple font tooling information).
67
+ ]
68
+ # if fontfile is a ttc, specify font number
69
+ if fontfile.endswith(".ttc"):
70
+ options.font_number = 0
71
+
72
+ font = subset.load_font(fontfile, options)
73
+ subsetter = subset.Subsetter(options=options)
74
+ subsetter.populate(text=characters)
75
+ subsetter.subset(font)
76
+ return font
77
+
78
+
79
+ def font_as_file(font):
80
+ """
81
+ Convert a TTFont object into a file-like object.
82
+
83
+ Parameters
84
+ ----------
85
+ font : fontTools.ttLib.ttFont.TTFont
86
+ A font object
87
+
88
+ Returns
89
+ -------
90
+ BytesIO
91
+ A file object with the font saved into it
92
+ """
93
+ fh = BytesIO()
94
+ font.save(fh, reorderTables=False)
95
+ return fh
96
+
97
+
98
+ class CharacterTracker:
99
+ """
100
+ Helper for font subsetting by the pdf and ps backends.
101
+
102
+ Maintains a mapping of font paths to the set of character codepoints that
103
+ are being used from that font.
104
+ """
105
+
106
+ def __init__(self):
107
+ self.used = {}
108
+
109
+ def track(self, font, s):
110
+ """Record that string *s* is being typeset using font *font*."""
111
+ char_to_font = font._get_fontmap(s)
112
+ for _c, _f in char_to_font.items():
113
+ self.used.setdefault(_f.fname, set()).add(ord(_c))
114
+
115
+ def track_glyph(self, font, glyph):
116
+ """Record that codepoint *glyph* is being typeset using font *font*."""
117
+ self.used.setdefault(font.fname, set()).add(glyph)
118
+
119
+
120
+ class RendererPDFPSBase(RendererBase):
121
+ # The following attributes must be defined by the subclasses:
122
+ # - _afm_font_dir
123
+ # - _use_afm_rc_name
124
+
125
+ def __init__(self, width, height):
126
+ super().__init__()
127
+ self.width = width
128
+ self.height = height
129
+
130
+ def flipy(self):
131
+ # docstring inherited
132
+ return False # y increases from bottom to top.
133
+
134
+ def option_scale_image(self):
135
+ # docstring inherited
136
+ return True # PDF and PS support arbitrary image scaling.
137
+
138
+ def option_image_nocomposite(self):
139
+ # docstring inherited
140
+ # Decide whether to composite image based on rcParam value.
141
+ return not mpl.rcParams["image.composite_image"]
142
+
143
+ def get_canvas_width_height(self):
144
+ # docstring inherited
145
+ return self.width * 72.0, self.height * 72.0
146
+
147
+ def get_text_width_height_descent(self, s, prop, ismath):
148
+ # docstring inherited
149
+ if ismath == "TeX":
150
+ return super().get_text_width_height_descent(s, prop, ismath)
151
+ elif ismath:
152
+ parse = self._text2path.mathtext_parser.parse(s, 72, prop)
153
+ return parse.width, parse.height, parse.depth
154
+ elif mpl.rcParams[self._use_afm_rc_name]:
155
+ font = self._get_font_afm(prop)
156
+ l, b, w, h, d = font.get_str_bbox_and_descent(s)
157
+ scale = prop.get_size_in_points() / 1000
158
+ w *= scale
159
+ h *= scale
160
+ d *= scale
161
+ return w, h, d
162
+ else:
163
+ font = self._get_font_ttf(prop)
164
+ font.set_text(s, 0.0, flags=ft2font.LoadFlags.NO_HINTING)
165
+ w, h = font.get_width_height()
166
+ d = font.get_descent()
167
+ scale = 1 / 64
168
+ w *= scale
169
+ h *= scale
170
+ d *= scale
171
+ return w, h, d
172
+
173
+ def _get_font_afm(self, prop):
174
+ fname = font_manager.findfont(
175
+ prop, fontext="afm", directory=self._afm_font_dir)
176
+ return _cached_get_afm_from_fname(fname)
177
+
178
+ def _get_font_ttf(self, prop):
179
+ fnames = font_manager.fontManager._find_fonts_by_props(prop)
180
+ try:
181
+ font = font_manager.get_font(fnames)
182
+ font.clear()
183
+ font.set_size(prop.get_size_in_points(), 72)
184
+ return font
185
+ except RuntimeError:
186
+ logging.getLogger(__name__).warning(
187
+ "The PostScript/PDF backend does not currently "
188
+ "support the selected font (%s).", fnames)
189
+ raise
lib/python3.12/site-packages/matplotlib/backends/_backend_tk.py ADDED
@@ -0,0 +1,1077 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ import weakref
3
+ from contextlib import contextmanager
4
+ import logging
5
+ import math
6
+ import os.path
7
+ import pathlib
8
+ import sys
9
+ import tkinter as tk
10
+ import tkinter.filedialog
11
+ import tkinter.font
12
+ import tkinter.messagebox
13
+ from tkinter.simpledialog import SimpleDialog
14
+
15
+ import numpy as np
16
+ from PIL import Image, ImageTk
17
+
18
+ import matplotlib as mpl
19
+ from matplotlib import _api, backend_tools, cbook, _c_internal_utils
20
+ from matplotlib.backend_bases import (
21
+ _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
22
+ TimerBase, ToolContainerBase, cursors, _Mode, MouseButton,
23
+ CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent)
24
+ from matplotlib._pylab_helpers import Gcf
25
+ from . import _tkagg
26
+ from ._tkagg import TK_PHOTO_COMPOSITE_OVERLAY, TK_PHOTO_COMPOSITE_SET
27
+
28
+
29
+ _log = logging.getLogger(__name__)
30
+ cursord = {
31
+ cursors.MOVE: "fleur",
32
+ cursors.HAND: "hand2",
33
+ cursors.POINTER: "arrow",
34
+ cursors.SELECT_REGION: "crosshair",
35
+ cursors.WAIT: "watch",
36
+ cursors.RESIZE_HORIZONTAL: "sb_h_double_arrow",
37
+ cursors.RESIZE_VERTICAL: "sb_v_double_arrow",
38
+ }
39
+
40
+
41
+ @contextmanager
42
+ def _restore_foreground_window_at_end():
43
+ foreground = _c_internal_utils.Win32_GetForegroundWindow()
44
+ try:
45
+ yield
46
+ finally:
47
+ if foreground and mpl.rcParams['tk.window_focus']:
48
+ _c_internal_utils.Win32_SetForegroundWindow(foreground)
49
+
50
+
51
+ _blit_args = {}
52
+ # Initialize to a non-empty string that is not a Tcl command
53
+ _blit_tcl_name = "mpl_blit_" + uuid.uuid4().hex
54
+
55
+
56
+ def _blit(argsid):
57
+ """
58
+ Thin wrapper to blit called via tkapp.call.
59
+
60
+ *argsid* is a unique string identifier to fetch the correct arguments from
61
+ the ``_blit_args`` dict, since arguments cannot be passed directly.
62
+ """
63
+ photoimage, data, offsets, bbox, comp_rule = _blit_args.pop(argsid)
64
+ if not photoimage.tk.call("info", "commands", photoimage):
65
+ return
66
+ _tkagg.blit(photoimage.tk.interpaddr(), str(photoimage), data, comp_rule, offsets,
67
+ bbox)
68
+
69
+
70
+ def blit(photoimage, aggimage, offsets, bbox=None):
71
+ """
72
+ Blit *aggimage* to *photoimage*.
73
+
74
+ *offsets* is a tuple describing how to fill the ``offset`` field of the
75
+ ``Tk_PhotoImageBlock`` struct: it should be (0, 1, 2, 3) for RGBA8888 data,
76
+ (2, 1, 0, 3) for little-endian ARBG32 (i.e. GBRA8888) data and (1, 2, 3, 0)
77
+ for big-endian ARGB32 (i.e. ARGB8888) data.
78
+
79
+ If *bbox* is passed, it defines the region that gets blitted. That region
80
+ will be composed with the previous data according to the alpha channel.
81
+ Blitting will be clipped to pixels inside the canvas, including silently
82
+ doing nothing if the *bbox* region is entirely outside the canvas.
83
+
84
+ Tcl events must be dispatched to trigger a blit from a non-Tcl thread.
85
+ """
86
+ data = np.asarray(aggimage)
87
+ height, width = data.shape[:2]
88
+ if bbox is not None:
89
+ (x1, y1), (x2, y2) = bbox.__array__()
90
+ x1 = max(math.floor(x1), 0)
91
+ x2 = min(math.ceil(x2), width)
92
+ y1 = max(math.floor(y1), 0)
93
+ y2 = min(math.ceil(y2), height)
94
+ if (x1 > x2) or (y1 > y2):
95
+ return
96
+ bboxptr = (x1, x2, y1, y2)
97
+ comp_rule = TK_PHOTO_COMPOSITE_OVERLAY
98
+ else:
99
+ bboxptr = (0, width, 0, height)
100
+ comp_rule = TK_PHOTO_COMPOSITE_SET
101
+
102
+ # NOTE: _tkagg.blit is thread unsafe and will crash the process if called
103
+ # from a thread (GH#13293). Instead of blanking and blitting here,
104
+ # use tkapp.call to post a cross-thread event if this function is called
105
+ # from a non-Tcl thread.
106
+
107
+ # tkapp.call coerces all arguments to strings, so to avoid string parsing
108
+ # within _blit, pack up the arguments into a global data structure.
109
+ args = photoimage, data, offsets, bboxptr, comp_rule
110
+ # Need a unique key to avoid thread races.
111
+ # Again, make the key a string to avoid string parsing in _blit.
112
+ argsid = str(id(args))
113
+ _blit_args[argsid] = args
114
+
115
+ try:
116
+ photoimage.tk.call(_blit_tcl_name, argsid)
117
+ except tk.TclError as e:
118
+ if "invalid command name" not in str(e):
119
+ raise
120
+ photoimage.tk.createcommand(_blit_tcl_name, _blit)
121
+ photoimage.tk.call(_blit_tcl_name, argsid)
122
+
123
+
124
+ class TimerTk(TimerBase):
125
+ """Subclass of `backend_bases.TimerBase` using Tk timer events."""
126
+
127
+ def __init__(self, parent, *args, **kwargs):
128
+ self._timer = None
129
+ super().__init__(*args, **kwargs)
130
+ self.parent = parent
131
+
132
+ def _timer_start(self):
133
+ self._timer_stop()
134
+ self._timer = self.parent.after(self._interval, self._on_timer)
135
+
136
+ def _timer_stop(self):
137
+ if self._timer is not None:
138
+ self.parent.after_cancel(self._timer)
139
+ self._timer = None
140
+
141
+ def _on_timer(self):
142
+ super()._on_timer()
143
+ # Tk after() is only a single shot, so we need to add code here to
144
+ # reset the timer if we're not operating in single shot mode. However,
145
+ # if _timer is None, this means that _timer_stop has been called; so
146
+ # don't recreate the timer in that case.
147
+ if not self._single and self._timer:
148
+ if self._interval > 0:
149
+ self._timer = self.parent.after(self._interval, self._on_timer)
150
+ else:
151
+ # Edge case: Tcl after 0 *prepends* events to the queue
152
+ # so a 0 interval does not allow any other events to run.
153
+ # This incantation is cancellable and runs as fast as possible
154
+ # while also allowing events and drawing every frame. GH#18236
155
+ self._timer = self.parent.after_idle(
156
+ lambda: self.parent.after(self._interval, self._on_timer)
157
+ )
158
+ else:
159
+ self._timer = None
160
+
161
+
162
+ class FigureCanvasTk(FigureCanvasBase):
163
+ required_interactive_framework = "tk"
164
+ manager_class = _api.classproperty(lambda cls: FigureManagerTk)
165
+
166
+ def __init__(self, figure=None, master=None):
167
+ super().__init__(figure)
168
+ self._idle_draw_id = None
169
+ self._event_loop_id = None
170
+ w, h = self.get_width_height(physical=True)
171
+ self._tkcanvas = tk.Canvas(
172
+ master=master, background="white",
173
+ width=w, height=h, borderwidth=0, highlightthickness=0)
174
+ self._tkphoto = tk.PhotoImage(
175
+ master=self._tkcanvas, width=w, height=h)
176
+ self._tkcanvas_image_region = self._tkcanvas.create_image(
177
+ w//2, h//2, image=self._tkphoto)
178
+ self._tkcanvas.bind("<Configure>", self.resize)
179
+ self._tkcanvas.bind("<Map>", self._update_device_pixel_ratio)
180
+ self._tkcanvas.bind("<Key>", self.key_press)
181
+ self._tkcanvas.bind("<Motion>", self.motion_notify_event)
182
+ self._tkcanvas.bind("<Enter>", self.enter_notify_event)
183
+ self._tkcanvas.bind("<Leave>", self.leave_notify_event)
184
+ self._tkcanvas.bind("<KeyRelease>", self.key_release)
185
+ for name in ["<Button-1>", "<Button-2>", "<Button-3>"]:
186
+ self._tkcanvas.bind(name, self.button_press_event)
187
+ for name in [
188
+ "<Double-Button-1>", "<Double-Button-2>", "<Double-Button-3>"]:
189
+ self._tkcanvas.bind(name, self.button_dblclick_event)
190
+ for name in [
191
+ "<ButtonRelease-1>", "<ButtonRelease-2>", "<ButtonRelease-3>"]:
192
+ self._tkcanvas.bind(name, self.button_release_event)
193
+
194
+ # Mouse wheel on Linux generates button 4/5 events
195
+ for name in "<Button-4>", "<Button-5>":
196
+ self._tkcanvas.bind(name, self.scroll_event)
197
+ # Mouse wheel for windows goes to the window with the focus.
198
+ # Since the canvas won't usually have the focus, bind the
199
+ # event to the window containing the canvas instead.
200
+ # See https://wiki.tcl-lang.org/3893 (mousewheel) for details
201
+ root = self._tkcanvas.winfo_toplevel()
202
+
203
+ # Prevent long-lived references via tkinter callback structure GH-24820
204
+ weakself = weakref.ref(self)
205
+ weakroot = weakref.ref(root)
206
+
207
+ def scroll_event_windows(event):
208
+ self = weakself()
209
+ if self is None:
210
+ root = weakroot()
211
+ if root is not None:
212
+ root.unbind("<MouseWheel>", scroll_event_windows_id)
213
+ return
214
+ return self.scroll_event_windows(event)
215
+ scroll_event_windows_id = root.bind("<MouseWheel>", scroll_event_windows, "+")
216
+
217
+ # Can't get destroy events by binding to _tkcanvas. Therefore, bind
218
+ # to the window and filter.
219
+ def filter_destroy(event):
220
+ self = weakself()
221
+ if self is None:
222
+ root = weakroot()
223
+ if root is not None:
224
+ root.unbind("<Destroy>", filter_destroy_id)
225
+ return
226
+ if event.widget is self._tkcanvas:
227
+ CloseEvent("close_event", self)._process()
228
+ filter_destroy_id = root.bind("<Destroy>", filter_destroy, "+")
229
+
230
+ self._tkcanvas.focus_set()
231
+
232
+ self._rubberband_rect_black = None
233
+ self._rubberband_rect_white = None
234
+
235
+ def _update_device_pixel_ratio(self, event=None):
236
+ ratio = None
237
+ if sys.platform == 'win32':
238
+ # Tk gives scaling with respect to 72 DPI, but Windows screens are
239
+ # scaled vs 96 dpi, and pixel ratio settings are given in whole
240
+ # percentages, so round to 2 digits.
241
+ ratio = round(self._tkcanvas.tk.call('tk', 'scaling') / (96 / 72), 2)
242
+ elif sys.platform == "linux":
243
+ ratio = self._tkcanvas.winfo_fpixels('1i') / 96
244
+ if ratio is not None and self._set_device_pixel_ratio(ratio):
245
+ # The easiest way to resize the canvas is to resize the canvas
246
+ # widget itself, since we implement all the logic for resizing the
247
+ # canvas backing store on that event.
248
+ w, h = self.get_width_height(physical=True)
249
+ self._tkcanvas.configure(width=w, height=h)
250
+
251
+ def resize(self, event):
252
+ width, height = event.width, event.height
253
+
254
+ # compute desired figure size in inches
255
+ dpival = self.figure.dpi
256
+ winch = width / dpival
257
+ hinch = height / dpival
258
+ self.figure.set_size_inches(winch, hinch, forward=False)
259
+
260
+ self._tkcanvas.delete(self._tkcanvas_image_region)
261
+ self._tkphoto.configure(width=int(width), height=int(height))
262
+ self._tkcanvas_image_region = self._tkcanvas.create_image(
263
+ int(width / 2), int(height / 2), image=self._tkphoto)
264
+ ResizeEvent("resize_event", self)._process()
265
+ self.draw_idle()
266
+
267
+ def draw_idle(self):
268
+ # docstring inherited
269
+ if self._idle_draw_id:
270
+ return
271
+
272
+ def idle_draw(*args):
273
+ try:
274
+ self.draw()
275
+ finally:
276
+ self._idle_draw_id = None
277
+
278
+ self._idle_draw_id = self._tkcanvas.after_idle(idle_draw)
279
+
280
+ def get_tk_widget(self):
281
+ """
282
+ Return the Tk widget used to implement FigureCanvasTkAgg.
283
+
284
+ Although the initial implementation uses a Tk canvas, this routine
285
+ is intended to hide that fact.
286
+ """
287
+ return self._tkcanvas
288
+
289
+ def _event_mpl_coords(self, event):
290
+ # calling canvasx/canvasy allows taking scrollbars into account (i.e.
291
+ # the top of the widget may have been scrolled out of view).
292
+ return (self._tkcanvas.canvasx(event.x),
293
+ # flipy so y=0 is bottom of canvas
294
+ self.figure.bbox.height - self._tkcanvas.canvasy(event.y))
295
+
296
+ def motion_notify_event(self, event):
297
+ MouseEvent("motion_notify_event", self,
298
+ *self._event_mpl_coords(event),
299
+ buttons=self._mpl_buttons(event),
300
+ modifiers=self._mpl_modifiers(event),
301
+ guiEvent=event)._process()
302
+
303
+ def enter_notify_event(self, event):
304
+ LocationEvent("figure_enter_event", self,
305
+ *self._event_mpl_coords(event),
306
+ modifiers=self._mpl_modifiers(event),
307
+ guiEvent=event)._process()
308
+
309
+ def leave_notify_event(self, event):
310
+ LocationEvent("figure_leave_event", self,
311
+ *self._event_mpl_coords(event),
312
+ modifiers=self._mpl_modifiers(event),
313
+ guiEvent=event)._process()
314
+
315
+ def button_press_event(self, event, dblclick=False):
316
+ # set focus to the canvas so that it can receive keyboard events
317
+ self._tkcanvas.focus_set()
318
+
319
+ num = getattr(event, 'num', None)
320
+ if sys.platform == 'darwin': # 2 and 3 are reversed.
321
+ num = {2: 3, 3: 2}.get(num, num)
322
+ MouseEvent("button_press_event", self,
323
+ *self._event_mpl_coords(event), num, dblclick=dblclick,
324
+ modifiers=self._mpl_modifiers(event),
325
+ guiEvent=event)._process()
326
+
327
+ def button_dblclick_event(self, event):
328
+ self.button_press_event(event, dblclick=True)
329
+
330
+ def button_release_event(self, event):
331
+ num = getattr(event, 'num', None)
332
+ if sys.platform == 'darwin': # 2 and 3 are reversed.
333
+ num = {2: 3, 3: 2}.get(num, num)
334
+ MouseEvent("button_release_event", self,
335
+ *self._event_mpl_coords(event), num,
336
+ modifiers=self._mpl_modifiers(event),
337
+ guiEvent=event)._process()
338
+
339
+ def scroll_event(self, event):
340
+ num = getattr(event, 'num', None)
341
+ step = 1 if num == 4 else -1 if num == 5 else 0
342
+ MouseEvent("scroll_event", self,
343
+ *self._event_mpl_coords(event), step=step,
344
+ modifiers=self._mpl_modifiers(event),
345
+ guiEvent=event)._process()
346
+
347
+ def scroll_event_windows(self, event):
348
+ """MouseWheel event processor"""
349
+ # need to find the window that contains the mouse
350
+ w = event.widget.winfo_containing(event.x_root, event.y_root)
351
+ if w != self._tkcanvas:
352
+ return
353
+ x = self._tkcanvas.canvasx(event.x_root - w.winfo_rootx())
354
+ y = (self.figure.bbox.height
355
+ - self._tkcanvas.canvasy(event.y_root - w.winfo_rooty()))
356
+ step = event.delta / 120
357
+ MouseEvent("scroll_event", self,
358
+ x, y, step=step, modifiers=self._mpl_modifiers(event),
359
+ guiEvent=event)._process()
360
+
361
+ @staticmethod
362
+ def _mpl_buttons(event): # See _mpl_modifiers.
363
+ # NOTE: This fails to report multiclicks on macOS; only one button is
364
+ # reported (multiclicks work correctly on Linux & Windows).
365
+ modifiers = [
366
+ # macOS appears to swap right and middle (look for "Swap buttons
367
+ # 2/3" in tk/macosx/tkMacOSXMouseEvent.c).
368
+ (MouseButton.LEFT, 1 << 8),
369
+ (MouseButton.RIGHT, 1 << 9),
370
+ (MouseButton.MIDDLE, 1 << 10),
371
+ (MouseButton.BACK, 1 << 11),
372
+ (MouseButton.FORWARD, 1 << 12),
373
+ ] if sys.platform == "darwin" else [
374
+ (MouseButton.LEFT, 1 << 8),
375
+ (MouseButton.MIDDLE, 1 << 9),
376
+ (MouseButton.RIGHT, 1 << 10),
377
+ (MouseButton.BACK, 1 << 11),
378
+ (MouseButton.FORWARD, 1 << 12),
379
+ ]
380
+ # State *before* press/release.
381
+ return [name for name, mask in modifiers if event.state & mask]
382
+
383
+ @staticmethod
384
+ def _mpl_modifiers(event, *, exclude=None):
385
+ # Add modifier keys to the key string. Bit values are inferred from
386
+ # the implementation of tkinter.Event.__repr__ (1, 2, 4, 8, ... =
387
+ # Shift, Lock, Control, Mod1, ..., Mod5, Button1, ..., Button5)
388
+ # In general, the modifier key is excluded from the modifier flag,
389
+ # however this is not the case on "darwin", so double check that
390
+ # we aren't adding repeat modifier flags to a modifier key.
391
+ modifiers = [
392
+ ("ctrl", 1 << 2, "control"),
393
+ ("alt", 1 << 17, "alt"),
394
+ ("shift", 1 << 0, "shift"),
395
+ ] if sys.platform == "win32" else [
396
+ ("ctrl", 1 << 2, "control"),
397
+ ("alt", 1 << 4, "alt"),
398
+ ("shift", 1 << 0, "shift"),
399
+ ("cmd", 1 << 3, "cmd"),
400
+ ] if sys.platform == "darwin" else [
401
+ ("ctrl", 1 << 2, "control"),
402
+ ("alt", 1 << 3, "alt"),
403
+ ("shift", 1 << 0, "shift"),
404
+ ("super", 1 << 6, "super"),
405
+ ]
406
+ return [name for name, mask, key in modifiers
407
+ if event.state & mask and exclude != key]
408
+
409
+ def _get_key(self, event):
410
+ unikey = event.char
411
+ key = cbook._unikey_or_keysym_to_mplkey(unikey, event.keysym)
412
+ if key is not None:
413
+ mods = self._mpl_modifiers(event, exclude=key)
414
+ # shift is not added to the keys as this is already accounted for.
415
+ if "shift" in mods and unikey:
416
+ mods.remove("shift")
417
+ return "+".join([*mods, key])
418
+
419
+ def key_press(self, event):
420
+ KeyEvent("key_press_event", self,
421
+ self._get_key(event), *self._event_mpl_coords(event),
422
+ guiEvent=event)._process()
423
+
424
+ def key_release(self, event):
425
+ KeyEvent("key_release_event", self,
426
+ self._get_key(event), *self._event_mpl_coords(event),
427
+ guiEvent=event)._process()
428
+
429
+ def new_timer(self, *args, **kwargs):
430
+ # docstring inherited
431
+ return TimerTk(self._tkcanvas, *args, **kwargs)
432
+
433
+ def flush_events(self):
434
+ # docstring inherited
435
+ self._tkcanvas.update()
436
+
437
+ def start_event_loop(self, timeout=0):
438
+ # docstring inherited
439
+ if timeout > 0:
440
+ milliseconds = int(1000 * timeout)
441
+ if milliseconds > 0:
442
+ self._event_loop_id = self._tkcanvas.after(
443
+ milliseconds, self.stop_event_loop)
444
+ else:
445
+ self._event_loop_id = self._tkcanvas.after_idle(
446
+ self.stop_event_loop)
447
+ self._tkcanvas.mainloop()
448
+
449
+ def stop_event_loop(self):
450
+ # docstring inherited
451
+ if self._event_loop_id:
452
+ self._tkcanvas.after_cancel(self._event_loop_id)
453
+ self._event_loop_id = None
454
+ self._tkcanvas.quit()
455
+
456
+ def set_cursor(self, cursor):
457
+ try:
458
+ self._tkcanvas.configure(cursor=cursord[cursor])
459
+ except tkinter.TclError:
460
+ pass
461
+
462
+
463
+ class FigureManagerTk(FigureManagerBase):
464
+ """
465
+ Attributes
466
+ ----------
467
+ canvas : `FigureCanvas`
468
+ The FigureCanvas instance
469
+ num : int or str
470
+ The Figure number
471
+ toolbar : tk.Toolbar
472
+ The tk.Toolbar
473
+ window : tk.Window
474
+ The tk.Window
475
+ """
476
+
477
+ _owns_mainloop = False
478
+
479
+ def __init__(self, canvas, num, window):
480
+ self.window = window
481
+ super().__init__(canvas, num)
482
+ self.window.withdraw()
483
+ # packing toolbar first, because if space is getting low, last packed
484
+ # widget is getting shrunk first (-> the canvas)
485
+ self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
486
+
487
+ # If the window has per-monitor DPI awareness, then setup a Tk variable
488
+ # to store the DPI, which will be updated by the C code, and the trace
489
+ # will handle it on the Python side.
490
+ window_frame = int(window.wm_frame(), 16)
491
+ self._window_dpi = tk.IntVar(master=window, value=96,
492
+ name=f'window_dpi{window_frame}')
493
+ self._window_dpi_cbname = ''
494
+ if _tkagg.enable_dpi_awareness(window_frame, window.tk.interpaddr()):
495
+ self._window_dpi_cbname = self._window_dpi.trace_add(
496
+ 'write', self._update_window_dpi)
497
+
498
+ self._shown = False
499
+
500
+ @classmethod
501
+ def create_with_canvas(cls, canvas_class, figure, num):
502
+ # docstring inherited
503
+ with _restore_foreground_window_at_end():
504
+ if cbook._get_running_interactive_framework() is None:
505
+ cbook._setup_new_guiapp()
506
+ _c_internal_utils.Win32_SetProcessDpiAwareness_max()
507
+ window = tk.Tk(className="matplotlib")
508
+ window.withdraw()
509
+
510
+ # Put a Matplotlib icon on the window rather than the default tk
511
+ # icon. See https://www.tcl.tk/man/tcl/TkCmd/wm.html#M50
512
+ #
513
+ # `ImageTk` can be replaced with `tk` whenever the minimum
514
+ # supported Tk version is increased to 8.6, as Tk 8.6+ natively
515
+ # supports PNG images.
516
+ icon_fname = str(cbook._get_data_path(
517
+ 'images/matplotlib.png'))
518
+ icon_img = ImageTk.PhotoImage(file=icon_fname, master=window)
519
+
520
+ icon_fname_large = str(cbook._get_data_path(
521
+ 'images/matplotlib_large.png'))
522
+ icon_img_large = ImageTk.PhotoImage(
523
+ file=icon_fname_large, master=window)
524
+
525
+ window.iconphoto(False, icon_img_large, icon_img)
526
+
527
+ canvas = canvas_class(figure, master=window)
528
+ manager = cls(canvas, num, window)
529
+ if mpl.is_interactive():
530
+ manager.show()
531
+ canvas.draw_idle()
532
+ return manager
533
+
534
+ @classmethod
535
+ def start_main_loop(cls):
536
+ managers = Gcf.get_all_fig_managers()
537
+ if managers:
538
+ first_manager = managers[0]
539
+ manager_class = type(first_manager)
540
+ if manager_class._owns_mainloop:
541
+ return
542
+ manager_class._owns_mainloop = True
543
+ try:
544
+ first_manager.window.mainloop()
545
+ finally:
546
+ manager_class._owns_mainloop = False
547
+
548
+ def _update_window_dpi(self, *args):
549
+ newdpi = self._window_dpi.get()
550
+ self.window.call('tk', 'scaling', newdpi / 72)
551
+ if self.toolbar and hasattr(self.toolbar, '_rescale'):
552
+ self.toolbar._rescale()
553
+ self.canvas._update_device_pixel_ratio()
554
+
555
+ def resize(self, width, height):
556
+ max_size = 1_400_000 # the measured max on xorg 1.20.8 was 1_409_023
557
+
558
+ if (width > max_size or height > max_size) and sys.platform == 'linux':
559
+ raise ValueError(
560
+ 'You have requested to resize the '
561
+ f'Tk window to ({width}, {height}), one of which '
562
+ f'is bigger than {max_size}. At larger sizes xorg will '
563
+ 'either exit with an error on newer versions (~1.20) or '
564
+ 'cause corruption on older version (~1.19). We '
565
+ 'do not expect a window over a million pixel wide or tall '
566
+ 'to be intended behavior.')
567
+ self.canvas._tkcanvas.configure(width=width, height=height)
568
+
569
+ def show(self):
570
+ with _restore_foreground_window_at_end():
571
+ if not self._shown:
572
+ def destroy(*args):
573
+ Gcf.destroy(self)
574
+ self.window.protocol("WM_DELETE_WINDOW", destroy)
575
+ self.window.deiconify()
576
+ self.canvas._tkcanvas.focus_set()
577
+ else:
578
+ self.canvas.draw_idle()
579
+ if mpl.rcParams['figure.raise_window']:
580
+ self.canvas.manager.window.attributes('-topmost', 1)
581
+ self.canvas.manager.window.attributes('-topmost', 0)
582
+ self._shown = True
583
+
584
+ def destroy(self, *args):
585
+ if self.canvas._idle_draw_id:
586
+ self.canvas._tkcanvas.after_cancel(self.canvas._idle_draw_id)
587
+ if self.canvas._event_loop_id:
588
+ self.canvas._tkcanvas.after_cancel(self.canvas._event_loop_id)
589
+ if self._window_dpi_cbname:
590
+ self._window_dpi.trace_remove('write', self._window_dpi_cbname)
591
+
592
+ # NOTE: events need to be flushed before issuing destroy (GH #9956),
593
+ # however, self.window.update() can break user code. An async callback
594
+ # is the safest way to achieve a complete draining of the event queue,
595
+ # but it leaks if no tk event loop is running. Therefore we explicitly
596
+ # check for an event loop and choose our best guess.
597
+ def delayed_destroy():
598
+ self.window.destroy()
599
+
600
+ if self._owns_mainloop and not Gcf.get_num_fig_managers():
601
+ self.window.quit()
602
+
603
+ if cbook._get_running_interactive_framework() == "tk":
604
+ # "after idle after 0" avoids Tcl error/race (GH #19940)
605
+ self.window.after_idle(self.window.after, 0, delayed_destroy)
606
+ else:
607
+ self.window.update()
608
+ delayed_destroy()
609
+
610
+ def get_window_title(self):
611
+ return self.window.wm_title()
612
+
613
+ def set_window_title(self, title):
614
+ self.window.wm_title(title)
615
+
616
+ def full_screen_toggle(self):
617
+ is_fullscreen = bool(self.window.attributes('-fullscreen'))
618
+ self.window.attributes('-fullscreen', not is_fullscreen)
619
+
620
+
621
+ class NavigationToolbar2Tk(NavigationToolbar2, tk.Frame):
622
+ def __init__(self, canvas, window=None, *, pack_toolbar=True):
623
+ """
624
+ Parameters
625
+ ----------
626
+ canvas : `FigureCanvas`
627
+ The figure canvas on which to operate.
628
+ window : tk.Window
629
+ The tk.Window which owns this toolbar.
630
+ pack_toolbar : bool, default: True
631
+ If True, add the toolbar to the parent's pack manager's packing
632
+ list during initialization with ``side="bottom"`` and ``fill="x"``.
633
+ If you want to use the toolbar with a different layout manager, use
634
+ ``pack_toolbar=False``.
635
+ """
636
+
637
+ if window is None:
638
+ window = canvas.get_tk_widget().master
639
+ tk.Frame.__init__(self, master=window, borderwidth=2,
640
+ width=int(canvas.figure.bbox.width), height=50)
641
+
642
+ self._buttons = {}
643
+ for text, tooltip_text, image_file, callback in self.toolitems:
644
+ if text is None:
645
+ # Add a spacer; return value is unused.
646
+ self._Spacer()
647
+ else:
648
+ self._buttons[text] = button = self._Button(
649
+ text,
650
+ str(cbook._get_data_path(f"images/{image_file}.png")),
651
+ toggle=callback in ["zoom", "pan"],
652
+ command=getattr(self, callback),
653
+ )
654
+ if tooltip_text is not None:
655
+ add_tooltip(button, tooltip_text)
656
+
657
+ self._label_font = tkinter.font.Font(root=window, size=10)
658
+
659
+ # This filler item ensures the toolbar is always at least two text
660
+ # lines high. Otherwise the canvas gets redrawn as the mouse hovers
661
+ # over images because those use two-line messages which resize the
662
+ # toolbar.
663
+ label = tk.Label(master=self, font=self._label_font,
664
+ text='\N{NO-BREAK SPACE}\n\N{NO-BREAK SPACE}')
665
+ label.pack(side=tk.RIGHT)
666
+
667
+ self.message = tk.StringVar(master=self)
668
+ self._message_label = tk.Label(master=self, font=self._label_font,
669
+ textvariable=self.message,
670
+ justify=tk.RIGHT)
671
+ self._message_label.pack(side=tk.RIGHT)
672
+
673
+ NavigationToolbar2.__init__(self, canvas)
674
+ if pack_toolbar:
675
+ self.pack(side=tk.BOTTOM, fill=tk.X)
676
+
677
+ def _rescale(self):
678
+ """
679
+ Scale all children of the toolbar to current DPI setting.
680
+
681
+ Before this is called, the Tk scaling setting will have been updated to
682
+ match the new DPI. Tk widgets do not update for changes to scaling, but
683
+ all measurements made after the change will match the new scaling. Thus
684
+ this function re-applies all the same sizes in points, which Tk will
685
+ scale correctly to pixels.
686
+ """
687
+ for widget in self.winfo_children():
688
+ if isinstance(widget, (tk.Button, tk.Checkbutton)):
689
+ if hasattr(widget, '_image_file'):
690
+ # Explicit class because ToolbarTk calls _rescale.
691
+ NavigationToolbar2Tk._set_image_for_button(self, widget)
692
+ else:
693
+ # Text-only button is handled by the font setting instead.
694
+ pass
695
+ elif isinstance(widget, tk.Frame):
696
+ widget.configure(height='18p')
697
+ widget.pack_configure(padx='3p')
698
+ elif isinstance(widget, tk.Label):
699
+ pass # Text is handled by the font setting instead.
700
+ else:
701
+ _log.warning('Unknown child class %s', widget.winfo_class)
702
+ self._label_font.configure(size=10)
703
+
704
+ def _update_buttons_checked(self):
705
+ # sync button checkstates to match active mode
706
+ for text, mode in [('Zoom', _Mode.ZOOM), ('Pan', _Mode.PAN)]:
707
+ if text in self._buttons:
708
+ if self.mode == mode:
709
+ self._buttons[text].select() # NOT .invoke()
710
+ else:
711
+ self._buttons[text].deselect()
712
+
713
+ def pan(self, *args):
714
+ super().pan(*args)
715
+ self._update_buttons_checked()
716
+
717
+ def zoom(self, *args):
718
+ super().zoom(*args)
719
+ self._update_buttons_checked()
720
+
721
+ def set_message(self, s):
722
+ self.message.set(s)
723
+
724
+ def draw_rubberband(self, event, x0, y0, x1, y1):
725
+ # Block copied from remove_rubberband for backend_tools convenience.
726
+ if self.canvas._rubberband_rect_white:
727
+ self.canvas._tkcanvas.delete(self.canvas._rubberband_rect_white)
728
+ if self.canvas._rubberband_rect_black:
729
+ self.canvas._tkcanvas.delete(self.canvas._rubberband_rect_black)
730
+ height = self.canvas.figure.bbox.height
731
+ y0 = height - y0
732
+ y1 = height - y1
733
+ self.canvas._rubberband_rect_black = (
734
+ self.canvas._tkcanvas.create_rectangle(
735
+ x0, y0, x1, y1))
736
+ self.canvas._rubberband_rect_white = (
737
+ self.canvas._tkcanvas.create_rectangle(
738
+ x0, y0, x1, y1, outline='white', dash=(3, 3)))
739
+
740
+ def remove_rubberband(self):
741
+ if self.canvas._rubberband_rect_white:
742
+ self.canvas._tkcanvas.delete(self.canvas._rubberband_rect_white)
743
+ self.canvas._rubberband_rect_white = None
744
+ if self.canvas._rubberband_rect_black:
745
+ self.canvas._tkcanvas.delete(self.canvas._rubberband_rect_black)
746
+ self.canvas._rubberband_rect_black = None
747
+
748
+ def _set_image_for_button(self, button):
749
+ """
750
+ Set the image for a button based on its pixel size.
751
+
752
+ The pixel size is determined by the DPI scaling of the window.
753
+ """
754
+ if button._image_file is None:
755
+ return
756
+
757
+ # Allow _image_file to be relative to Matplotlib's "images" data
758
+ # directory.
759
+ path_regular = cbook._get_data_path('images', button._image_file)
760
+ path_large = path_regular.with_name(
761
+ path_regular.name.replace('.png', '_large.png'))
762
+ size = button.winfo_pixels('18p')
763
+
764
+ # Nested functions because ToolbarTk calls _Button.
765
+ def _get_color(color_name):
766
+ # `winfo_rgb` returns an (r, g, b) tuple in the range 0-65535
767
+ return button.winfo_rgb(button.cget(color_name))
768
+
769
+ def _is_dark(color):
770
+ if isinstance(color, str):
771
+ color = _get_color(color)
772
+ return max(color) < 65535 / 2
773
+
774
+ def _recolor_icon(image, color):
775
+ image_data = np.asarray(image).copy()
776
+ black_mask = (image_data[..., :3] == 0).all(axis=-1)
777
+ image_data[black_mask, :3] = color
778
+ return Image.fromarray(image_data, mode="RGBA")
779
+
780
+ # Use the high-resolution (48x48 px) icon if it exists and is needed
781
+ with Image.open(path_large if (size > 24 and path_large.exists())
782
+ else path_regular) as im:
783
+ # assure a RGBA image as foreground color is RGB
784
+ im = im.convert("RGBA")
785
+ image = ImageTk.PhotoImage(im.resize((size, size)), master=self)
786
+ button._ntimage = image
787
+
788
+ # create a version of the icon with the button's text color
789
+ foreground = (255 / 65535) * np.array(
790
+ button.winfo_rgb(button.cget("foreground")))
791
+ im_alt = _recolor_icon(im, foreground)
792
+ image_alt = ImageTk.PhotoImage(
793
+ im_alt.resize((size, size)), master=self)
794
+ button._ntimage_alt = image_alt
795
+
796
+ if _is_dark("background"):
797
+ # For Checkbuttons, we need to set `image` and `selectimage` at
798
+ # the same time. Otherwise, when updating the `image` option
799
+ # (such as when changing DPI), if the old `selectimage` has
800
+ # just been overwritten, Tk will throw an error.
801
+ image_kwargs = {"image": image_alt}
802
+ else:
803
+ image_kwargs = {"image": image}
804
+ # Checkbuttons may switch the background to `selectcolor` in the
805
+ # checked state, so check separately which image it needs to use in
806
+ # that state to still ensure enough contrast with the background.
807
+ if (
808
+ isinstance(button, tk.Checkbutton)
809
+ and button.cget("selectcolor") != ""
810
+ ):
811
+ if self._windowingsystem != "x11":
812
+ selectcolor = "selectcolor"
813
+ else:
814
+ # On X11, selectcolor isn't used directly for indicator-less
815
+ # buttons. See `::tk::CheckEnter` in the Tk button.tcl source
816
+ # code for details.
817
+ r1, g1, b1 = _get_color("selectcolor")
818
+ r2, g2, b2 = _get_color("activebackground")
819
+ selectcolor = ((r1+r2)/2, (g1+g2)/2, (b1+b2)/2)
820
+ if _is_dark(selectcolor):
821
+ image_kwargs["selectimage"] = image_alt
822
+ else:
823
+ image_kwargs["selectimage"] = image
824
+
825
+ button.configure(**image_kwargs, height='18p', width='18p')
826
+
827
+ def _Button(self, text, image_file, toggle, command):
828
+ if not toggle:
829
+ b = tk.Button(
830
+ master=self, text=text, command=command,
831
+ relief="flat", overrelief="groove", borderwidth=1,
832
+ )
833
+ else:
834
+ # There is a bug in tkinter included in some python 3.6 versions
835
+ # that without this variable, produces a "visual" toggling of
836
+ # other near checkbuttons
837
+ # https://bugs.python.org/issue29402
838
+ # https://bugs.python.org/issue25684
839
+ var = tk.IntVar(master=self)
840
+ b = tk.Checkbutton(
841
+ master=self, text=text, command=command, indicatoron=False,
842
+ variable=var, offrelief="flat", overrelief="groove",
843
+ borderwidth=1
844
+ )
845
+ b.var = var
846
+ b._image_file = image_file
847
+ if image_file is not None:
848
+ # Explicit class because ToolbarTk calls _Button.
849
+ NavigationToolbar2Tk._set_image_for_button(self, b)
850
+ else:
851
+ b.configure(font=self._label_font)
852
+ b.pack(side=tk.LEFT)
853
+ return b
854
+
855
+ def _Spacer(self):
856
+ # Buttons are also 18pt high.
857
+ s = tk.Frame(master=self, height='18p', relief=tk.RIDGE, bg='DarkGray')
858
+ s.pack(side=tk.LEFT, padx='3p')
859
+ return s
860
+
861
+ def save_figure(self, *args):
862
+ filetypes = self.canvas.get_supported_filetypes_grouped()
863
+ tk_filetypes = [
864
+ (name, " ".join(f"*.{ext}" for ext in exts))
865
+ for name, exts in sorted(filetypes.items())
866
+ ]
867
+
868
+ default_extension = self.canvas.get_default_filetype()
869
+ default_filetype = self.canvas.get_supported_filetypes()[default_extension]
870
+ filetype_variable = tk.StringVar(self.canvas.get_tk_widget(), default_filetype)
871
+
872
+ # adding a default extension seems to break the
873
+ # asksaveasfilename dialog when you choose various save types
874
+ # from the dropdown. Passing in the empty string seems to
875
+ # work - JDH!
876
+ # defaultextension = self.canvas.get_default_filetype()
877
+ defaultextension = ''
878
+ initialdir = os.path.expanduser(mpl.rcParams['savefig.directory'])
879
+ # get_default_filename() contains the default extension. On some platforms,
880
+ # choosing a different extension from the dropdown does not overwrite it,
881
+ # so we need to remove it to make the dropdown functional.
882
+ initialfile = pathlib.Path(self.canvas.get_default_filename()).stem
883
+ fname = tkinter.filedialog.asksaveasfilename(
884
+ master=self.canvas.get_tk_widget().master,
885
+ title='Save the figure',
886
+ filetypes=tk_filetypes,
887
+ defaultextension=defaultextension,
888
+ initialdir=initialdir,
889
+ initialfile=initialfile,
890
+ typevariable=filetype_variable
891
+ )
892
+
893
+ if fname in ["", ()]:
894
+ return None
895
+ # Save dir for next time, unless empty str (i.e., use cwd).
896
+ if initialdir != "":
897
+ mpl.rcParams['savefig.directory'] = (
898
+ os.path.dirname(str(fname)))
899
+
900
+ # If the filename contains an extension, let savefig() infer the file
901
+ # format from that. If it does not, use the selected dropdown option.
902
+ if pathlib.Path(fname).suffix[1:] != "":
903
+ extension = None
904
+ else:
905
+ extension = filetypes[filetype_variable.get()][0]
906
+
907
+ try:
908
+ self.canvas.figure.savefig(fname, format=extension)
909
+ return fname
910
+ except Exception as e:
911
+ tkinter.messagebox.showerror("Error saving file", str(e))
912
+
913
+ def set_history_buttons(self):
914
+ state_map = {True: tk.NORMAL, False: tk.DISABLED}
915
+ can_back = self._nav_stack._pos > 0
916
+ can_forward = self._nav_stack._pos < len(self._nav_stack) - 1
917
+ if "Back" in self._buttons:
918
+ self._buttons['Back']['state'] = state_map[can_back]
919
+ if "Forward" in self._buttons:
920
+ self._buttons['Forward']['state'] = state_map[can_forward]
921
+
922
+
923
+ def add_tooltip(widget, text):
924
+ tipwindow = None
925
+
926
+ def showtip(event):
927
+ """Display text in tooltip window."""
928
+ nonlocal tipwindow
929
+ if tipwindow or not text:
930
+ return
931
+ x, y, _, _ = widget.bbox("insert")
932
+ x = x + widget.winfo_rootx() + widget.winfo_width()
933
+ y = y + widget.winfo_rooty()
934
+ tipwindow = tk.Toplevel(widget)
935
+ tipwindow.overrideredirect(1)
936
+ tipwindow.geometry(f"+{x}+{y}")
937
+ try: # For Mac OS
938
+ tipwindow.tk.call("::tk::unsupported::MacWindowStyle",
939
+ "style", tipwindow._w,
940
+ "help", "noActivates")
941
+ except tk.TclError:
942
+ pass
943
+ label = tk.Label(tipwindow, text=text, justify=tk.LEFT,
944
+ relief=tk.SOLID, borderwidth=1)
945
+ label.pack(ipadx=1)
946
+
947
+ def hidetip(event):
948
+ nonlocal tipwindow
949
+ if tipwindow:
950
+ tipwindow.destroy()
951
+ tipwindow = None
952
+
953
+ widget.bind("<Enter>", showtip)
954
+ widget.bind("<Leave>", hidetip)
955
+
956
+
957
+ @backend_tools._register_tool_class(FigureCanvasTk)
958
+ class RubberbandTk(backend_tools.RubberbandBase):
959
+ def draw_rubberband(self, x0, y0, x1, y1):
960
+ NavigationToolbar2Tk.draw_rubberband(
961
+ self._make_classic_style_pseudo_toolbar(), None, x0, y0, x1, y1)
962
+
963
+ def remove_rubberband(self):
964
+ NavigationToolbar2Tk.remove_rubberband(
965
+ self._make_classic_style_pseudo_toolbar())
966
+
967
+
968
+ class ToolbarTk(ToolContainerBase, tk.Frame):
969
+ def __init__(self, toolmanager, window=None):
970
+ ToolContainerBase.__init__(self, toolmanager)
971
+ if window is None:
972
+ window = self.toolmanager.canvas.get_tk_widget().master
973
+ xmin, xmax = self.toolmanager.canvas.figure.bbox.intervalx
974
+ height, width = 50, xmax - xmin
975
+ tk.Frame.__init__(self, master=window,
976
+ width=int(width), height=int(height),
977
+ borderwidth=2)
978
+ self._label_font = tkinter.font.Font(size=10)
979
+ # This filler item ensures the toolbar is always at least two text
980
+ # lines high. Otherwise the canvas gets redrawn as the mouse hovers
981
+ # over images because those use two-line messages which resize the
982
+ # toolbar.
983
+ label = tk.Label(master=self, font=self._label_font,
984
+ text='\N{NO-BREAK SPACE}\n\N{NO-BREAK SPACE}')
985
+ label.pack(side=tk.RIGHT)
986
+ self._message = tk.StringVar(master=self)
987
+ self._message_label = tk.Label(master=self, font=self._label_font,
988
+ textvariable=self._message)
989
+ self._message_label.pack(side=tk.RIGHT)
990
+ self._toolitems = {}
991
+ self.pack(side=tk.TOP, fill=tk.X)
992
+ self._groups = {}
993
+
994
+ def _rescale(self):
995
+ return NavigationToolbar2Tk._rescale(self)
996
+
997
+ def add_toolitem(
998
+ self, name, group, position, image_file, description, toggle):
999
+ frame = self._get_groupframe(group)
1000
+ buttons = frame.pack_slaves()
1001
+ if position >= len(buttons) or position < 0:
1002
+ before = None
1003
+ else:
1004
+ before = buttons[position]
1005
+ button = NavigationToolbar2Tk._Button(frame, name, image_file, toggle,
1006
+ lambda: self._button_click(name))
1007
+ button.pack_configure(before=before)
1008
+ if description is not None:
1009
+ add_tooltip(button, description)
1010
+ self._toolitems.setdefault(name, [])
1011
+ self._toolitems[name].append(button)
1012
+
1013
+ def _get_groupframe(self, group):
1014
+ if group not in self._groups:
1015
+ if self._groups:
1016
+ self._add_separator()
1017
+ frame = tk.Frame(master=self, borderwidth=0)
1018
+ frame.pack(side=tk.LEFT, fill=tk.Y)
1019
+ frame._label_font = self._label_font
1020
+ self._groups[group] = frame
1021
+ return self._groups[group]
1022
+
1023
+ def _add_separator(self):
1024
+ return NavigationToolbar2Tk._Spacer(self)
1025
+
1026
+ def _button_click(self, name):
1027
+ self.trigger_tool(name)
1028
+
1029
+ def toggle_toolitem(self, name, toggled):
1030
+ if name not in self._toolitems:
1031
+ return
1032
+ for toolitem in self._toolitems[name]:
1033
+ if toggled:
1034
+ toolitem.select()
1035
+ else:
1036
+ toolitem.deselect()
1037
+
1038
+ def remove_toolitem(self, name):
1039
+ for toolitem in self._toolitems.pop(name, []):
1040
+ toolitem.pack_forget()
1041
+
1042
+ def set_message(self, s):
1043
+ self._message.set(s)
1044
+
1045
+
1046
+ @backend_tools._register_tool_class(FigureCanvasTk)
1047
+ class SaveFigureTk(backend_tools.SaveFigureBase):
1048
+ def trigger(self, *args):
1049
+ NavigationToolbar2Tk.save_figure(
1050
+ self._make_classic_style_pseudo_toolbar())
1051
+
1052
+
1053
+ @backend_tools._register_tool_class(FigureCanvasTk)
1054
+ class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase):
1055
+ def trigger(self, *args):
1056
+ NavigationToolbar2Tk.configure_subplots(self)
1057
+
1058
+
1059
+ @backend_tools._register_tool_class(FigureCanvasTk)
1060
+ class HelpTk(backend_tools.ToolHelpBase):
1061
+ def trigger(self, *args):
1062
+ dialog = SimpleDialog(
1063
+ self.figure.canvas._tkcanvas, self._get_help_text(), ["OK"])
1064
+ dialog.done = lambda num: dialog.frame.master.withdraw()
1065
+
1066
+
1067
+ Toolbar = ToolbarTk
1068
+ FigureManagerTk._toolbar2_class = NavigationToolbar2Tk
1069
+ FigureManagerTk._toolmanager_toolbar_class = ToolbarTk
1070
+
1071
+
1072
+ @_Backend.export
1073
+ class _BackendTk(_Backend):
1074
+ backend_version = tk.TkVersion
1075
+ FigureCanvas = FigureCanvasTk
1076
+ FigureManager = FigureManagerTk
1077
+ mainloop = FigureManagerTk.start_main_loop
lib/python3.12/site-packages/matplotlib/backends/_macosx.pyi ADDED
File without changes