Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/matplotlib/__pycache__/_animation_data.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/_cm_multivar.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/_pylab_helpers.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/backend_managers.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/colorbar.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/colorizer.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/contour.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/hatch.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/lines.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/quiver.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/rcsetup.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/__pycache__/streamplot.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/_backend_gtk.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/_backend_pdf_ps.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/_backend_tk.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_agg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_cairo.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk3.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk3agg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk3cairo.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk4.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk4agg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_gtk4cairo.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_macosx.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_mixed.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_nbagg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_pgf.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_ps.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt5.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt5agg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qt5cairo.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qtagg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_qtcairo.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_svg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_template.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_tkagg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_tkcairo.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_webagg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_webagg_core.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_wx.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_wxagg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/backend_wxcairo.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/qt_compat.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/__pycache__/registry.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/matplotlib/backends/_backend_agg.pyi +0 -0
- lib/python3.12/site-packages/matplotlib/backends/_backend_pdf_ps.py +189 -0
- lib/python3.12/site-packages/matplotlib/backends/_backend_tk.py +1077 -0
- 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
|