123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907 |
- from contextlib import contextmanager
- import logging
- import math
- import os.path
- import sys
- import tkinter as tk
- from tkinter.simpledialog import SimpleDialog
- import tkinter.filedialog
- import tkinter.messagebox
- import numpy as np
- import matplotlib
- from matplotlib import backend_tools, cbook, rcParams
- from matplotlib.backend_bases import (
- _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
- StatusbarBase, TimerBase, ToolContainerBase, cursors)
- from matplotlib.backend_managers import ToolManager
- from matplotlib._pylab_helpers import Gcf
- from matplotlib.figure import Figure
- from matplotlib.widgets import SubplotTool
- from . import _tkagg
- try:
- from ._tkagg import Win32_GetForegroundWindow, Win32_SetForegroundWindow
- except ImportError:
- @contextmanager
- def _restore_foreground_window_at_end():
- yield
- else:
- @contextmanager
- def _restore_foreground_window_at_end():
- foreground = Win32_GetForegroundWindow()
- try:
- yield
- finally:
- if rcParams['tk.window_focus']:
- Win32_SetForegroundWindow(foreground)
- _log = logging.getLogger(__name__)
- backend_version = tk.TkVersion
- cursord = {
- cursors.MOVE: "fleur",
- cursors.HAND: "hand2",
- cursors.POINTER: "arrow",
- cursors.SELECT_REGION: "tcross",
- cursors.WAIT: "watch",
- }
- def blit(photoimage, aggimage, offsets, bbox=None):
- """
- Blit *aggimage* to *photoimage*.
- *offsets* is a tuple describing how to fill the ``offset`` field of the
- ``Tk_PhotoImageBlock`` struct: it should be (0, 1, 2, 3) for RGBA8888 data,
- (2, 1, 0, 3) for little-endian ARBG32 (i.e. GBRA8888) data and (1, 2, 3, 0)
- for big-endian ARGB32 (i.e. ARGB8888) data.
- If *bbox* is passed, it defines the region that gets blitted.
- """
- data = np.asarray(aggimage)
- height, width = data.shape[:2]
- dataptr = (height, width, data.ctypes.data)
- if bbox is not None:
- (x1, y1), (x2, y2) = bbox.__array__()
- x1 = max(math.floor(x1), 0)
- x2 = min(math.ceil(x2), width)
- y1 = max(math.floor(y1), 0)
- y2 = min(math.ceil(y2), height)
- bboxptr = (x1, x2, y1, y2)
- else:
- photoimage.blank()
- bboxptr = (0, width, 0, height)
- _tkagg.blit(
- photoimage.tk.interpaddr(), str(photoimage), dataptr, offsets, bboxptr)
- class TimerTk(TimerBase):
- '''
- Subclass of :class:`backend_bases.TimerBase` that uses Tk's timer events.
- Attributes
- ----------
- interval : int
- The time between timer events in milliseconds. Default is 1000 ms.
- single_shot : bool
- Boolean flag indicating whether this timer should operate as single
- shot (run once and then stop). Defaults to False.
- callbacks : list
- Stores list of (func, args) tuples that will be called upon timer
- events. This list can be manipulated directly, or the functions
- `add_callback` and `remove_callback` can be used.
- '''
- def __init__(self, parent, *args, **kwargs):
- TimerBase.__init__(self, *args, **kwargs)
- self.parent = parent
- self._timer = None
- def _timer_start(self):
- self._timer_stop()
- self._timer = self.parent.after(self._interval, self._on_timer)
- def _timer_stop(self):
- if self._timer is not None:
- self.parent.after_cancel(self._timer)
- self._timer = None
- def _on_timer(self):
- TimerBase._on_timer(self)
- # Tk after() is only a single shot, so we need to add code here to
- # reset the timer if we're not operating in single shot mode. However,
- # if _timer is None, this means that _timer_stop has been called; so
- # don't recreate the timer in that case.
- if not self._single and self._timer:
- self._timer = self.parent.after(self._interval, self._on_timer)
- else:
- self._timer = None
- class FigureCanvasTk(FigureCanvasBase):
- required_interactive_framework = "tk"
- keyvald = {65507: 'control',
- 65505: 'shift',
- 65513: 'alt',
- 65515: 'super',
- 65508: 'control',
- 65506: 'shift',
- 65514: 'alt',
- 65361: 'left',
- 65362: 'up',
- 65363: 'right',
- 65364: 'down',
- 65307: 'escape',
- 65470: 'f1',
- 65471: 'f2',
- 65472: 'f3',
- 65473: 'f4',
- 65474: 'f5',
- 65475: 'f6',
- 65476: 'f7',
- 65477: 'f8',
- 65478: 'f9',
- 65479: 'f10',
- 65480: 'f11',
- 65481: 'f12',
- 65300: 'scroll_lock',
- 65299: 'break',
- 65288: 'backspace',
- 65293: 'enter',
- 65379: 'insert',
- 65535: 'delete',
- 65360: 'home',
- 65367: 'end',
- 65365: 'pageup',
- 65366: 'pagedown',
- 65438: '0',
- 65436: '1',
- 65433: '2',
- 65435: '3',
- 65430: '4',
- 65437: '5',
- 65432: '6',
- 65429: '7',
- 65431: '8',
- 65434: '9',
- 65451: '+',
- 65453: '-',
- 65450: '*',
- 65455: '/',
- 65439: 'dec',
- 65421: 'enter',
- }
- _keycode_lookup = {
- 262145: 'control',
- 524320: 'alt',
- 524352: 'alt',
- 1048584: 'super',
- 1048592: 'super',
- 131074: 'shift',
- 131076: 'shift',
- }
- """_keycode_lookup is used for badly mapped (i.e. no event.key_sym set)
- keys on apple keyboards."""
- def __init__(self, figure, master=None, resize_callback=None):
- super(FigureCanvasTk, self).__init__(figure)
- self._idle = True
- self._idle_callback = None
- t1, t2, w, h = self.figure.bbox.bounds
- w, h = int(w), int(h)
- self._tkcanvas = tk.Canvas(
- master=master, background="white",
- width=w, height=h, borderwidth=0, highlightthickness=0)
- self._tkphoto = tk.PhotoImage(
- master=self._tkcanvas, width=w, height=h)
- self._tkcanvas.create_image(w//2, h//2, image=self._tkphoto)
- self._resize_callback = resize_callback
- self._tkcanvas.bind("<Configure>", self.resize)
- self._tkcanvas.bind("<Key>", self.key_press)
- self._tkcanvas.bind("<Motion>", self.motion_notify_event)
- self._tkcanvas.bind("<Enter>", self.enter_notify_event)
- self._tkcanvas.bind("<Leave>", self.leave_notify_event)
- self._tkcanvas.bind("<KeyRelease>", self.key_release)
- for name in ["<Button-1>", "<Button-2>", "<Button-3>"]:
- self._tkcanvas.bind(name, self.button_press_event)
- for name in [
- "<Double-Button-1>", "<Double-Button-2>", "<Double-Button-3>"]:
- self._tkcanvas.bind(name, self.button_dblclick_event)
- for name in [
- "<ButtonRelease-1>", "<ButtonRelease-2>", "<ButtonRelease-3>"]:
- self._tkcanvas.bind(name, self.button_release_event)
- # Mouse wheel on Linux generates button 4/5 events
- for name in "<Button-4>", "<Button-5>":
- self._tkcanvas.bind(name, self.scroll_event)
- # Mouse wheel for windows goes to the window with the focus.
- # Since the canvas won't usually have the focus, bind the
- # event to the window containing the canvas instead.
- # See http://wiki.tcl.tk/3893 (mousewheel) for details
- root = self._tkcanvas.winfo_toplevel()
- root.bind("<MouseWheel>", self.scroll_event_windows, "+")
- # Can't get destroy events by binding to _tkcanvas. Therefore, bind
- # to the window and filter.
- def filter_destroy(evt):
- if evt.widget is self._tkcanvas:
- self._master.update_idletasks()
- self.close_event()
- root.bind("<Destroy>", filter_destroy, "+")
- self._master = master
- self._tkcanvas.focus_set()
- def resize(self, event):
- width, height = event.width, event.height
- if self._resize_callback is not None:
- self._resize_callback(event)
- # compute desired figure size in inches
- dpival = self.figure.dpi
- winch = width / dpival
- hinch = height / dpival
- self.figure.set_size_inches(winch, hinch, forward=False)
- self._tkcanvas.delete(self._tkphoto)
- self._tkphoto = tk.PhotoImage(
- master=self._tkcanvas, width=int(width), height=int(height))
- self._tkcanvas.create_image(
- int(width / 2), int(height / 2), image=self._tkphoto)
- self.resize_event()
- self.draw()
- def draw_idle(self):
- # docstring inherited
- if not self._idle:
- return
- self._idle = False
- def idle_draw(*args):
- try:
- self.draw()
- finally:
- self._idle = True
- self._idle_callback = self._tkcanvas.after_idle(idle_draw)
- def get_tk_widget(self):
- """Return the Tk widget used to implement FigureCanvasTkAgg.
- Although the initial implementation uses a Tk canvas, this routine
- is intended to hide that fact.
- """
- return self._tkcanvas
- def motion_notify_event(self, event):
- x = event.x
- # flipy so y=0 is bottom of canvas
- y = self.figure.bbox.height - event.y
- FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event)
- def enter_notify_event(self, event):
- x = event.x
- # flipy so y=0 is bottom of canvas
- y = self.figure.bbox.height - event.y
- FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y))
- def button_press_event(self, event, dblclick=False):
- x = event.x
- # flipy so y=0 is bottom of canvas
- y = self.figure.bbox.height - event.y
- num = getattr(event, 'num', None)
- if sys.platform == 'darwin':
- # 2 and 3 were reversed on the OSX platform I tested under tkagg.
- if num == 2:
- num = 3
- elif num == 3:
- num = 2
- FigureCanvasBase.button_press_event(
- self, x, y, num, dblclick=dblclick, guiEvent=event)
- def button_dblclick_event(self, event):
- self.button_press_event(event, dblclick=True)
- def button_release_event(self, event):
- x = event.x
- # flipy so y=0 is bottom of canvas
- y = self.figure.bbox.height - event.y
- num = getattr(event, 'num', None)
- if sys.platform == 'darwin':
- # 2 and 3 were reversed on the OSX platform I tested under tkagg.
- if num == 2:
- num = 3
- elif num == 3:
- num = 2
- FigureCanvasBase.button_release_event(self, x, y, num, guiEvent=event)
- def scroll_event(self, event):
- x = event.x
- y = self.figure.bbox.height - event.y
- num = getattr(event, 'num', None)
- step = 1 if num == 4 else -1 if num == 5 else 0
- FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
- def scroll_event_windows(self, event):
- """MouseWheel event processor"""
- # need to find the window that contains the mouse
- w = event.widget.winfo_containing(event.x_root, event.y_root)
- if w == self._tkcanvas:
- x = event.x_root - w.winfo_rootx()
- y = event.y_root - w.winfo_rooty()
- y = self.figure.bbox.height - y
- step = event.delta/120.
- FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
- def _get_key(self, event):
- val = event.keysym_num
- if val in self.keyvald:
- key = self.keyvald[val]
- elif (val == 0 and sys.platform == 'darwin'
- and event.keycode in self._keycode_lookup):
- key = self._keycode_lookup[event.keycode]
- elif val < 256:
- key = chr(val)
- else:
- key = None
- # add modifier keys to the key string. Bit details originate from
- # http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
- # BIT_SHIFT = 0x001; BIT_CAPSLOCK = 0x002; BIT_CONTROL = 0x004;
- # BIT_LEFT_ALT = 0x008; BIT_NUMLOCK = 0x010; BIT_RIGHT_ALT = 0x080;
- # BIT_MB_1 = 0x100; BIT_MB_2 = 0x200; BIT_MB_3 = 0x400;
- # In general, the modifier key is excluded from the modifier flag,
- # however this is not the case on "darwin", so double check that
- # we aren't adding repeat modifier flags to a modifier key.
- if sys.platform == 'win32':
- modifiers = [(17, 'alt', 'alt'),
- (2, 'ctrl', 'control'),
- ]
- elif sys.platform == 'darwin':
- modifiers = [(3, 'super', 'super'),
- (4, 'alt', 'alt'),
- (2, 'ctrl', 'control'),
- ]
- else:
- modifiers = [(6, 'super', 'super'),
- (3, 'alt', 'alt'),
- (2, 'ctrl', 'control'),
- ]
- if key is not None:
- # shift is not added to the keys as this is already accounted for
- for bitmask, prefix, key_name in modifiers:
- if event.state & (1 << bitmask) and key_name not in key:
- key = '{0}+{1}'.format(prefix, key)
- return key
- def key_press(self, event):
- key = self._get_key(event)
- FigureCanvasBase.key_press_event(self, key, guiEvent=event)
- def key_release(self, event):
- key = self._get_key(event)
- FigureCanvasBase.key_release_event(self, key, guiEvent=event)
- def new_timer(self, *args, **kwargs):
- # docstring inherited
- return TimerTk(self._tkcanvas, *args, **kwargs)
- def flush_events(self):
- # docstring inherited
- self._master.update()
- class FigureManagerTk(FigureManagerBase):
- """
- Attributes
- ----------
- canvas : `FigureCanvas`
- The FigureCanvas instance
- num : int or str
- The Figure number
- toolbar : tk.Toolbar
- The tk.Toolbar
- window : tk.Window
- The tk.Window
- """
- def __init__(self, canvas, num, window):
- FigureManagerBase.__init__(self, canvas, num)
- self.window = window
- self.window.withdraw()
- self.set_window_title("Figure %d" % num)
- self.canvas = canvas
- # If using toolmanager it has to be present when initializing the
- # toolbar
- self.toolmanager = self._get_toolmanager()
- # packing toolbar first, because if space is getting low, last packed
- # widget is getting shrunk first (-> the canvas)
- self.toolbar = self._get_toolbar()
- self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
- self.statusbar = None
- if self.toolmanager:
- backend_tools.add_tools_to_manager(self.toolmanager)
- if self.toolbar:
- backend_tools.add_tools_to_container(self.toolbar)
- self.statusbar = StatusbarTk(self.window, self.toolmanager)
- self._shown = False
- def _get_toolbar(self):
- if matplotlib.rcParams['toolbar'] == 'toolbar2':
- toolbar = NavigationToolbar2Tk(self.canvas, self.window)
- elif matplotlib.rcParams['toolbar'] == 'toolmanager':
- toolbar = ToolbarTk(self.toolmanager, self.window)
- else:
- toolbar = None
- return toolbar
- def _get_toolmanager(self):
- if rcParams['toolbar'] == 'toolmanager':
- toolmanager = ToolManager(self.canvas.figure)
- else:
- toolmanager = None
- return toolmanager
- def resize(self, width, height):
- self.canvas._tkcanvas.master.geometry("%dx%d" % (width, height))
- if self.toolbar is not None:
- self.toolbar.configure(width=width)
- def show(self):
- with _restore_foreground_window_at_end():
- if not self._shown:
- def destroy(*args):
- self.window = None
- Gcf.destroy(self.num)
- self.canvas._tkcanvas.bind("<Destroy>", destroy)
- self.window.deiconify()
- else:
- self.canvas.draw_idle()
- # Raise the new window.
- self.canvas.manager.window.attributes('-topmost', 1)
- self.canvas.manager.window.attributes('-topmost', 0)
- self._shown = True
- def destroy(self, *args):
- if self.window is not None:
- #self.toolbar.destroy()
- if self.canvas._idle_callback:
- self.canvas._tkcanvas.after_cancel(self.canvas._idle_callback)
- self.window.destroy()
- if Gcf.get_num_fig_managers() == 0:
- if self.window is not None:
- self.window.quit()
- self.window = None
- def get_window_title(self):
- return self.window.wm_title()
- def set_window_title(self, title):
- self.window.wm_title(title)
- def full_screen_toggle(self):
- is_fullscreen = bool(self.window.attributes('-fullscreen'))
- self.window.attributes('-fullscreen', not is_fullscreen)
- class NavigationToolbar2Tk(NavigationToolbar2, tk.Frame):
- """
- Attributes
- ----------
- canvas : `FigureCanvas`
- the figure canvas on which to operate
- win : tk.Window
- the tk.Window which owns this toolbar
- """
- def __init__(self, canvas, window):
- self.canvas = canvas
- # Avoid using self.window (prefer self.canvas.get_tk_widget().master),
- # so that Tool implementations can reuse the methods.
- self.window = window
- NavigationToolbar2.__init__(self, canvas)
- def destroy(self, *args):
- del self.message
- tk.Frame.destroy(self, *args)
- def set_message(self, s):
- self.message.set(s)
- def draw_rubberband(self, event, x0, y0, x1, y1):
- height = self.canvas.figure.bbox.height
- y0 = height - y0
- y1 = height - y1
- if hasattr(self, "lastrect"):
- self.canvas._tkcanvas.delete(self.lastrect)
- self.lastrect = self.canvas._tkcanvas.create_rectangle(x0, y0, x1, y1)
- def release(self, event):
- if hasattr(self, "lastrect"):
- self.canvas._tkcanvas.delete(self.lastrect)
- del self.lastrect
- def set_cursor(self, cursor):
- window = self.canvas.get_tk_widget().master
- window.configure(cursor=cursord[cursor])
- window.update_idletasks()
- def _Button(self, text, file, command, extension='.gif'):
- img_file = str(cbook._get_data_path('images', file + extension))
- im = tk.PhotoImage(master=self, file=img_file)
- b = tk.Button(
- master=self, text=text, padx=2, pady=2, image=im, command=command)
- b._ntimage = im
- b.pack(side=tk.LEFT)
- return b
- def _Spacer(self):
- # Buttons are 30px high. Make this 26px tall +2px padding to center it.
- s = tk.Frame(
- master=self, height=26, relief=tk.RIDGE, pady=2, bg="DarkGray")
- s.pack(side=tk.LEFT, padx=5)
- return s
- def _init_toolbar(self):
- xmin, xmax = self.canvas.figure.bbox.intervalx
- height, width = 50, xmax-xmin
- tk.Frame.__init__(self, master=self.window,
- width=int(width), height=int(height),
- borderwidth=2)
- self.update() # Make axes menu
- for text, tooltip_text, image_file, callback in self.toolitems:
- if text is None:
- # Add a spacer; return value is unused.
- self._Spacer()
- else:
- button = self._Button(text=text, file=image_file,
- command=getattr(self, callback))
- if tooltip_text is not None:
- ToolTip.createToolTip(button, tooltip_text)
- self.message = tk.StringVar(master=self)
- self._message_label = tk.Label(master=self, textvariable=self.message)
- self._message_label.pack(side=tk.RIGHT)
- self.pack(side=tk.BOTTOM, fill=tk.X)
- def configure_subplots(self):
- toolfig = Figure(figsize=(6, 3))
- window = tk.Toplevel()
- canvas = type(self.canvas)(toolfig, master=window)
- toolfig.subplots_adjust(top=0.9)
- canvas.tool = SubplotTool(self.canvas.figure, toolfig)
- canvas.draw()
- canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
- window.grab_set()
- def save_figure(self, *args):
- filetypes = self.canvas.get_supported_filetypes().copy()
- default_filetype = self.canvas.get_default_filetype()
- # Tk doesn't provide a way to choose a default filetype,
- # so we just have to put it first
- default_filetype_name = filetypes.pop(default_filetype)
- sorted_filetypes = ([(default_filetype, default_filetype_name)]
- + sorted(filetypes.items()))
- tk_filetypes = [(name, '*.%s' % ext) for ext, name in sorted_filetypes]
- # adding a default extension seems to break the
- # asksaveasfilename dialog when you choose various save types
- # from the dropdown. Passing in the empty string seems to
- # work - JDH!
- #defaultextension = self.canvas.get_default_filetype()
- defaultextension = ''
- initialdir = os.path.expanduser(rcParams['savefig.directory'])
- initialfile = self.canvas.get_default_filename()
- fname = tkinter.filedialog.asksaveasfilename(
- master=self.canvas.get_tk_widget().master,
- title='Save the figure',
- filetypes=tk_filetypes,
- defaultextension=defaultextension,
- initialdir=initialdir,
- initialfile=initialfile,
- )
- if fname in ["", ()]:
- return
- # Save dir for next time, unless empty str (i.e., use cwd).
- if initialdir != "":
- rcParams['savefig.directory'] = (
- os.path.dirname(str(fname)))
- try:
- # This method will handle the delegation to the correct type
- self.canvas.figure.savefig(fname)
- except Exception as e:
- tkinter.messagebox.showerror("Error saving file", str(e))
- @cbook.deprecated("3.1")
- def set_active(self, ind):
- self._ind = ind
- self._active = [self._axes[i] for i in self._ind]
- def update(self):
- self._axes = self.canvas.figure.axes
- with _restore_foreground_window_at_end():
- NavigationToolbar2.update(self)
- class ToolTip:
- """
- Tooltip recipe from
- http://www.voidspace.org.uk/python/weblog/arch_d7_2006_07_01.shtml#e387
- """
- @staticmethod
- def createToolTip(widget, text):
- toolTip = ToolTip(widget)
- def enter(event):
- toolTip.showtip(text)
- def leave(event):
- toolTip.hidetip()
- widget.bind('<Enter>', enter)
- widget.bind('<Leave>', leave)
- def __init__(self, widget):
- self.widget = widget
- self.tipwindow = None
- self.id = None
- self.x = self.y = 0
- def showtip(self, text):
- "Display text in tooltip window"
- self.text = text
- if self.tipwindow or not self.text:
- return
- x, y, _, _ = self.widget.bbox("insert")
- x = x + self.widget.winfo_rootx() + 27
- y = y + self.widget.winfo_rooty()
- self.tipwindow = tw = tk.Toplevel(self.widget)
- tw.wm_overrideredirect(1)
- tw.wm_geometry("+%d+%d" % (x, y))
- try:
- # For Mac OS
- tw.tk.call("::tk::unsupported::MacWindowStyle",
- "style", tw._w,
- "help", "noActivates")
- except tk.TclError:
- pass
- label = tk.Label(tw, text=self.text, justify=tk.LEFT,
- background="#ffffe0", relief=tk.SOLID, borderwidth=1)
- label.pack(ipadx=1)
- def hidetip(self):
- tw = self.tipwindow
- self.tipwindow = None
- if tw:
- tw.destroy()
- class RubberbandTk(backend_tools.RubberbandBase):
- def draw_rubberband(self, x0, y0, x1, y1):
- height = self.figure.canvas.figure.bbox.height
- y0 = height - y0
- y1 = height - y1
- if hasattr(self, "lastrect"):
- self.figure.canvas._tkcanvas.delete(self.lastrect)
- self.lastrect = self.figure.canvas._tkcanvas.create_rectangle(
- x0, y0, x1, y1)
- def remove_rubberband(self):
- if hasattr(self, "lastrect"):
- self.figure.canvas._tkcanvas.delete(self.lastrect)
- del self.lastrect
- class SetCursorTk(backend_tools.SetCursorBase):
- def set_cursor(self, cursor):
- NavigationToolbar2Tk.set_cursor(
- self._make_classic_style_pseudo_toolbar(), cursor)
- class ToolbarTk(ToolContainerBase, tk.Frame):
- _icon_extension = '.gif'
- def __init__(self, toolmanager, window):
- ToolContainerBase.__init__(self, toolmanager)
- xmin, xmax = self.toolmanager.canvas.figure.bbox.intervalx
- height, width = 50, xmax - xmin
- tk.Frame.__init__(self, master=window,
- width=int(width), height=int(height),
- borderwidth=2)
- self._toolitems = {}
- self.pack(side=tk.TOP, fill=tk.X)
- self._groups = {}
- def add_toolitem(
- self, name, group, position, image_file, description, toggle):
- frame = self._get_groupframe(group)
- button = self._Button(name, image_file, toggle, frame)
- if description is not None:
- ToolTip.createToolTip(button, description)
- self._toolitems.setdefault(name, [])
- self._toolitems[name].append(button)
- def _get_groupframe(self, group):
- if group not in self._groups:
- if self._groups:
- self._add_separator()
- frame = tk.Frame(master=self, borderwidth=0)
- frame.pack(side=tk.LEFT, fill=tk.Y)
- self._groups[group] = frame
- return self._groups[group]
- def _add_separator(self):
- separator = tk.Frame(master=self, bd=5, width=1, bg='black')
- separator.pack(side=tk.LEFT, fill=tk.Y, padx=2)
- def _Button(self, text, image_file, toggle, frame):
- if image_file is not None:
- im = tk.PhotoImage(master=self, file=image_file)
- else:
- im = None
- if not toggle:
- b = tk.Button(master=frame, text=text, padx=2, pady=2, image=im,
- command=lambda: self._button_click(text))
- else:
- # There is a bug in tkinter included in some python 3.6 versions
- # that without this variable, produces a "visual" toggling of
- # other near checkbuttons
- # https://bugs.python.org/issue29402
- # https://bugs.python.org/issue25684
- var = tk.IntVar()
- b = tk.Checkbutton(master=frame, text=text, padx=2, pady=2,
- image=im, indicatoron=False,
- command=lambda: self._button_click(text),
- variable=var)
- b._ntimage = im
- b.pack(side=tk.LEFT)
- return b
- def _button_click(self, name):
- self.trigger_tool(name)
- def toggle_toolitem(self, name, toggled):
- if name not in self._toolitems:
- return
- for toolitem in self._toolitems[name]:
- if toggled:
- toolitem.select()
- else:
- toolitem.deselect()
- def remove_toolitem(self, name):
- for toolitem in self._toolitems[name]:
- toolitem.pack_forget()
- del self._toolitems[name]
- class StatusbarTk(StatusbarBase, tk.Frame):
- def __init__(self, window, *args, **kwargs):
- StatusbarBase.__init__(self, *args, **kwargs)
- xmin, xmax = self.toolmanager.canvas.figure.bbox.intervalx
- height, width = 50, xmax - xmin
- tk.Frame.__init__(self, master=window,
- width=int(width), height=int(height),
- borderwidth=2)
- self._message = tk.StringVar(master=self)
- self._message_label = tk.Label(master=self, textvariable=self._message)
- self._message_label.pack(side=tk.RIGHT)
- self.pack(side=tk.TOP, fill=tk.X)
- def set_message(self, s):
- self._message.set(s)
- class SaveFigureTk(backend_tools.SaveFigureBase):
- def trigger(self, *args):
- NavigationToolbar2Tk.save_figure(
- self._make_classic_style_pseudo_toolbar())
- class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase):
- def __init__(self, *args, **kwargs):
- backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs)
- self.window = None
- def trigger(self, *args):
- self.init_window()
- self.window.lift()
- def init_window(self):
- if self.window:
- return
- toolfig = Figure(figsize=(6, 3))
- self.window = tk.Tk()
- canvas = type(self.canvas)(toolfig, master=self.window)
- toolfig.subplots_adjust(top=0.9)
- SubplotTool(self.figure, toolfig)
- canvas.draw()
- canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
- self.window.protocol("WM_DELETE_WINDOW", self.destroy)
- def destroy(self, *args, **kwargs):
- if self.window is not None:
- self.window.destroy()
- self.window = None
- class HelpTk(backend_tools.ToolHelpBase):
- def trigger(self, *args):
- dialog = SimpleDialog(
- self.figure.canvas._tkcanvas, self._get_help_text(), ["OK"])
- dialog.done = lambda num: dialog.frame.master.withdraw()
- backend_tools.ToolSaveFigure = SaveFigureTk
- backend_tools.ToolConfigureSubplots = ConfigureSubplotsTk
- backend_tools.ToolSetCursor = SetCursorTk
- backend_tools.ToolRubberband = RubberbandTk
- backend_tools.ToolHelp = HelpTk
- backend_tools.ToolCopyToClipboard = backend_tools.ToolCopyToClipboardBase
- Toolbar = ToolbarTk
- @_Backend.export
- class _BackendTk(_Backend):
- FigureManager = FigureManagerTk
- @classmethod
- def new_figure_manager_given_figure(cls, num, figure):
- """
- Create a new figure manager instance for the given figure.
- """
- with _restore_foreground_window_at_end():
- window = tk.Tk(className="matplotlib")
- window.withdraw()
- # Put a Matplotlib icon on the window rather than the default tk
- # icon. Tkinter doesn't allow colour icons on linux systems, but
- # tk>=8.5 has a iconphoto command which we call directly. See
- # http://mail.python.org/pipermail/tkinter-discuss/2006-November/000954.html
- icon_fname = str(cbook._get_data_path(
- 'images/matplotlib_128.ppm'))
- icon_img = tk.PhotoImage(file=icon_fname, master=window)
- try:
- window.iconphoto(False, icon_img)
- except Exception as exc:
- # log the failure (due e.g. to Tk version), but carry on
- _log.info('Could not load matplotlib icon: %s', exc)
- canvas = cls.FigureCanvas(figure, master=window)
- manager = cls.FigureManager(canvas, num, window)
- if matplotlib.is_interactive():
- manager.show()
- canvas.draw_idle()
- return manager
- @staticmethod
- def trigger_manager_draw(manager):
- manager.show()
- @staticmethod
- def mainloop():
- managers = Gcf.get_all_fig_managers()
- if managers:
- managers[0].window.mainloop()
|